Skip to content

Commit 719e75b

Browse files
authoredJan 25, 2017
Add repository-url module and move URLRepository (elastic#22752)
This is related to elastic#22116. URLRepository requires SocketPermission connect. This commit introduces a new module called "repository-url" where URLRepository will reside. With the new module, permissions can be removed from core.
1 parent e9a68b3 commit 719e75b

File tree

21 files changed

+525
-132
lines changed

21 files changed

+525
-132
lines changed
 

‎buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy

+1-2
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,7 @@ class ClusterFormationTasks {
276276
'path.repo' : "${node.sharedDir}/repo",
277277
'path.shared_data' : "${node.sharedDir}/",
278278
// Define a node attribute so we can test that it exists
279-
'node.attr.testattr' : 'test',
280-
'repositories.url.allowed_urls': 'http://snapshot.test*'
279+
'node.attr.testattr' : 'test'
281280
]
282281
// we set min master nodes to the total number of nodes in the cluster and
283282
// basically skip initial state recovery to allow the cluster to form using a realistic master election

‎buildSrc/src/main/resources/checkstyle_suppressions.xml

-2
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,6 @@
256256
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]Base64.java" checks="LineLength" />
257257
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]Numbers.java" checks="LineLength" />
258258
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]blobstore[/\\]fs[/\\]FsBlobStore.java" checks="LineLength" />
259-
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]blobstore[/\\]url[/\\]URLBlobStore.java" checks="LineLength" />
260259
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]bytes[/\\]BytesArray.java" checks="LineLength" />
261260
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]bytes[/\\]PagedBytesReference.java" checks="LineLength" />
262261
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]cache[/\\]Cache.java" checks="LineLength" />
@@ -437,7 +436,6 @@
437436
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]repositories[/\\]blobstore[/\\]ChecksumBlobStoreFormat.java" checks="LineLength" />
438437
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]repositories[/\\]fs[/\\]FsRepository.java" checks="LineLength" />
439438
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]repositories[/\\]uri[/\\]URLIndexShardRepository.java" checks="LineLength" />
440-
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]repositories[/\\]uri[/\\]URLRepository.java" checks="LineLength" />
441439
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]rest[/\\]RestController.java" checks="LineLength" />
442440
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]rest[/\\]action[/\\]cat[/\\]RestCountAction.java" checks="LineLength" />
443441
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]rest[/\\]action[/\\]cat[/\\]RestIndicesAction.java" checks="LineLength" />

‎core/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java

