Skip to content

Commit 6d1a821

Browse files
committed
Add repository integration tests for Azure (#46263)
Similarly to what had been done for S3 (#46081) and GCS (#46255) this commit adds repository integration tests for Azure, based on an internal HTTP server instead of mocks.
1 parent 9563d65 commit 6d1a821

File tree

2 files changed

+191
-1
lines changed

2 files changed

+191
-1
lines changed

plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageSettings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ final class AzureStorageSettings {
6666
/**
6767
* Azure endpoint suffix. Default to core.windows.net (CloudStorageAccount.DEFAULT_DNS).
6868
*/
69-
public static final Setting<String> ENDPOINT_SUFFIX_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "endpoint_suffix",
69+
public static final AffixSetting<String> ENDPOINT_SUFFIX_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "endpoint_suffix",
7070
key -> Setting.simpleString(key, Property.NodeScope), ACCOUNT_SETTING, KEY_SETTING);
7171

7272
public static final AffixSetting<TimeValue> TIMEOUT_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "timeout",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.repositories.azure;
20+
21+
import com.sun.net.httpserver.HttpExchange;
22+
import com.sun.net.httpserver.HttpHandler;
23+
import com.sun.net.httpserver.HttpServer;
24+
import org.elasticsearch.common.SuppressForbidden;
25+
import org.elasticsearch.common.bytes.BytesReference;
26+
import org.elasticsearch.common.io.Streams;
27+
import org.elasticsearch.common.network.InetAddresses;
28+
import org.elasticsearch.common.regex.Regex;
29+
import org.elasticsearch.common.settings.MockSecureSettings;
30+
import org.elasticsearch.common.settings.Settings;
31+
import org.elasticsearch.mocksocket.MockHttpServer;
32+
import org.elasticsearch.plugins.Plugin;
33+
import org.elasticsearch.repositories.blobstore.ESBlobStoreRepositoryIntegTestCase;
34+
import org.elasticsearch.rest.RestStatus;
35+
import org.elasticsearch.rest.RestUtils;
36+
import org.junit.After;
37+
import org.junit.AfterClass;
38+
import org.junit.Before;
39+
import org.junit.BeforeClass;
40+
41+
import java.io.IOException;
42+
import java.net.InetAddress;
43+
import java.net.InetSocketAddress;
44+
import java.nio.charset.StandardCharsets;
45+
import java.util.Base64;
46+
import java.util.Collection;
47+
import java.util.Collections;
48+
import java.util.HashMap;
49+
import java.util.Map;
50+
import java.util.concurrent.ConcurrentHashMap;
51+
52+
@SuppressForbidden(reason = "this test uses a HttpServer to emulate an Azure endpoint")
53+
public class AzureBlobStoreRepositoryTests extends ESBlobStoreRepositoryIntegTestCase {
54+
55+
private static HttpServer httpServer;
56+
57+
@BeforeClass
58+
public static void startHttpServer() throws Exception {
59+
httpServer = MockHttpServer.createHttp(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
60+
httpServer.start();
61+
}
62+
63+
@Before
64+
public void setUpHttpServer() {
65+
httpServer.createContext("/container", new InternalHttpHandler());
66+
}
67+
68+
@AfterClass
69+
public static void stopHttpServer() {
70+
httpServer.stop(0);
71+
httpServer = null;
72+
}
73+
74+
@After
75+
public void tearDownHttpServer() {
76+
httpServer.removeContext("/container");
77+
}
78+
79+
@Override
80+
protected String repositoryType() {
81+
return AzureRepository.TYPE;
82+
}
83+
84+
@Override
85+
protected Settings repositorySettings() {
86+
return Settings.builder()
87+
.put(AzureRepository.Repository.CONTAINER_SETTING.getKey(), "container")
88+
.put(AzureStorageSettings.ACCOUNT_SETTING.getKey(), "test")
89+
.build();
90+
}
91+
92+
@Override
93+
protected Collection<Class<? extends Plugin>> nodePlugins() {
94+
return Collections.singletonList(AzureRepositoryPlugin.class);
95+
}
96+
97+
@Override
98+
protected Settings nodeSettings(int nodeOrdinal) {
99+
final String key = Base64.getEncoder().encodeToString(randomAlphaOfLength(10).getBytes(StandardCharsets.UTF_8));
100+
final MockSecureSettings secureSettings = new MockSecureSettings();
101+
secureSettings.setString(AzureStorageSettings.ACCOUNT_SETTING.getConcreteSettingForNamespace("test").getKey(), "account");
102+
secureSettings.setString(AzureStorageSettings.KEY_SETTING.getConcreteSettingForNamespace("test").getKey(), key);
103+
104+
final InetSocketAddress address = httpServer.getAddress();
105+
final String endpoint = "ignored;DefaultEndpointsProtocol=http;BlobEndpoint=http://"
106+
+ InetAddresses.toUriString(address.getAddress()) + ":" + address.getPort();
107+
108+
return Settings.builder()
109+
.put(super.nodeSettings(nodeOrdinal))
110+
.put(AzureStorageSettings.ENDPOINT_SUFFIX_SETTING.getConcreteSettingForNamespace("test").getKey(), endpoint)
111+
.setSecureSettings(secureSettings)
112+
.build();
113+
}
114+
115+
/**
116+
* Minimal HTTP handler that acts as an Azure compliant server
117+
*/
118+
@SuppressForbidden(reason = "this test uses a HttpServer to emulate an Azure endpoint")
119+
private static class InternalHttpHandler implements HttpHandler {
120+
121+
private final Map<String, BytesReference> blobs = new ConcurrentHashMap<>();
122+
123+
@Override
124+
public void handle(final HttpExchange exchange) throws IOException {
125+
final String request = exchange.getRequestMethod() + " " + exchange.getRequestURI().toString();
126+
try {
127+
if (Regex.simpleMatch("PUT /container/*", request)) {
128+
blobs.put(exchange.getRequestURI().toString(), Streams.readFully(exchange.getRequestBody()));
129+
exchange.sendResponseHeaders(RestStatus.CREATED.getStatus(), -1);
130+
131+
} else if (Regex.simpleMatch("HEAD /container/*", request)) {
132+
BytesReference blob = blobs.get(exchange.getRequestURI().toString());
133+
if (blob == null) {
134+
exchange.sendResponseHeaders(RestStatus.NOT_FOUND.getStatus(), -1);
135+
return;
136+
}
137+
exchange.getResponseHeaders().add("x-ms-blob-content-length", String.valueOf(blob.length()));
138+
exchange.getResponseHeaders().add("x-ms-blob-type", "blockblob");
139+
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), -1);
140+
141+
} else if (Regex.simpleMatch("GET /container/*", request)) {
142+
final BytesReference blob = blobs.get(exchange.getRequestURI().toString());
143+
if (blob == null) {
144+
exchange.sendResponseHeaders(RestStatus.NOT_FOUND.getStatus(), -1);
145+
return;
146+
}
147+
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
148+
exchange.getResponseHeaders().add("x-ms-blob-content-length", String.valueOf(blob.length()));
149+
exchange.getResponseHeaders().add("x-ms-blob-type", "blockblob");
150+
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), blob.length());
151+
blob.writeTo(exchange.getResponseBody());
152+
153+
} else if (Regex.simpleMatch("DELETE /container/*", request)) {
154+
Streams.readFully(exchange.getRequestBody());
155+
blobs.entrySet().removeIf(blob -> blob.getKey().startsWith(exchange.getRequestURI().toString()));
156+
exchange.sendResponseHeaders(RestStatus.ACCEPTED.getStatus(), -1);
157+
158+
} else if (Regex.simpleMatch("GET /container?restype=container&comp=list*", request)) {
159+
final Map<String, String> params = new HashMap<>();
160+
RestUtils.decodeQueryString(exchange.getRequestURI().getQuery(), 0, params);
161+
162+
final StringBuilder list = new StringBuilder();
163+
list.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
164+
list.append("<EnumerationResults>");
165+
final String prefix = params.get("prefix");
166+
list.append("<Blobs>");
167+
for (Map.Entry<String, BytesReference> blob : blobs.entrySet()) {
168+
if (prefix == null || blob.getKey().startsWith("/container/" + prefix)) {
169+
list.append("<Blob><Name>").append(blob.getKey().replace("/container/", "")).append("</Name>");
170+
list.append("<Properties><Content-Length>").append(blob.getValue().length()).append("</Content-Length>");
171+
list.append("<BlobType>BlockBlob</BlobType></Properties></Blob>");
172+
}
173+
}
174+
list.append("</Blobs>");
175+
list.append("</EnumerationResults>");
176+
177+
byte[] response = list.toString().getBytes(StandardCharsets.UTF_8);
178+
exchange.getResponseHeaders().add("Content-Type", "application/xml");
179+
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), response.length);
180+
exchange.getResponseBody().write(response);
181+
182+
} else {
183+
exchange.sendResponseHeaders(RestStatus.BAD_REQUEST.getStatus(), -1);
184+
}
185+
} finally {
186+
exchange.close();
187+
}
188+
}
189+
}
190+
}

0 commit comments

Comments
 (0)