Skip to content

Commit aa40147

Browse files
authored
Add integ tests for ftp:// URL repository (#126757)
We document support for snapshot repositories using `ftp://` URLs but it seems this functionality has not worked for many years because of security-manager restrictions, although nobody noticed because it was not covered by any tests. The migration to the Entitlements framework means that this functionality now works again, so this commit adds tests to make sure we do not break it again in future.
1 parent d71ada6 commit aa40147

File tree

8 files changed

+177
-96
lines changed

8 files changed

+177
-96
lines changed

build-tools-internal/version.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ junit = 4.13.2
4141
junit5 = 5.12.1
4242
hamcrest = 3.0
4343
mocksocket = 1.2
44+
apache_mina = 2.2.4
4445

4546
# test container dependencies
4647
testcontainer = 1.19.2

gradle/verification-metadata.xml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2388,6 +2388,16 @@
23882388
<sha256 value="24c0b2d8081cbc9624e60a1c19f1dd0d104e014cdba4038d9b1aed0ab63721c6" origin="Generated by Gradle"/>
23892389
</artifact>
23902390
</component>
2391+
<component group="org.apache.ftpserver" name="ftplet-api" version="1.2.1">
2392+
<artifact name="ftplet-api-1.2.1.jar">
2393+
<sha256 value="36e47834dfa4225448aaaa5460b304f26e99e837c4a80ef5b41d090735be9b07" origin="Generated by Gradle"/>
2394+
</artifact>
2395+
</component>
2396+
<component group="org.apache.ftpserver" name="ftpserver-core" version="1.2.1">
2397+
<artifact name="ftpserver-core-1.2.1.jar">
2398+
<sha256 value="18279abb21abd74e10bb60d5ac6fa044044cc40f1b34d62418e906f50d4212ec" origin="Generated by Gradle"/>
2399+
</artifact>
2400+
</component>
23912401
<component group="org.apache.geronimo.specs" name="geronimo-jcache_1.0_spec" version="1.0-alpha-1">
23922402
<artifact name="geronimo-jcache_1.0_spec-1.0-alpha-1.jar">
23932403
<sha256 value="0070a12e58f491b95719391325299a6294530ee6c3ce25e50bdc98b0b700966c" origin="Generated by Gradle"/>
@@ -3196,9 +3206,9 @@
31963206
<sha256 value="44a60c610f4e31524b03d81a698b1ecceba116320ea510babf859575b2ea7233" origin="Generated by Gradle"/>
31973207
</artifact>
31983208
</component>
3199-
<component group="org.apache.mina" name="mina-core" version="2.0.17">
3200-
<artifact name="mina-core-2.0.17.jar">
3201-
<sha256 value="08316826fa2b9357b061e52fa8f19ccae75420c949ebe29e28759d2bddd9b39b" origin="Generated by Gradle"/>
3209+
<component group="org.apache.mina" name="mina-core" version="2.2.4">
3210+
<artifact name="mina-core-2.2.4.jar">
3211+
<sha256 value="39b2dfc8e84380bf7adab657d3d5e1625cb6592a885ebdb854ec5c6f7a3ec88d" origin="Generated by Gradle"/>
32023212
</artifact>
32033213
</component>
32043214
<component group="org.apache.pdfbox" name="fontbox" version="2.0.31">

modules/repository-url/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import org.elasticsearch.gradle.PropertyNormalization
1111

1212
apply plugin: 'elasticsearch.internal-yaml-rest-test'
13-
apply plugin: 'elasticsearch.yaml-rest-compat-test'
1413
apply plugin: 'elasticsearch.internal-cluster-test'
1514