-4
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@
8181
import org.elasticsearch.node.Node;
8282
import org.elasticsearch.plugins.PluginsService;
8383
import org.elasticsearch.repositories.fs.FsRepository;
84-
import org.elasticsearch.repositories.uri.URLRepository;
8584
import org.elasticsearch.rest.BaseRestHandler;
8685
import org.elasticsearch.script.ScriptService;
8786
import org.elasticsearch.search.SearchModule;
@@ -348,9 +347,6 @@ public void apply(Settings value, Settings current, Settings previous) {
348347
Node.NODE_INGEST_SETTING,
349348
Node.NODE_ATTRIBUTES,
350349
Node.NODE_LOCAL_STORAGE_SETTING,
351-
URLRepository.ALLOWED_URLS_SETTING,
352-
URLRepository.REPOSITORIES_URL_SETTING,
353-
URLRepository.SUPPORTED_PROTOCOLS_SETTING,
354350
TransportMasterNodeReadAction.FORCE_LOCAL_SETTING,
355351
AutoCreateIndex.AUTO_CREATE_INDEX_SETTING,
356352
BaseRestHandler.MULTI_ALLOW_EXPLICIT_INDEX,

‎core/src/main/java/org/elasticsearch/repositories/RepositoriesModule.java

+5-8
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,22 @@
1919

2020
package org.elasticsearch.repositories;
2121

22-
import java.util.Collections;
23-
import java.util.HashMap;
24-
import java.util.List;
25-
import java.util.Map;
26-
2722
import org.elasticsearch.action.admin.cluster.snapshots.status.TransportNodesSnapshotsStatus;
2823
import org.elasticsearch.common.inject.AbstractModule;
29-
import org.elasticsearch.common.inject.binder.LinkedBindingBuilder;
3024
import org.elasticsearch.common.inject.multibindings.MapBinder;
3125
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
3226
import org.elasticsearch.env.Environment;
3327
import org.elasticsearch.plugins.RepositoryPlugin;
3428
import org.elasticsearch.repositories.fs.FsRepository;
35-
import org.elasticsearch.repositories.uri.URLRepository;
3629
import org.elasticsearch.snapshots.RestoreService;
3730
import org.elasticsearch.snapshots.SnapshotShardsService;
3831
import org.elasticsearch.snapshots.SnapshotsService;
3932

33+
import java.util.Collections;
34+
import java.util.HashMap;
35+
import java.util.List;
36+
import java.util.Map;
37+
4038
/**
4139
* Sets up classes for Snapshot/Restore.
4240
*/
@@ -47,7 +45,6 @@ public class RepositoriesModule extends AbstractModule {
4745
public RepositoriesModule(Environment env, List<RepositoryPlugin> repoPlugins, NamedXContentRegistry namedXContentRegistry) {
4846
Map<String, Repository.Factory> factories = new HashMap<>();
4947
factories.put(FsRepository.TYPE, (metadata) -> new FsRepository(metadata, env, namedXContentRegistry));
50-
factories.put(URLRepository.TYPE, (metadata) -> new URLRepository(metadata, env, namedXContentRegistry));
5148

5249
for (RepositoryPlugin repoPlugin : repoPlugins) {
5350
Map<String, Repository.Factory> newRepoTypes = repoPlugin.getRepositories(env, namedXContentRegistry);

‎core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java

+21-27
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,22 @@
2626
import org.elasticsearch.cluster.metadata.IndexMetaData;
2727
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
2828
import org.elasticsearch.cluster.routing.allocation.decider.FilterAllocationDecider;
29+
import org.elasticsearch.common.io.FileTestUtils;
2930
import org.elasticsearch.common.settings.Settings;
3031
import org.elasticsearch.env.Environment;
31-
import org.elasticsearch.repositories.uri.URLRepository;
32+
import org.elasticsearch.repositories.fs.FsRepository;
3233
import org.elasticsearch.rest.RestStatus;
3334
import org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase;
3435
import org.elasticsearch.snapshots.RestoreInfo;
3536
import org.elasticsearch.snapshots.SnapshotInfo;
3637
import org.elasticsearch.snapshots.SnapshotRestoreException;
38+
import org.elasticsearch.snapshots.mockstore.MockRepository;
3739
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
3840
import org.elasticsearch.test.ESIntegTestCase.Scope;
3941
import org.elasticsearch.test.VersionUtils;
42+
import org.junit.BeforeClass;
4043

4144
import java.io.IOException;
42-
import java.net.URI;
43-
import java.net.URISyntaxException;
4445
import java.nio.file.DirectoryStream;
4546
import java.nio.file.Files;
4647
import java.nio.file.Path;
@@ -61,27 +62,19 @@
6162
@ClusterScope(scope = Scope.TEST)
6263
public class RestoreBackwardsCompatIT extends AbstractSnapshotIntegTestCase {
6364

65+
private static Path repoPath;
66+
6467
@Override
6568
protected Settings nodeSettings(int nodeOrdinal) {
66-
if (randomBoolean()) {
67-
// Configure using path.repo
68-
return Settings.builder()
69-
.put(super.nodeSettings(nodeOrdinal))
70-
.put(Environment.PATH_REPO_SETTING.getKey(), getBwcIndicesPath())
71-
.build();
72-
} else {
73-
// Configure using url white list
74-
try {
75-
URI repoJarPatternUri = new URI("jar:" + getBwcIndicesPath().toUri().toString() + "*.zip!/repo/");
76-
return Settings.builder()
77-
.put(super.nodeSettings(nodeOrdinal))
78-
.putArray(URLRepository.ALLOWED_URLS_SETTING.getKey(), repoJarPatternUri.toString())
79-
.build();
80-
} catch (URISyntaxException ex) {
81-
throw new IllegalArgumentException(ex);
82-
}
69+
return Settings.builder()
70+
.put(super.nodeSettings(nodeOrdinal))
71+
.put(Environment.PATH_REPO_SETTING.getKey(), repoPath)
72+
.build();
73+
}
8374

84-
}
75+
@BeforeClass
76+
public static void repoSetup() throws IOException {
77+
repoPath = createTempDir("repositories");
8578
}
8679

8780
public void testRestoreOldSnapshots() throws Exception {
@@ -151,13 +144,14 @@ private List<String> listRepoVersions(String prefix) throws Exception {
151144
}
152145

153146
private void createRepo(String prefix, String version, String repo) throws Exception {
154-
Path repoFile = getBwcIndicesPath().resolve(prefix + "-" + version + ".zip");
155-
URI repoFileUri = repoFile.toUri();
156-
URI repoJarUri = new URI("jar:" + repoFileUri.toString() + "!/repo/");
147+
Path repoFileFromBuild = getBwcIndicesPath().resolve(prefix + "-" + version + ".zip");
148+
String repoFileName = repoFileFromBuild.getFileName().toString().split(".zip")[0];
149+
Path fsRepoPath = repoPath.resolve(repoFileName);
150+
FileTestUtils.unzip(repoFileFromBuild, fsRepoPath, null);
157151
logger.info("--> creating repository [{}] for version [{}]", repo, version);
158152
assertAcked(client().admin().cluster().preparePutRepository(repo)
159-
.setType("url").setSettings(Settings.builder()
160-
.put("url", repoJarUri.toString())));
153+
.setType(MockRepository.TYPE).setSettings(Settings.builder()
154+
.put(FsRepository.REPOSITORIES_LOCATION_SETTING.getKey(), fsRepoPath.getParent().relativize(fsRepoPath).resolve("repo").toString())));
161155
}
162156

163157
private void testOldSnapshot(String version, String repo, String snapshot) throws IOException {
@@ -198,7 +192,7 @@ private void testOldSnapshot(String version, String repo, String snapshot) throw
198192
equalTo("{\"type1\":{\"_source\":{\"enabled\":0}}}"),
199193
equalTo("{\"type1\":{\"_source\":{\"enabled\":\"off\"}}}"),
200194
equalTo("{\"type1\":{\"_source\":{\"enabled\":\"no\"}}}")
201-
));
195+
));
202196
assertThat(template.aliases().size(), equalTo(3));
203197
assertThat(template.aliases().get("alias1"), notNullValue());
204198
assertThat(template.aliases().get("alias2").filter().string(), containsString(version));

‎core/src/test/java/org/elasticsearch/snapshots/RepositoriesIT.java

-24
Original file line numberDiff line numberDiff line change
@@ -142,30 +142,6 @@ public void testMisconfiguredRepository() throws Exception {
142142
} catch (RepositoryException ex) {
143143
assertThat(ex.toString(), containsString("location [" + location + "] doesn't match any of the locations specified by path.repo"));
144144
}
145-
146-
String repoUrl = invalidRepoPath.toAbsolutePath().toUri().toURL().toString();
147-
String unsupportedUrl = repoUrl.replace("file:/", "netdoc:/");
148-
logger.info("--> trying creating url repository with unsupported url protocol");
149-
try {
150-
client().admin().cluster().preparePutRepository("test-repo")
151-
.setType("url").setSettings(Settings.builder().put("url", unsupportedUrl))
152-
.get();
153-
fail("Shouldn't be here");
154-
} catch (RepositoryException ex) {
155-
assertThat(ex.toString(),
156-
either(containsString("unsupported url protocol [netdoc]"))
157-
.or(containsString("unknown protocol: netdoc"))); // newer versions of JDK 9
158-
}
159-
160-
logger.info("--> trying creating url repository with location that is not registered in path.repo setting");
161-
try {
162-
client().admin().cluster().preparePutRepository("test-repo")
163-
.setType("url").setSettings(Settings.builder().put("url", invalidRepoPath.toUri().toURL()))
164-
.get();
165-
fail("Shouldn't be here");
166-
} catch (RepositoryException ex) {
167-
assertThat(ex.toString(), containsString("doesn't match any of the locations specified by path.repo"));
168-
}
169145
}
170146

171147
public void testRepositoryAckTimeout() throws Exception {

‎core/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java

-57
Original file line numberDiff line numberDiff line change
@@ -1442,63 +1442,6 @@ public void testDeleteRepositoryWhileSnapshotting() throws Exception {
14421442
assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L));
14431443
}
14441444

