From 46e0576482c40fea7a58cd1a4772786dc86f1a28 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Tue, 3 Apr 2018 18:00:18 +0200 Subject: [PATCH 1/2] Use fixture to test repository-url module Pursuing the effort to test repository implementations using fixtures, this commit adds a YAML integration test for the repository-url module that uses a fixture to test URL based repositories on both http:// and file:// prefixes. --- modules/repository-url/build.gradle | 27 +- .../RepositoryURLClientYamlTestSuiteIT.java | 78 +++++- .../repositories/url/URLFixture.java | 160 ++++++++++++ .../test/repository_url/10_basic.yml | 233 ++++++++++++++++-- 4 files changed, 479 insertions(+), 19 deletions(-) create mode 100644 modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java diff --git a/modules/repository-url/build.gradle b/modules/repository-url/build.gradle index 7008111ca9c54..0da879a9503ac 100644 --- a/modules/repository-url/build.gradle +++ b/modules/repository-url/build.gradle @@ -1,3 +1,5 @@ +import org.elasticsearch.gradle.test.AntFixture + /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with @@ -22,6 +24,29 @@ esplugin { classname 'org.elasticsearch.plugin.repository.url.URLRepositoryPlugin' } +forbiddenApisTest { + // we are using jdk-internal instead of jdk-non-portable to allow for com.sun.net.httpserver.* usage + bundledSignatures -= 'jdk-non-portable' + bundledSignatures += 'jdk-internal' +} + +// This directory is shared between two URL repositories and one FS repository in YAML integration tests +File repositoryDir = new File(project.buildDir, "shared-repository") + +/** A task to start the URLFixture that exposes the repositoryDir over HTTP **/ +task urlFixture(type: AntFixture) { + doFirst { + repositoryDir.mkdirs() + } + env 'CLASSPATH', "${ -> project.sourceSets.test.runtimeClasspath.asPath }" + executable = new File(project.runtimeJavaHome, 'bin/java') + args 'org.elasticsearch.repositories.url.URLFixture', baseDir, "${repositoryDir.absolutePath}" +} + integTestCluster { - setting 'repositories.url.allowed_urls', 'http://snapshot.test*' + dependsOn urlFixture + // repositoryDir is used by a FS repository to create snapshots + setting 'path.repo', "${repositoryDir.absolutePath}" + // repositoryDir is used by a URL repository to restore snapshots + setting 'repositories.url.allowed_urls', "http://snapshot.test*,http://${ -> urlFixture.addressAndPort }" } diff --git a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java index 238b14ce013ad..797ea96d890ed 100644 --- a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java +++ b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java @@ -21,9 +21,31 @@ import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; - +import org.apache.http.HttpEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.nio.entity.NStringEntity; +import org.elasticsearch.client.Response; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.support.XContentMapValues; +import org.elasticsearch.repositories.fs.FsRepository; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.junit.Before; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URL; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyMap; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; public class RepositoryURLClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { @@ -35,5 +57,59 @@ public RepositoryURLClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate public static Iterable parameters() throws Exception { return ESClientYamlSuiteTestCase.createParameters(); } + + @Before + public void registerRepositories() throws IOException { + Response clusterSettingsResponse = client().performRequest("GET", "/_cluster/settings?include_defaults=true" + + "&filter_path=defaults.path.repo,defaults.repositories.url.allowed_urls"); + Map clusterSettings = entityAsMap(clusterSettingsResponse); + + @SuppressWarnings("unchecked") + List pathRepo = (List) XContentMapValues.extractValue("defaults.path.repo", clusterSettings); + assertThat(pathRepo, hasSize(1)); + + // Create a FS repository using the path.repo location + Response createFsRepositoryResponse = client().performRequest("PUT", "_snapshot/repository-fs", emptyMap(), + buildRepositorySettings(FsRepository.TYPE, Settings.builder().put("location", pathRepo.get(0)).build())); + assertThat(createFsRepositoryResponse.getStatusLine().getStatusCode(), equalTo(RestStatus.OK.getStatus())); + + // Create a URL repository using the file://{path.repo} URL + Response createFileRepositoryResponse = client().performRequest("PUT", "_snapshot/repository-file", emptyMap(), + buildRepositorySettings(URLRepository.TYPE, Settings.builder().put("url", "file://" + pathRepo.get(0)).build())); + assertThat(createFileRepositoryResponse.getStatusLine().getStatusCode(), equalTo(RestStatus.OK.getStatus())); + + // Create a URL repository using the http://{fixture} URL + @SuppressWarnings("unchecked") + List allowedUrls = (List) XContentMapValues.extractValue("defaults.repositories.url.allowed_urls", clusterSettings); + for (String allowedUrl : allowedUrls) { + try { + InetAddress inetAddress = InetAddress.getByName(new URL(allowedUrl).getHost()); + if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress()) { + Response createUrlRepositoryResponse = client().performRequest("PUT", "_snapshot/repository-url", emptyMap(), + buildRepositorySettings(URLRepository.TYPE, Settings.builder().put("url", allowedUrl).build())); + assertThat(createUrlRepositoryResponse.getStatusLine().getStatusCode(), equalTo(RestStatus.OK.getStatus())); + break; + } + } catch (Exception e) { + logger.debug("Failed to resolve inet address for allowed URL [{}], skipping", allowedUrl); + } + } + } + + private static HttpEntity buildRepositorySettings(final String type, final Settings settings) throws IOException { + try (XContentBuilder builder = jsonBuilder()) { + builder.startObject(); + { + builder.field("type", type); + builder.startObject("settings"); + { + settings.toXContent(builder, ToXContent.EMPTY_PARAMS); + } + builder.endObject(); + } + builder.endObject(); + return new NStringEntity(Strings.toString(builder), ContentType.APPLICATION_JSON); + } + } } diff --git a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java new file mode 100644 index 0000000000000..204b679662bf1 --- /dev/null +++ b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java @@ -0,0 +1,160 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.repositories.url; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.mocksocket.MockHttpServer; +import org.elasticsearch.rest.RestStatus; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Map; +import java.util.Objects; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; + +/** + * This {@link URLFixture} exposes a filesystem directory over HTTP. It is used in repository-url + * integration tests to expose a directory populated using a regular FS repository. + */ +public class URLFixture { + + public static void main(String[] args) throws Exception { + if (args == null || args.length != 2) { + throw new IllegalArgumentException("URLFixture "); + } + + final InetSocketAddress socketAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); + final HttpServer httpServer = MockHttpServer.createHttp(socketAddress, 0); + + try { + final Path workingDirectory = dir(args[0]); + /// Writes the PID of the current Java process in a `pid` file located in the working directory + writeFile(workingDirectory, "pid", ManagementFactory.getRuntimeMXBean().getName().split("@")[0]); + + final String addressAndPort = addressToString(httpServer.getAddress()); + // Writes the address and port of the http server in a `ports` file located in the working directory + writeFile(workingDirectory, "ports", addressAndPort); + + // Exposes the repository over HTTP + final String url = "http://" + addressAndPort; + httpServer.createContext("/", new ResponseHandler(dir(args[1]))); + httpServer.start(); + + // Wait to be killed + Thread.sleep(Long.MAX_VALUE); + + } finally { + httpServer.stop(0); + } + } + + @SuppressForbidden(reason = "Paths#get is fine - we don't have environment here") + private static Path dir(final String dir) { + return Paths.get(dir); + } + + private static void writeFile(final Path dir, final String fileName, final String content) throws IOException { + final Path tempPidFile = Files.createTempFile(dir, null, null); + Files.write(tempPidFile, singleton(content)); + Files.move(tempPidFile, dir.resolve(fileName), StandardCopyOption.ATOMIC_MOVE); + } + + private static String addressToString(final SocketAddress address) { + final InetSocketAddress inetSocketAddress = (InetSocketAddress) address; + if (inetSocketAddress.getAddress() instanceof Inet6Address) { + return "[" + inetSocketAddress.getHostString() + "]:" + inetSocketAddress.getPort(); + } else { + return inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort(); + } + } + + static class ResponseHandler implements HttpHandler { + + private final Path repositoryDir; + + ResponseHandler(final Path repositoryDir) { + this.repositoryDir = repositoryDir; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Response response; + if ("GET".equalsIgnoreCase(exchange.getRequestMethod())) { + String path = exchange.getRequestURI().toString(); + if (path.length() > 0 && path.charAt(0) == '/') { + path = path.substring(1); + } + + Path normalizedRepositoryDir = repositoryDir.normalize(); + Path normalizedPath = normalizedRepositoryDir.resolve(path).normalize(); + + if (normalizedPath.startsWith(normalizedRepositoryDir)) { + if (Files.exists(normalizedPath) && Files.isReadable(normalizedPath) && Files.isRegularFile(normalizedPath)) { + byte[] content = Files.readAllBytes(normalizedPath); + Map headers = singletonMap("Content-Length", String.valueOf(content.length)); + response = new Response(RestStatus.OK, headers, "application/octet-stream", content); + } else { + response = new Response(RestStatus.NOT_FOUND, emptyMap(), "text/plain", new byte[0]); + } + } else { + response = new Response(RestStatus.FORBIDDEN, emptyMap(), "text/plain", new byte[0]); + } + } else { + response = new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), "text/plain", new byte[0]); + } + exchange.sendResponseHeaders(response.status.getStatus(), response.body.length); + if (response.body.length > 0) { + exchange.getResponseBody().write(response.body); + } + exchange.close(); + } + } + + /** + * Represents a HTTP Response. + */ + static class Response { + + final RestStatus status; + final Map headers; + final String contentType; + final byte[] body; + + Response(final RestStatus status, final Map headers, final String contentType, final byte[] body) { + this.status = Objects.requireNonNull(status); + this.headers = Objects.requireNonNull(headers); + this.contentType = Objects.requireNonNull(contentType); + this.body = Objects.requireNonNull(body); + } + } +} diff --git a/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/10_basic.yml b/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/10_basic.yml index 75e7873299869..13d557dfa3bd9 100644 --- a/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/10_basic.yml +++ b/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/10_basic.yml @@ -1,6 +1,99 @@ -# Integration tests for URL Repository component -# -"URL Repository plugin loaded": +# Integration tests for repository-url +setup: + + # Ensure that the FS repository is registered, so we can create + # snapshots that we later restore using the URL repository + - do: + snapshot.get_repository: + repository: repository-fs + + # Index documents + - do: + bulk: + refresh: true + body: + - index: + _index: docs + _type: doc + _id: 1 + - snapshot: one + - index: + _index: docs + _type: doc + _id: 2 + - snapshot: one + - index: + _index: docs + _type: doc + _id: 3 + - snapshot: one + + # Create a first snapshot using the FS repository + - do: + snapshot.create: + repository: repository-fs + snapshot: snapshot-one + wait_for_completion: true + + # Index more documents + - do: + bulk: + refresh: true + body: + - index: + _index: docs + _type: doc + _id: 4 + - snapshot: two + - index: + _index: docs + _type: doc + _id: 5 + - snapshot: two + - index: + _index: docs + _type: doc + _id: 6 + - snapshot: two + - index: + _index: docs + _type: doc + _id: 7 + - snapshot: two + + # Create a second snapshot + - do: + snapshot.create: + repository: repository-fs + snapshot: snapshot-two + wait_for_completion: true + + - do: + snapshot.get: + repository: repository-fs + snapshot: snapshot-one,snapshot-two + +--- +teardown: + + - do: + indices.delete: + index: docs + ignore_unavailable: true + + # Remove the snapshots + - do: + snapshot.delete: + repository: repository-fs + snapshot: snapshot-two + + - do: + snapshot.delete: + repository: repository-fs + snapshot: snapshot-one + +--- +"Module repository-url is loaded": - do: cluster.state: {} @@ -10,23 +103,129 @@ - do: nodes.info: {} - - match: { nodes.$master.modules.0.name: repository-url } + - match: { nodes.$master.modules.0.name: repository-url } --- -setup: +"Restore with repository-url using http://": + # Ensure that the URL repository is registered - do: - snapshot.create_repository: - repository: test_repo1 - body: - type: url - settings: - url: "http://snapshot.test1" + snapshot.get_repository: + repository: repository-url + + - match: { repository-url.type : "url" } + - match: { repository-url.settings.url: '/http://(.+):\d+/' } - do: - snapshot.create_repository: - repository: test_repo2 - body: - type: url - settings: - url: "http://snapshot.test2" + snapshot.get: + repository: repository-url + snapshot: snapshot-one,snapshot-two + + - is_true: snapshots + - match: { snapshots.0.state : SUCCESS } + - match: { snapshots.1.state : SUCCESS } + + # Delete the index + - do: + indices.delete: + index: docs + + # Restore the second snapshot + - do: + snapshot.restore: + repository: repository-url + snapshot: snapshot-two + wait_for_completion: true + + - do: + count: + index: docs + + - match: {count: 7} + + # Delete the index again + - do: + indices.delete: + index: docs + + # Restore the first snapshot + - do: + snapshot.restore: + repository: repository-url + snapshot: snapshot-one + wait_for_completion: true + + - do: + count: + index: docs + + - match: {count: 3} + + - do: + catch: /cannot delete snapshot from a readonly repository/ + snapshot.delete: + repository: repository-url + snapshot: snapshot-two + +--- +"Restore with repository-url using file://": + + # Ensure that the URL repository is registered + - do: + snapshot.get_repository: + repository: repository-file + + - match: { repository-file.type : "url" } + - match: { repository-file.settings.url: '/file://(.+)/' } + + - do: + snapshot.get: + repository: repository-file + snapshot: snapshot-one,snapshot-two + + - is_true: snapshots + - match: { snapshots.0.state : SUCCESS } + - match: { snapshots.1.state : SUCCESS } + + # Delete the index + - do: + indices.delete: + index: docs + + # Restore the second snapshot + - do: + snapshot.restore: + repository: repository-file + snapshot: snapshot-two + wait_for_completion: true + + - do: + count: + index: docs + + - match: {count: 7} + + # Delete the index again + - do: + indices.delete: + index: docs + + # Restore the first snapshot + - do: + snapshot.restore: + repository: repository-file + snapshot: snapshot-one + wait_for_completion: true + + - do: + count: + index: docs + + - match: {count: 3} + + - do: + catch: /cannot delete snapshot from a readonly repository/ + snapshot.delete: + repository: repository-file + snapshot: snapshot-one + From 9a0a65cf110def5b95497176aa0682853591a34e Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Wed, 4 Apr 2018 10:11:14 +0200 Subject: [PATCH 2/2] Apply feedback and add more doc --- modules/repository-url/build.gradle | 7 +++---- .../url/RepositoryURLClientYamlTestSuiteIT.java | 7 +++++++ .../repositories/url/URLFixture.java | 6 ++++-- .../test/repository_url/10_basic.yml | 11 ++++++++++- .../test/repository_url/20_repository.yml | 15 +++++++++++++++ 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/modules/repository-url/build.gradle b/modules/repository-url/build.gradle index 0da879a9503ac..79fe5e7aaefa7 100644 --- a/modules/repository-url/build.gradle +++ b/modules/repository-url/build.gradle @@ -1,5 +1,3 @@ -import org.elasticsearch.gradle.test.AntFixture - /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with @@ -18,6 +16,7 @@ import org.elasticsearch.gradle.test.AntFixture * specific language governing permissions and limitations * under the License. */ +import org.elasticsearch.gradle.test.AntFixture esplugin { description 'Module for URL repository' @@ -33,7 +32,7 @@ forbiddenApisTest { // This directory is shared between two URL repositories and one FS repository in YAML integration tests File repositoryDir = new File(project.buildDir, "shared-repository") -/** A task to start the URLFixture that exposes the repositoryDir over HTTP **/ +/** A task to start the URLFixture which exposes the repositoryDir over HTTP **/ task urlFixture(type: AntFixture) { doFirst { repositoryDir.mkdirs() @@ -47,6 +46,6 @@ integTestCluster { dependsOn urlFixture // repositoryDir is used by a FS repository to create snapshots setting 'path.repo', "${repositoryDir.absolutePath}" - // repositoryDir is used by a URL repository to restore snapshots + // repositoryDir is used by two URL repositories to restore snapshots setting 'repositories.url.allowed_urls', "http://snapshot.test*,http://${ -> urlFixture.addressAndPort }" } diff --git a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java index 797ea96d890ed..79b3f8c5df4bf 100644 --- a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java +++ b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/RepositoryURLClientYamlTestSuiteIT.java @@ -58,6 +58,13 @@ public static Iterable parameters() throws Exception { return ESClientYamlSuiteTestCase.createParameters(); } + /** + * This method registers 3 snapshot/restore repositories: + * - repository-fs: this FS repository is used to create snapshots. + * - repository-url: this URL repository is used to restore snapshots created using the previous repository. It uses + * the URLFixture to restore snapshots over HTTP. + * - repository-file: similar as the previous repository but using a file:// prefix instead of http://. + **/ @Before public void registerRepositories() throws IOException { Response clusterSettingsResponse = client().performRequest("GET", "/_cluster/settings?include_defaults=true" + diff --git a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java index 204b679662bf1..c9a36ec859021 100644 --- a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java +++ b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java @@ -31,6 +31,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -44,7 +45,7 @@ /** * This {@link URLFixture} exposes a filesystem directory over HTTP. It is used in repository-url - * integration tests to expose a directory populated using a regular FS repository. + * integration tests to expose a directory created by a regular FS repository. */ public class URLFixture { @@ -130,7 +131,8 @@ public void handle(HttpExchange exchange) throws IOException { response = new Response(RestStatus.FORBIDDEN, emptyMap(), "text/plain", new byte[0]); } } else { - response = new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), "text/plain", new byte[0]); + response = new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), "text/plain", + "Unsupported HTTP method".getBytes(StandardCharsets.UTF_8)); } exchange.sendResponseHeaders(response.status.getStatus(), response.body.length); if (response.body.length > 0) { diff --git a/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/10_basic.yml b/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/10_basic.yml index 13d557dfa3bd9..7edbc4c08fbf7 100644 --- a/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/10_basic.yml +++ b/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/10_basic.yml @@ -1,4 +1,13 @@ # Integration tests for repository-url +# +# This test is based on 3 repositories, all registered before this +# test is executed. The repository-fs is used to create snapshots +# in a shared directory on the filesystem. Then the test uses a URL +# repository with a "http://" prefix to test the restore of the +# snapshots. In order to do that it uses a URLFixture that exposes +# the content of the shared directory over HTTP. A second URL +# repository is used to test the snapshot restore but this time +# with a "file://" prefix. setup: # Ensure that the FS repository is registered, so we can create @@ -103,7 +112,7 @@ teardown: - do: nodes.info: {} - - match: { nodes.$master.modules.0.name: repository-url } + - match: { nodes.$master.modules.0.name: repository-url } --- "Restore with repository-url using http://": diff --git a/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/20_repository.yml b/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/20_repository.yml index 39cfeee192c9b..77bdac3ac1573 100644 --- a/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/20_repository.yml +++ b/modules/repository-url/src/test/resources/rest-api-spec/test/repository_url/20_repository.yml @@ -14,3 +14,18 @@ repository: test_repo1 - is_true : test_repo1 + +--- +"Repository cannot be be registered": + + - do: + catch: /doesn't match any of the locations specified by path.repo or repositories.url.allowed_urls/ + snapshot.create_repository: + repository: test_repo2 + body: + type: url + settings: + url: "http://snapshot.unknown" + + +