1615
esplugin {
@@ -31,7 +30,7 @@ dependencies {
3130
api "commons-codec:commons-codec:${versions.commonscodec}"
3231
api "org.apache.logging.log4j:log4j-1.2-api:${versions.log4j}"
3332
yamlRestTestImplementation project(':test:fixtures:url-fixture')
34-
internalClusterTestImplementation project(':test:fixtures:url-fixture')
33+
yamlRestTestImplementation project(':modules:repository-url')
3534
}
3635

3736
tasks.named("thirdPartyAudit").configure {

modules/repository-url/src/yamlRestTest/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java

Lines changed: 32 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,35 @@
1010
package org.elasticsearch.repositories.url;
1111

1212
import fixture.url.URLFixture;
13+
import io.netty.handler.codec.http.HttpMethod;
1314

1415
import com.carrotsearch.randomizedtesting.annotations.Name;
1516
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
1617

17-
import org.apache.http.HttpEntity;
18-
import org.apache.http.entity.ContentType;
19-
import org.apache.http.nio.entity.NStringEntity;
2018
import org.elasticsearch.client.Request;
2119
import org.elasticsearch.client.Response;
22-
import org.elasticsearch.common.Strings;
2320
import org.elasticsearch.common.settings.Settings;
2421
import org.elasticsearch.common.xcontent.support.XContentMapValues;
2522
import org.elasticsearch.core.PathUtils;
2623
import org.elasticsearch.repositories.fs.FsRepository;
27-
import org.elasticsearch.rest.RestStatus;
2824
import org.elasticsearch.test.cluster.ElasticsearchCluster;
25+
import org.elasticsearch.test.rest.ESRestTestCase;
2926
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
3027
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
31-
import org.elasticsearch.xcontent.ToXContent;
32-
import org.elasticsearch.xcontent.XContentBuilder;
3328
import org.junit.Before;
3429
import org.junit.ClassRule;
3530
import org.junit.rules.RuleChain;
3631
import org.junit.rules.TestRule;
3732

3833
import java.io.IOException;
39-
import java.net.InetAddress;
4034
import java.net.URI;
41-
import java.net.URL;
4235
import java.util.List;
4336
import java.util.Map;
37+
import java.util.function.UnaryOperator;
4438

45-
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
46-
import static org.hamcrest.Matchers.equalTo;
4739
import static org.hamcrest.Matchers.hasSize;
4840
import static org.hamcrest.Matchers.notNullValue;
41+
import static org.hamcrest.Matchers.startsWith;
4942

5043
public class RepositoryURLClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
5144

@@ -54,7 +47,7 @@ public class RepositoryURLClientYamlTestSuiteIT extends ESClientYamlSuiteTestCas
5447
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
5548
.module("repository-url")
5649
.setting("path.repo", urlFixture::getRepositoryDir)
57-
.setting("repositories.url.allowed_urls", () -> "http://snapshot.test*, " + urlFixture.getAddress())
50+
.setting("repositories.url.allowed_urls", () -> "http://snapshot.test*, " + urlFixture.getAddress() + "," + urlFixture.getFtpUrl())
5851
.build();
5952

6053
@ClassRule
@@ -75,11 +68,16 @@ public static Iterable<Object[]> parameters() throws Exception {
7568
}
7669

7770
/**
78-
* This method registers 3 snapshot/restore repositories:
79-
* - repository-fs: this FS repository is used to create snapshots.
80-
* - repository-url: this URL repository is used to restore snapshots created using the previous repository. It uses
81-
* the URLFixture to restore snapshots over HTTP.
82-
* - repository-file: similar as the previous repository but using a file:// prefix instead of http://.
71+
* This method registers 4 snapshot/restore repositories:
72+
* <ol>
73+
* <li>{@code repository-fs}: this FS repository is used to create snapshots.</li>
74+
* <li>{@code repository-url-http}: this URL repository is used to restore snapshots created using the previous repository. It uses
75+
* the {@link URLFixture} to restore snapshots over HTTP.</li>
76+
* <li>{@code repository-url-file}: similar as the previous repository but using the {@code file://} scheme instead of
77+
* {@code http://}.</li>
78+
* <li>{@code repository-url-ftp}: similar as the previous repository but using the {@code ftp://} scheme instead of
79+
* {@code http://}.</li>
80+
* </ol>
8381
**/
8482
@Before
8583
public void registerRepositories() throws IOException {
@@ -97,54 +95,25 @@ public void registerRepositories() throws IOException {
9795
final String pathRepo = pathRepos.get(0);
9896
final URI pathRepoUri = PathUtils.get(pathRepo).toUri().normalize();
9997

100-
// Create a FS repository using the path.repo location
101-
Request createFsRepositoryRequest = new Request("PUT", "/_snapshot/repository-fs");
102-
createFsRepositoryRequest.setEntity(
103-
buildRepositorySettings(FsRepository.TYPE, Settings.builder().put("location", pathRepo).build())
104-
);
105-
Response createFsRepositoryResponse = client().performRequest(createFsRepositoryRequest);
106-
assertThat(createFsRepositoryResponse.getStatusLine().getStatusCode(), equalTo(RestStatus.OK.getStatus()));
107-
108-
// Create a URL repository using the file://{path.repo} URL
109-
Request createFileRepositoryRequest = new Request("PUT", "/_snapshot/repository-file");
110-
createFileRepositoryRequest.setEntity(
111-
buildRepositorySettings("url", Settings.builder().put("url", pathRepoUri.toString()).build())
112-
);
113-
Response createFileRepositoryResponse = client().performRequest(createFileRepositoryRequest);
114-
assertThat(createFileRepositoryResponse.getStatusLine().getStatusCode(), equalTo(RestStatus.OK.getStatus()));
115-
116-
// Create a URL repository using the http://{fixture} URL
117-
@SuppressWarnings("unchecked")
118-
List<String> allowedUrls = (List<String>) XContentMapValues.extractValue("defaults.repositories.url.allowed_urls", clusterSettings);
119-
for (String allowedUrl : allowedUrls) {
120-
try {
121-
InetAddress inetAddress = InetAddress.getByName(new URL(allowedUrl).getHost());
122-
if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress()) {
123-
Request createUrlRepositoryRequest = new Request("PUT", "/_snapshot/repository-url");
124-
createUrlRepositoryRequest.setEntity(buildRepositorySettings("url", Settings.builder().put("url", allowedUrl).build()));
125-
Response createUrlRepositoryResponse = client().performRequest(createUrlRepositoryRequest);
126-
assertThat(createUrlRepositoryResponse.getStatusLine().getStatusCode(), equalTo(RestStatus.OK.getStatus()));
127-
break;
128-
}
129-
} catch (Exception e) {
130-
logger.debug("Failed to resolve inet address for allowed URL [{}], skipping", allowedUrl);
131-
}
132-
}
98+
createRepository("repository-fs", FsRepository.TYPE, b -> b.put("location", pathRepo));
99+
createUrlRepository("file", pathRepoUri.toString());
100+
createUrlRepository("http", urlFixture.getAddress());
101+
createUrlRepository("ftp", urlFixture.getFtpUrl());
102+
}
103+
104+
private static void createUrlRepository(final String nameSuffix, final String url) throws IOException {
105+
assertThat(url, startsWith(nameSuffix + "://"));
106+
createRepository("repository-url-" + nameSuffix, URLRepository.TYPE, b -> b.put("url", url));
133107
}
134108

135-
private static HttpEntity buildRepositorySettings(final String type, final Settings settings) throws IOException {
136-
try (XContentBuilder builder = jsonBuilder()) {
137-
builder.startObject();
138-
{
139-
builder.field("type", type);
140-
builder.startObject("settings");
141-
{
142-
settings.toXContent(builder, ToXContent.EMPTY_PARAMS);
143-
}
144-
builder.endObject();
145-
}
109+
private static void createRepository(final String name, final String type, final UnaryOperator<Settings.Builder> settings)
110+
throws IOException {
111+
assertOK(client().performRequest(ESRestTestCase.newXContentRequest(HttpMethod.PUT, "/_snapshot/" + name, (builder, params) -> {
112+
builder.field("type", type);
113+
builder.startObject("settings");
114+
settings.apply(Settings.builder()).build().toXContent(builder, params);
146115
builder.endObject();
147-
return new NStringEntity(Strings.toString(builder), ContentType.APPLICATION_JSON);
148-
}
116+
return builder;
117+
})));
149118
}
150119
}

modules/repository-url/src/yamlRestTest/resources/rest-api-spec/test/repository_url/10_basic.yml

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,14 @@ teardown:
116116
# Ensure that the URL repository is registered
117117
- do:
118118
snapshot.get_repository:
119-
repository: repository-url
119+
repository: repository-url-http
120120

121-
- match: { repository-url.type : "url" }
122-
- match: { repository-url.settings.url: '/http://(.+):\d+/' }
121+
- match: { repository-url-http.type : "url" }
122+
- match: { repository-url-http.settings.url: '/http://(.+):\d+/' }
123123

124124
- do:
125125
snapshot.get:
126-
repository: repository-url
126+
repository: repository-url-http
127127
snapshot: snapshot-one,snapshot-two
128128

129129
- is_true: snapshots
@@ -138,7 +138,7 @@ teardown:
138138
# Restore the second snapshot
139139
- do:
140140
snapshot.restore:
141-
repository: repository-url
141+
repository: repository-url-http
142142
snapshot: snapshot-two
143143
wait_for_completion: true
144144

@@ -156,7 +156,7 @@ teardown:
156156
# Restore the first snapshot
157157
- do:
158158
snapshot.restore:
159-
repository: repository-url
159+
repository: repository-url-http
160160
snapshot: snapshot-one
161161
wait_for_completion: true
162162

@@ -169,7 +169,7 @@ teardown:
169169
- do:
170170
catch: /repository is readonly/
171171
snapshot.delete:
172-
repository: repository-url
172+
repository: repository-url-http
173173
snapshot: snapshot-two
174174

175175
---
@@ -178,14 +178,14 @@ teardown:
178178
# Ensure that the URL repository is registered
179179
- do:
180180
snapshot.get_repository:
181-
repository: repository-file
181+
repository: repository-url-file
182182

183-
- match: { repository-file.type : "url" }
184-
- match: { repository-file.settings.url: '/file://(.+)/' }
183+
- match: { repository-url-file.type : "url" }
184+
- match: { repository-url-file.settings.url: '/file://(.+)/' }
185185

186186
- do:
187187
snapshot.get:
188-
repository: repository-file
188+
repository: repository-url-file
189189
snapshot: snapshot-one,snapshot-two
190190

191191
- is_true: snapshots
@@ -200,7 +200,7 @@ teardown:
200200
# Restore the second snapshot
201201
- do:
202202
snapshot.restore:
203-
repository: repository-file
203+
repository: repository-url-file
204204
snapshot: snapshot-two
205205
wait_for_completion: true
206206

@@ -218,7 +218,7 @@ teardown:
218218
# Restore the first snapshot
219219
- do:
220220
snapshot.restore:
221-
repository: repository-file
221+
repository: repository-url-file
222222
snapshot: snapshot-one
223223
wait_for_completion: true
224224

@@ -231,7 +231,69 @@ teardown:
231231
- do:
232232
catch: /repository is readonly/
233233
snapshot.delete:
234-
repository: repository-file
234+
repository: repository-url-file
235+
snapshot: snapshot-one
236+
237+
---
238+
"Restore with repository-url using ftp://":
239+
240+
# Ensure that the ftp:// URL repository is registered
241+
- do:
242+
snapshot.get_repository:
243+
repository: repository-url-ftp
244+
245+
- match: { repository-url-ftp.type : "url" }
246+
- match: { repository-url-ftp.settings.url: '/ftp://(.+)/' }
247+
248+
- do:
249+
snapshot.get:
250+
repository: repository-url-ftp
251+
snapshot: snapshot-one,snapshot-two
252+
253+
- is_true: snapshots
254+
- match: { snapshots.0.state : SUCCESS }
255+
- match: { snapshots.1.state : SUCCESS }
256+
257+
# Delete the index
258+
- do:
259+
indices.delete:
260+
index: docs
261+
262+
# Restore the second snapshot
263+
- do:
264+
snapshot.restore:
265+
repository: repository-url-ftp
266+
snapshot: snapshot-two
267+
wait_for_completion: true
268+
269+
- do:
270+
count:
271+
index: docs
272+
273+
- match: {count: 7}
274+
275+
# Delete the index again
276+
- do:
277+
indices.delete:
278+
index: docs
279+
280+
# Restore the first snapshot
281+
- do:
282+
snapshot.restore:
283+
repository: repository-url-ftp
284+
snapshot: snapshot-one
285+
wait_for_completion: true
286+
287+
- do:
288+
count:
289+
index: docs
290+
291+
- match: {count: 3}
292+
293+
- do:
294+
catch: /repository is readonly/
295+
snapshot.delete:
296+
repository: repository-url-ftp
235297
snapshot: snapshot-one
236298

237299
---
@@ -240,7 +302,7 @@ teardown:
240302
- do:
241303
catch: /snapshot_missing_exception/
242304
snapshot.get:
243-
repository: repository-url
305+
repository: repository-url-http
244306
snapshot: missing
245307

246308
---
@@ -249,7 +311,7 @@ teardown:
249311
- do:
250312
catch: /snapshot_missing_exception/
251313
snapshot.delete:
252-
repository: repository-url
314+
repository: repository-url-http
253315
snapshot: missing
254316

255317
---
@@ -258,6 +320,6 @@ teardown:
258320
- do:
259321
catch: /snapshot_restore_exception/
260322
snapshot.restore:
261-
repository: repository-url
323+
repository: repository-url-http
262324
snapshot: missing
263325
wait_for_completion: true

test/fixtures/url-fixture/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ description = 'Fixture for URL external service'
1212
dependencies {
1313
api project(':server')
1414
api project(':test:framework')
15+
api "org.apache.ftpserver:ftpserver-core:1.2.1"
16+
api "org.apache.ftpserver:ftplet-api:1.2.1"
17+
api "org.apache.mina:mina-core:${versions.apache_mina}"
1518
}

0 commit comments

Comments
 (0)