1445-
public void testUrlRepository() throws Exception {
1446-
Client client = client();
1447-
1448-
logger.info("--> creating repository");
1449-
Path repositoryLocation = randomRepoPath();
1450-
assertAcked(client.admin().cluster().preparePutRepository("test-repo")
1451-
.setType("fs").setSettings(Settings.builder()
1452-
.put("location", repositoryLocation)
1453-
.put("compress", randomBoolean())
1454-
.put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES)));
1455-
1456-
createIndex("test-idx");
1457-
ensureGreen();
1458-
1459-
logger.info("--> indexing some data");
1460-
for (int i = 0; i < 100; i++) {
1461-
index("test-idx", "doc", Integer.toString(i), "foo", "bar" + i);
1462-
}
1463-
refresh();
1464-
assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L));
1465-
1466-
logger.info("--> snapshot");
1467-
CreateSnapshotResponse createSnapshotResponse = client.admin().cluster().prepareCreateSnapshot("test-repo", "test-snap").setWaitForCompletion(true).setIndices("test-idx").get();
1468-
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0));
1469-
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards()));
1470-
1471-
assertThat(client.admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("test-snap").get().getSnapshots().get(0).state(), equalTo(SnapshotState.SUCCESS));
1472-
1473-
logger.info("--> delete index");
1474-
cluster().wipeIndices("test-idx");
1475-
1476-
logger.info("--> create read-only URL repository");
1477-
assertAcked(client.admin().cluster().preparePutRepository("url-repo")
1478-
.setType("url").setSettings(Settings.builder()
1479-
.put("url", repositoryLocation.toUri().toURL())
1480-
.put("list_directories", randomBoolean())));
1481-
logger.info("--> restore index after deletion");
1482-
RestoreSnapshotResponse restoreSnapshotResponse = client.admin().cluster().prepareRestoreSnapshot("url-repo", "test-snap").setWaitForCompletion(true).setIndices("test-idx").execute().actionGet();
1483-
assertThat(restoreSnapshotResponse.getRestoreInfo().totalShards(), greaterThan(0));
1484-
1485-
assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L));
1486-
1487-
logger.info("--> list available shapshots");
1488-
GetSnapshotsResponse getSnapshotsResponse = client.admin().cluster().prepareGetSnapshots("url-repo").get();
1489-
assertThat(getSnapshotsResponse.getSnapshots(), notNullValue());
1490-
assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(1));
1491-
1492-
logger.info("--> delete snapshot");
1493-
DeleteSnapshotResponse deleteSnapshotResponse = client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").get();
1494-
assertAcked(deleteSnapshotResponse);
1495-
1496-
logger.info("--> list available shapshot again, no snapshots should be returned");
1497-
getSnapshotsResponse = client.admin().cluster().prepareGetSnapshots("url-repo").get();
1498-
assertThat(getSnapshotsResponse.getSnapshots(), notNullValue());
1499-
assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(0));
1500-
}
1501-
15021445
@TestLogging("_root:DEBUG") // this fails every now and then: https://github.com/elastic/elasticsearch/issues/18121 but without
15031446
// more logs we cannot find out why
15041447
public void testReadonlyRepository() throws Exception {

‎modules/repository-url/build.gradle

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
20+
esplugin {
21+
description 'Module for URL repository'
22+
classname 'org.elasticsearch.plugin.repository.url.URLRepositoryPlugin'
23+
}
24+
25+
integTest {
26+
cluster {
27+
setting 'repositories.url.allowed_urls', 'http://snapshot.test*'
28+
}
29+
}

‎core/src/main/java/org/elasticsearch/common/blobstore/url/URLBlobStore.java ‎modules/repository-url/src/main/java/org/elasticsearch/common/blobstore/url/URLBlobStore.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public class URLBlobStore extends AbstractComponent implements BlobStore {
5555
public URLBlobStore(Settings settings, URL path) {
5656
super(settings);
5757
this.path = path;
58-
this.bufferSizeInBytes = (int) settings.getAsBytesSize("repositories.uri.buffer_size", new ByteSizeValue(100, ByteSizeUnit.KB)).getBytes();
58+
this.bufferSizeInBytes = (int) settings.getAsBytesSize("repositories.uri.buffer_size",
59+
new ByteSizeValue(100, ByteSizeUnit.KB)).getBytes();
5960
}
6061

6162
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
20+
package org.elasticsearch.plugin.repository.url;
21+
22+
import org.elasticsearch.common.settings.Setting;
23+
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
24+
import org.elasticsearch.env.Environment;
25+
import org.elasticsearch.plugins.Plugin;
26+
import org.elasticsearch.plugins.RepositoryPlugin;
27+
import org.elasticsearch.repositories.Repository;
28+
import org.elasticsearch.repositories.url.URLRepository;
29+
30+
import java.util.Arrays;
31+
import java.util.Collections;
32+
import java.util.List;
33+
import java.util.Map;
34+
35+
public class URLRepositoryPlugin extends Plugin implements RepositoryPlugin {
36+
37+
@Override
38+
public List<Setting<?>> getSettings() {
39+
return Arrays.asList(
40+
URLRepository.ALLOWED_URLS_SETTING,
41+
URLRepository.REPOSITORIES_URL_SETTING,
42+
URLRepository.SUPPORTED_PROTOCOLS_SETTING
43+
);
44+
}
45+
46+
@Override
47+
public Map<String, Repository.Factory> getRepositories(Environment env, NamedXContentRegistry namedXContentRegistry) {
48+
return Collections.singletonMap(URLRepository.TYPE, metadata -> new URLRepository(metadata, env, namedXContentRegistry));
49+
}
50+
}

‎core/src/main/java/org/elasticsearch/repositories/uri/URLRepository.java ‎modules/repository-url/src/main/java/org/elasticsearch/repositories/url/URLRepository.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* under the License.
1818
*/
1919

20-
package org.elasticsearch.repositories.uri;
20+
package org.elasticsearch.repositories.url;
2121

2222
import org.elasticsearch.cluster.metadata.RepositoryMetaData;
2323
import org.elasticsearch.common.blobstore.BlobPath;
@@ -127,8 +127,12 @@ private URL checkURL(URL url) {
127127
// We didn't match white list - try to resolve against path.repo
128128
URL normalizedUrl = environment.resolveRepoURL(url);
129129
if (normalizedUrl == null) {
130-
logger.warn("The specified url [{}] doesn't start with any repository paths specified by the path.repo setting or by {} setting: [{}] ", url, ALLOWED_URLS_SETTING.getKey(), environment.repoFiles());
131-
throw new RepositoryException(getMetadata().name(), "file url [" + url + "] doesn't match any of the locations specified by path.repo or " + ALLOWED_URLS_SETTING.getKey());
130+
String logMessage = "The specified url [{}] doesn't start with any repository paths specified by the " +
131+
"path.repo setting or by {} setting: [{}] ";
132+
logger.warn(logMessage, url, ALLOWED_URLS_SETTING.getKey(), environment.repoFiles());
133+
String exceptionMessage = "file url [" + url + "] doesn't match any of the locations specified by path.repo or "
134+
+ ALLOWED_URLS_SETTING.getKey();
135+
throw new RepositoryException(getMetadata().name(), exceptionMessage);
132136
}
133137
return normalizedUrl;
134138
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
20+
package org.elasticsearch.common.blobstore.url;
21+
22+
import com.sun.net.httpserver.HttpServer;
23+
import org.elasticsearch.common.SuppressForbidden;
24+
import org.elasticsearch.common.blobstore.BlobContainer;
25+
import org.elasticsearch.common.blobstore.BlobPath;
26+
import org.elasticsearch.common.settings.Settings;
27+
import org.elasticsearch.mocksocket.MockHttpServer;
28+
import org.elasticsearch.test.ESTestCase;
29+
import org.junit.AfterClass;
30+
import org.junit.Before;
31+
import org.junit.BeforeClass;
32+
33+
import java.io.IOException;
34+
import java.io.InputStream;
35+
import java.io.OutputStream;
36+
import java.net.InetAddress;
37+
import java.net.InetSocketAddress;
38+
import java.net.MalformedURLException;
39+
import java.net.URL;
40+
import java.nio.file.NoSuchFileException;
41+
42+
@SuppressForbidden(reason = "use http server")
43+
public class URLBlobStoreTests extends ESTestCase {
44+
45+
private static HttpServer httpServer;
46+
private static String blobName;
47+
private static byte[] message = new byte[512];
48+
private URLBlobStore urlBlobStore;
49+
50+
@BeforeClass
51+
public static void startHttp() throws Exception {
52+
for (int i = 0; i < message.length; ++i) {
53+
message[i] = randomByte();
54+
}
55+
blobName = randomAsciiOfLength(8);
56+
57+
httpServer = MockHttpServer.createHttp(new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 6001), 0);
58+
59+
httpServer.createContext("/indices/" + blobName, (s) -> {
60+
s.sendResponseHeaders(200, message.length);
61+
OutputStream responseBody = s.getResponseBody();
62+
responseBody.write(message);
63+
responseBody.close();
64+
});
65+
66+
httpServer.start();
67+
}
68+
69+
@AfterClass
70+
public static void stopHttp() throws IOException {
71+
httpServer.stop(0);
72+
httpServer = null;
73+
}
74+
75+
@Before
76+
public void storeSetup() throws MalformedURLException {
77+
Settings settings = Settings.EMPTY;
78+
String spec = "http://localhost:6001/";
79+
urlBlobStore = new URLBlobStore(settings, new URL(spec));
80+
}
81+
82+
public void testURLBlobStoreCanReadBlob() throws IOException {
83+
BlobContainer container = urlBlobStore.blobContainer(BlobPath.cleanPath().add("indices"));
84+
try (InputStream stream = container.readBlob(blobName)) {
85+
byte[] bytes = new byte[message.length];
86+
int read = stream.read(bytes);
87+
assertEquals(message.length, read);
88+
assertArrayEquals(message, bytes);
89+
}
90+
}
91+
92+
public void testNoBlobFound() throws IOException {
93+
BlobContainer container = urlBlobStore.blobContainer(BlobPath.cleanPath().add("indices"));
94+
String incorrectBlobName = "incorrect_" + blobName;
95+
try (InputStream ignored = container.readBlob(incorrectBlobName)) {
96+
fail("Should have thrown NoSuchFileException exception");
97+
ignored.read();
98+
} catch (NoSuchFileException e) {
99+
assertEquals(String.format("[%s] blob not found", incorrectBlobName), e.getMessage());
100+
}
101+
}
102+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
20+
package org.elasticsearch.repositories.url;
21+
22+
import com.carrotsearch.randomizedtesting.annotations.Name;
23+
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
24+
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
25+
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
26+
27+
import java.io.IOException;
28+
29+
public class RepositoryURLClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
30+
31+
public RepositoryURLClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
32+
super(testCandidate);
33+
}
34+
35+
@ParametersFactory
36+
public static Iterable<Object[]> parameters() throws IOException {
37+
return ESClientYamlSuiteTestCase.createParameters();
38+
}
39+
}
40+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
20+
package org.elasticsearch.repositories.url;
21+
22+
import org.elasticsearch.cluster.metadata.RepositoryMetaData;
23+
import org.elasticsearch.common.settings.Settings;
24+
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
25+
import org.elasticsearch.env.Environment;
26+
import org.elasticsearch.repositories.RepositoryException;
27+
import org.elasticsearch.test.ESTestCase;
28+
29+
import java.io.IOException;
30+
import java.nio.file.Path;
31+
import java.util.Collections;
32+
33+
public class URLRepositoryTests extends ESTestCase {
34+
35+
public void testWhiteListingRepoURL() throws IOException {
36+
String repoPath = createTempDir().resolve("repository").toUri().toURL().toString();
37+
Settings baseSettings = Settings.builder()
38+
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
39+
.put(URLRepository.ALLOWED_URLS_SETTING.getKey(), repoPath)
40+
.put(URLRepository.REPOSITORIES_URL_SETTING.getKey(), repoPath)
41+
.build();
42+
RepositoryMetaData repositoryMetaData = new RepositoryMetaData("url", URLRepository.TYPE, baseSettings);
43+
new URLRepository(repositoryMetaData, new Environment(baseSettings), new NamedXContentRegistry(Collections.emptyList()));
44+
}
45+
46+
public void testIfNotWhiteListedMustSetRepoURL() throws IOException {
47+
String repoPath = createTempDir().resolve("repository").toUri().toURL().toString();
48+
Settings baseSettings = Settings.builder()
49+
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
50+
.put(URLRepository.REPOSITORIES_URL_SETTING.getKey(), repoPath)
51+
.build();
52+
RepositoryMetaData repositoryMetaData = new RepositoryMetaData("url", URLRepository.TYPE, baseSettings);
53+
try {
54+
new URLRepository(repositoryMetaData, new Environment(baseSettings), new NamedXContentRegistry(Collections.emptyList()));
55+
fail("RepositoryException should have been thrown.");
56+
} catch (RepositoryException e) {
57+
String msg = "[url] file url [" + repoPath
58+
+ "] doesn't match any of the locations specified by path.repo or repositories.url.allowed_urls";
59+
assertEquals(msg, e.getMessage());
60+
}
61+
}
62+
63+
public void testMustBeSupportedProtocol() throws IOException {
64+
Path directory = createTempDir();
65+
String repoPath = directory.resolve("repository").toUri().toURL().toString();
66+
Settings baseSettings = Settings.builder()
67+
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
68+
.put(Environment.PATH_REPO_SETTING.getKey(), directory.toString())
69+
.put(URLRepository.REPOSITORIES_URL_SETTING.getKey(), repoPath)
70+
.put(URLRepository.SUPPORTED_PROTOCOLS_SETTING.getKey(), "http,https")
71+
.build();
72+
RepositoryMetaData repositoryMetaData = new RepositoryMetaData("url", URLRepository.TYPE, baseSettings);
73+
try {
74+
new URLRepository(repositoryMetaData, new Environment(baseSettings), new NamedXContentRegistry(Collections.emptyList()));
75+
fail("RepositoryException should have been thrown.");
76+
} catch (RepositoryException e) {
77+
assertEquals("[url] unsupported url protocol [file] from URL [" + repoPath +"]", e.getMessage());
78+
}
79+
}
80+
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
20+
package org.elasticsearch.repositories.url;
21+
22+
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
23+
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotResponse;
24+
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
25+
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
26+
import org.elasticsearch.client.Client;
27+
import org.elasticsearch.common.settings.Settings;
28+
import org.elasticsearch.common.unit.ByteSizeUnit;
29+
import org.elasticsearch.plugin.repository.url.URLRepositoryPlugin;
30+
import org.elasticsearch.plugins.Plugin;
31+
import org.elasticsearch.repositories.fs.FsRepository;
32+
import org.elasticsearch.snapshots.SnapshotState;
33+
import org.elasticsearch.test.ESIntegTestCase;
34+
35+
import java.nio.file.Path;
36+
import java.util.Collection;
37+
import java.util.Collections;
38+
39+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
40+
import static org.hamcrest.Matchers.equalTo;
41+
import static org.hamcrest.Matchers.greaterThan;
42+
import static org.hamcrest.Matchers.notNullValue;
43+
44+
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST)
45+
public class URLSnapshotRestoreTests extends ESIntegTestCase {
46+
47+
@Override
48+
protected Collection<Class<? extends Plugin>> nodePlugins() {
49+
return Collections.singletonList(URLRepositoryPlugin.class);
50+
}
51+
52+
public void testUrlRepository() throws Exception {
53+
Client client = client();
54+
55+
logger.info("--> creating repository");
56+
Path repositoryLocation = randomRepoPath();
57+
assertAcked(client.admin().cluster().preparePutRepository("test-repo")
58+
.setType(FsRepository.TYPE).setSettings(Settings.builder()
59+
.put(FsRepository.LOCATION_SETTING.getKey(), repositoryLocation)
60+
.put(FsRepository.COMPRESS_SETTING.getKey(), randomBoolean())
61+
.put(FsRepository.CHUNK_SIZE_SETTING.getKey(), randomIntBetween(100, 1000), ByteSizeUnit.BYTES)));
62+
63+
createIndex("test-idx");
64+
ensureGreen();
65+
66+
logger.info("--> indexing some data");
67+
for (int i = 0; i < 100; i++) {
68+
index("test-idx", "doc", Integer.toString(i), "foo", "bar" + i);
69+
}
70+
refresh();
71+
assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L));
72+
73+
logger.info("--> snapshot");
74+
CreateSnapshotResponse createSnapshotResponse = client
75+
.admin()
76+
.cluster()
77+
.prepareCreateSnapshot("test-repo", "test-snap")
78+
.setWaitForCompletion(true)
79+
.setIndices("test-idx")
80+
.get();
81+
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0));
82+
int actualTotalShards = createSnapshotResponse.getSnapshotInfo().totalShards();
83+
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(actualTotalShards));
84+
85+
SnapshotState state = client
86+
.admin()
87+
.cluster()
88+
.prepareGetSnapshots("test-repo")
89+
.setSnapshots("test-snap")
90+
.get()
91+
.getSnapshots()
92+
.get(0)
93+
.state();
94+
assertThat(state, equalTo(SnapshotState.SUCCESS));
95+
96+
logger.info("--> delete index");
97+
cluster().wipeIndices("test-idx");
98+
99+
logger.info("--> create read-only URL repository");
100+
assertAcked(client.admin().cluster().preparePutRepository("url-repo")
101+
.setType(URLRepository.TYPE).setSettings(Settings.builder()
102+
.put(URLRepository.URL_SETTING.getKey(), repositoryLocation.toUri().toURL())
103+
.put("list_directories", randomBoolean())));
104+
logger.info("--> restore index after deletion");
105+
RestoreSnapshotResponse restoreSnapshotResponse = client
106+
.admin()
107+
.cluster()
108+
.prepareRestoreSnapshot("url-repo", "test-snap")
109+
.setWaitForCompletion(true)
110+
.setIndices("test-idx")
111+
.execute()
112+
.actionGet();
113+
assertThat(restoreSnapshotResponse.getRestoreInfo().totalShards(), greaterThan(0));
114+
115+
assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L));
116+
117+
logger.info("--> list available shapshots");
118+
GetSnapshotsResponse getSnapshotsResponse = client.admin().cluster().prepareGetSnapshots("url-repo").get();
119+
assertThat(getSnapshotsResponse.getSnapshots(), notNullValue());
120+
assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(1));
121+
122+
logger.info("--> delete snapshot");
123+
DeleteSnapshotResponse deleteSnapshotResponse = client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").get();
124+
assertAcked(deleteSnapshotResponse);
125+
126+
logger.info("--> list available shapshot again, no snapshots should be returned");
127+
getSnapshotsResponse = client.admin().cluster().prepareGetSnapshots("url-repo").get();
128+
assertThat(getSnapshotsResponse.getSnapshots(), notNullValue());
129+
assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(0));
130+
}
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Integration tests for URL Repository component
2+
#
3+
"URL Repository plugin loaded":
4+
- do:
5+
cluster.state: {}
6+
7+
# Get master node id
8+
- set: { master_node: master }
9+
10+
- do:
11+
nodes.info: {}
12+
13+
- match: { nodes.$master.modules.0.name: repository-url }
14+
15+
---
16+
setup:
17+
18+
- do:
19+
snapshot.create_repository:
20+
repository: test_repo1
21+
body:
22+
type: url
23+
settings:
24+
url: "http://snapshot.test1"
25+
26+
- do:
27+
snapshot.create_repository:
28+
repository: test_repo2
29+
body:
30+
type: url
31+
settings:
32+
url: "http://snapshot.test2"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"Repository can be registered":
2+
3+
- do:
4+
snapshot.create_repository:
5+
repository: test_repo1
6+
body:
7+
type: url
8+
settings:
9+
url: "http://snapshot.test1"
10+
- is_true: acknowledged
11+
12+
- do:
13+
snapshot.get_repository:
14+
repository: test_repo1
15+
16+
- is_true : test_repo1

