Skip to content

Commit 26fc8ad

Browse files
authored
Use fixture to test repository-azure plugin (#29347)
This commit adds a new fixture that emulates an Azure Storage service in order to improve the existing integration tests. This is very similar to what has been made for Google Cloud Storage in #28788 and for Amazon S3 in #29296, and it would have helped a lot to catch bugs like #22534.
1 parent 85f5382 commit 26fc8ad

File tree

8 files changed

+806
-136
lines changed

8 files changed

+806
-136
lines changed

plugins/repository-azure/build.gradle

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19+
import org.elasticsearch.gradle.test.AntFixture
1920

2021
esplugin {
2122
description 'The Azure Repository plugin adds support for Azure storage repositories.'
@@ -42,9 +43,28 @@ thirdPartyAudit.excludes = [
4243
'org.slf4j.LoggerFactory',
4344
]
4445

45-
integTestCluster {
46-
keystoreSetting 'azure.client.default.account', 'cloudazureresource'
47-
keystoreSetting 'azure.client.default.key', 'abcdefgh'
48-
keystoreSetting 'azure.client.secondary.account', 'cloudazureresource'
49-
keystoreSetting 'azure.client.secondary.key', 'abcdefgh'
46+
forbiddenApisTest {
47+
// we are using jdk-internal instead of jdk-non-portable to allow for com.sun.net.httpserver.* usage
48+
bundledSignatures -= 'jdk-non-portable'
49+
bundledSignatures += 'jdk-internal'
50+
}
51+
52+
/** A task to start the fixture which emulates an Azure Storage service **/
53+
task azureStorageFixture(type: AntFixture) {
54+
dependsOn compileTestJava
55+
env 'CLASSPATH', "${ -> project.sourceSets.test.runtimeClasspath.asPath }"
56+
executable = new File(project.runtimeJavaHome, 'bin/java')
57+
args 'org.elasticsearch.repositories.azure.AzureStorageFixture', baseDir, 'container_test'
5058
}
59+
60+
integTestCluster {
61+
dependsOn azureStorageFixture
62+
63+
keystoreSetting 'azure.client.integration_test.account', "azure_integration_test_account"
64+
/* The key is "azure_integration_test_key" encoded using base64 */
65+
keystoreSetting 'azure.client.integration_test.key', "YXp1cmVfaW50ZWdyYXRpb25fdGVzdF9rZXk="
66+
// Use a closure on the string to delay evaluation until tests are executed. The endpoint_suffix is used
67+
// in a hacky way to change the protocol and endpoint. We must fix that.
68+
setting 'azure.client.integration_test.endpoint_suffix',
69+
"ignored;DefaultEndpointsProtocol=http;BlobEndpoint=http://${ -> azureStorageFixture.addressAndPort }"
70+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.microsoft.azure.storage.StorageException;
22+
import org.elasticsearch.cluster.metadata.RepositoryMetaData;
23+
import org.elasticsearch.common.blobstore.BlobStore;
24+
import org.elasticsearch.common.settings.Settings;
25+
import org.elasticsearch.repositories.ESBlobStoreTestCase;
26+
27+
import java.io.IOException;
28+
import java.net.URISyntaxException;
29+
30+
public class AzureBlobStoreTests extends ESBlobStoreTestCase {
31+
32+
@Override
33+
protected BlobStore newBlobStore() throws IOException {
34+
try {
35+
RepositoryMetaData repositoryMetaData = new RepositoryMetaData("azure", "ittest", Settings.EMPTY);
36+
AzureStorageServiceMock client = new AzureStorageServiceMock();
37+
return new AzureBlobStore(repositoryMetaData, Settings.EMPTY, client);
38+
} catch (URISyntaxException | StorageException e) {
39+
throw new IOException(e);
40+
}
41+
}
42+
}

plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureRepositoryF.java

Lines changed: 0 additions & 128 deletions
This file was deleted.

plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureRepositorySettingsTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ private AzureRepository azureRepository(Settings settings) throws StorageExcepti
4747
TestEnvironment.newEnvironment(internalSettings), NamedXContentRegistry.EMPTY, null);
4848
}
4949

50-
5150
public void testReadonlyDefault() throws StorageException, IOException, URISyntaxException {
5251
assertThat(azureRepository(Settings.EMPTY).isReadOnly(), is(false));
5352
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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.io.Streams;
26+
import org.elasticsearch.mocksocket.MockHttpServer;
27+
28+
import java.io.ByteArrayOutputStream;
29+
import java.io.IOException;
30+
import java.lang.management.ManagementFactory;
31+
import java.net.Inet6Address;
32+
import java.net.InetAddress;
33+
import java.net.InetSocketAddress;
34+
import java.net.SocketAddress;
35+
import java.nio.file.Files;
36+
import java.nio.file.Path;
37+
import java.nio.file.Paths;
38+
import java.nio.file.StandardCopyOption;
39+
import java.util.List;
40+
import java.util.Map;
41+
42+
import static java.util.Collections.singleton;
43+
import static java.util.Collections.singletonList;
44+
45+
/**
46+
* {@link AzureStorageFixture} is a fixture that emulates an Azure Storage service.
47+
* <p>
48+
* It starts an asynchronous socket server that binds to a random local port. The server parses
49+
* HTTP requests and uses a {@link AzureStorageTestServer} to handle them before returning
50+
* them to the client as HTTP responses.
51+
*/
52+
public class AzureStorageFixture {
53+
54+
public static void main(String[] args) throws Exception {
55+
if (args == null || args.length != 2) {
56+
throw new IllegalArgumentException("AzureStorageFixture <working directory> <container>");
57+
}
58+
59+
final InetSocketAddress socketAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
60+
final HttpServer httpServer = MockHttpServer.createHttp(socketAddress, 0);
61+
62+
try {
63+
final Path workingDirectory = workingDir(args[0]);
64+
/// Writes the PID of the current Java process in a `pid` file located in the working directory
65+
writeFile(workingDirectory, "pid", ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
66+
67+
final String addressAndPort = addressToString(httpServer.getAddress());
68+
// Writes the address and port of the http server in a `ports` file located in the working directory
69+
writeFile(workingDirectory, "ports", addressAndPort);
70+
71+
// Emulates Azure
72+
final String storageUrl = "http://" + addressAndPort;
73+
final AzureStorageTestServer testServer = new AzureStorageTestServer(storageUrl);
74+
testServer.createContainer(args[1]);
75+
76+
httpServer.createContext("/", new ResponseHandler(testServer));
77+
httpServer.start();
78+
79+
// Wait to be killed
80+
Thread.sleep(Long.MAX_VALUE);
81+
82+
} finally {
83+
httpServer.stop(0);
84+
}
85+
}
86+
87+
@SuppressForbidden(reason = "Paths#get is fine - we don't have environment here")
88+
private static Path workingDir(final String dir) {
89+
return Paths.get(dir);
90+
}
91+
92+
private static void writeFile(final Path dir, final String fileName, final String content) throws IOException {
93+
final Path tempPidFile = Files.createTempFile(dir, null, null);
94+
Files.write(tempPidFile, singleton(content));
95+
Files.move(tempPidFile, dir.resolve(fileName), StandardCopyOption.ATOMIC_MOVE);
96+
}
97+
98+
private static String addressToString(final SocketAddress address) {
99+
final InetSocketAddress inetSocketAddress = (InetSocketAddress) address;
100+
if (inetSocketAddress.getAddress() instanceof Inet6Address) {
101+
return "[" + inetSocketAddress.getHostString() + "]:" + inetSocketAddress.getPort();
102+
} else {
103+
return inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort();
104+
}
105+
}
106+
107+
static class ResponseHandler implements HttpHandler {
108+
109+
private final AzureStorageTestServer server;
110+
111+
private ResponseHandler(final AzureStorageTestServer server) {
112+
this.server = server;
113+
}
114+
115+
@Override
116+
public void handle(HttpExchange exchange) throws IOException {
117+
String method = exchange.getRequestMethod();
118+
String path = server.getEndpoint() + exchange.getRequestURI().getRawPath();
119+
String query = exchange.getRequestURI().getRawQuery();
120+
Map<String, List<String>> headers = exchange.getRequestHeaders();
121+
ByteArrayOutputStream out = new ByteArrayOutputStream();
122+
Streams.copy(exchange.getRequestBody(), out);
123+
124+
final AzureStorageTestServer.Response response = server.handle(method, path, query, headers, out.toByteArray());
125+
126+
Map<String, List<String>> responseHeaders = exchange.getResponseHeaders();
127+
responseHeaders.put("Content-Type", singletonList(response.contentType));
128+
response.headers.forEach((k, v) -> responseHeaders.put(k, singletonList(v)));
129+
exchange.sendResponseHeaders(response.status.getStatus(), response.body.length);
130+
if (response.body.length > 0) {
131+
exchange.getResponseBody().write(response.body);
132+
}
133+
exchange.close();
134+
}
135+
}
136+
}

plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public void createContainer(String account, LocationMode mode, String container)
6666

6767
@Override
6868
public void deleteFiles(String account, LocationMode mode, String container, String path) {
69+
final Map<String, BlobMetaData> blobs = listBlobsByPrefix(account, mode, container, path, null);
70+
blobs.keySet().forEach(key -> deleteBlob(account, mode, container, key));
6971
}
7072

7173
@Override

0 commit comments

Comments
 (0)