‎qa/rolling-upgrade/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ task oldClusterTest(type: RestIntegTestTask) {
2929
numBwcNodes = 2
3030
numNodes = 2
3131
clusterName = 'rolling-upgrade'
32+
setting 'repositories.url.allowed_urls', 'http://snapshot.test*'
3233
}
3334
systemProperty 'tests.rest.suite', 'old_cluster'
3435
}
@@ -40,6 +41,7 @@ task mixedClusterTest(type: RestIntegTestTask) {
4041
clusterName = 'rolling-upgrade'
4142
unicastTransportUri = { seedNode, node, ant -> oldClusterTest.nodes.get(0).transportUri() }
4243
dataDir = "${-> oldClusterTest.nodes[1].dataDir}"
44+
setting 'repositories.url.allowed_urls', 'http://snapshot.test*'
4345
}
4446
systemProperty 'tests.rest.suite', 'mixed_cluster'
4547
finalizedBy 'oldClusterTest#node0.stop'
@@ -52,6 +54,7 @@ task upgradedClusterTest(type: RestIntegTestTask) {
5254
clusterName = 'rolling-upgrade'
5355
unicastTransportUri = { seedNode, node, ant -> mixedClusterTest.nodes.get(0).transportUri() }
5456
dataDir = "${-> oldClusterTest.nodes[0].dataDir}"
57+
setting 'repositories.url.allowed_urls', 'http://snapshot.test*'
5558
}
5659
systemProperty 'tests.rest.suite', 'upgraded_cluster'
5760
// only need to kill the mixed cluster tests node here because we explicitly told it to not stop nodes upon completion

‎rest-api-spec/src/main/resources/rest-api-spec/test/snapshot.get_repository/10_basic.yaml

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ setup:
55
snapshot.create_repository:
66
repository: test_repo_get_1
77
body:
8-
type: url
8+
type: fs
99
settings:
10-
url: "http://snapshot.test1"
10+
location: "test_repo_get_1_loc"
1111

1212
- do:
1313
snapshot.create_repository:
1414
repository: test_repo_get_2
1515
body:
16-
type: url
16+
type: fs
1717
settings:
18-
url: "http://snapshot.test2"
18+
location: "test_repo_get_1_loc"
1919

2020
---
2121
"Get all repositories":

‎settings.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ List projects = [
3131
'modules:transport-netty4',
3232
'modules:reindex',
3333
'modules:percolator',
34+
'modules:repository-url',
3435
'plugins:analysis-icu',
3536
'plugins:analysis-kuromoji',
3637
'plugins:analysis-phonetic',

0 commit comments

Comments
 (0)
Please sign in to comment.