diff options
author | Valerij Fredriksen <freva@users.noreply.github.com> | 2019-10-31 14:52:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-31 14:52:51 +0100 |
commit | 2caa198f03f94ce82dd3bac879b03a2084a781c7 (patch) | |
tree | 45dfe1527bc0e07aa296897f52fa4b0f6fb787b8 /controller-server | |
parent | 7799f40156642d588112ce765188583659bf4f42 (diff) | |
parent | d1ec7b663b51ad2013f38d7ee03fbf2e3aa677c3 (diff) |
Merge pull request #11172 from vespa-engine/freva/configserver-v1
Simplify ConfigServerRestExecutorImpl
Diffstat (limited to 'controller-server')
7 files changed, 160 insertions, 312 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java index 72b11f56d9f..e3c048e865a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java @@ -1,19 +1,12 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.proxy; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Inject; -import com.yahoo.config.provision.Environment; -import com.yahoo.config.provision.zone.ZoneApi; import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.config.provision.zone.ZoneList; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.log.LogLevel; -import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier; -import com.yahoo.vespa.athenz.utils.AthenzIdentities; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import org.apache.http.Header; import org.apache.http.client.config.RequestConfig; @@ -24,20 +17,15 @@ import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; +import javax.net.ssl.HostnameVerifier; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.net.URI; -import java.security.cert.X509Certificate; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; @@ -47,7 +35,8 @@ import java.util.Optional; import java.util.Set; import java.util.logging.Logger; -import static java.util.Collections.singleton; +import static com.yahoo.yolean.Exceptions.uncheck; + /** * @author Haakon Dybdahl @@ -65,28 +54,22 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { private final ServiceIdentityProvider sslContextProvider; @Inject - public ConfigServerRestExecutorImpl(ZoneRegistry zoneRegistry, - ServiceIdentityProvider sslContextProvider) { + public ConfigServerRestExecutorImpl(ZoneRegistry zoneRegistry, ServiceIdentityProvider sslContextProvider) { this.zoneRegistry = zoneRegistry; this.sslContextProvider = sslContextProvider; } @Override public ProxyResponse handle(ProxyRequest proxyRequest) throws ProxyException { - if (proxyRequest.isDiscoveryRequest()) { - return createDiscoveryResponse(proxyRequest); - } - - ZoneId zoneId = ZoneId.from(proxyRequest.getEnvironment(), proxyRequest.getRegion()); - - List<URI> allServers = getConfigserverEndpoints(zoneId); + HostnameVerifier hostnameVerifier = createHostnameVerifier(proxyRequest.getZoneId()); + List<URI> allServers = getConfigserverEndpoints(proxyRequest.getZoneId()); StringBuilder errorBuilder = new StringBuilder(); - if (queueFirstServerIfDown(allServers, proxyRequest)) { + if (queueFirstServerIfDown(allServers, hostnameVerifier)) { errorBuilder.append("Change ordering due to failed ping."); } for (URI uri : allServers) { - Optional<ProxyResponse> proxyResponse = proxyCall(uri, proxyRequest, errorBuilder); + Optional<ProxyResponse> proxyResponse = proxyCall(uri, proxyRequest, hostnameVerifier, errorBuilder); if (proxyResponse.isPresent()) { return proxyResponse.get(); } @@ -100,45 +83,17 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { // TODO: Use config server VIP for all zones that have one // Make a local copy of the list as we want to manipulate it in case of ping problems. if (zoneId.region().value().startsWith("aws-") || zoneId.region().value().contains("-aws-")) { - return Collections.singletonList(zoneRegistry.getConfigServerVipUri(zoneId)); + return List.of(zoneRegistry.getConfigServerVipUri(zoneId)); } else { return new ArrayList<>(zoneRegistry.getConfigServerUris(zoneId)); } } - private static class DiscoveryResponseStructure { - public List<String> uris = new ArrayList<>(); - } - - private ProxyResponse createDiscoveryResponse(ProxyRequest proxyRequest) { - ObjectMapper mapper = new ObjectMapper(); - DiscoveryResponseStructure responseStructure = new DiscoveryResponseStructure(); - String environmentName = proxyRequest.getEnvironment(); - - ZoneList zones = zoneRegistry.zones().all(); - if ( ! environmentName.isEmpty()) - zones = zones.in(Environment.from(environmentName)); - - for (ZoneApi zone : zones.zones()) { - responseStructure.uris.add(proxyRequest.getScheme() + "://" + proxyRequest.getControllerPrefix() + - zone.getEnvironment().value() + "/" + zone.getRegionName().value()); - } - JsonNode node = mapper.valueToTree(responseStructure); - return new ProxyResponse(proxyRequest, node.toString(), 200, Optional.empty(), "application/json"); - } - - private static String removeFirstSlashIfAny(String url) { - if (url.startsWith("/")) { - return url.substring(1); - } - return url; - } - - private Optional<ProxyResponse> proxyCall(URI uri, ProxyRequest proxyRequest, StringBuilder errorBuilder) + private Optional<ProxyResponse> proxyCall( + URI uri, ProxyRequest proxyRequest, HostnameVerifier hostnameVerifier, StringBuilder errorBuilder) throws ProxyException { - String fullUri = uri.toString() + removeFirstSlashIfAny(proxyRequest.getConfigServerRequest()); final HttpRequestBase requestBase = createHttpBaseRequest( - proxyRequest.getMethod(), fullUri, proxyRequest.getData()); + proxyRequest.getMethod(), proxyRequest.createConfigServerRequestUri(uri), proxyRequest.getData()); // Empty list of headers to copy for now, add headers when needed, or rewrite logic. copyHeaders(proxyRequest.getHeaders(), requestBase); @@ -147,7 +102,7 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { .setConnectionRequestTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis()) .setSocketTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis()).build(); try ( - CloseableHttpClient client = createHttpClient(config, sslContextProvider, zoneRegistry, proxyRequest); + CloseableHttpClient client = createHttpClient(config, sslContextProvider, hostnameVerifier); CloseableHttpResponse response = client.execute(requestBase) ) { String content = getContent(response); @@ -168,7 +123,7 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { contentType = "application/json"; } // Send response back - return Optional.of(new ProxyResponse(proxyRequest, content, status, Optional.of(uri), contentType)); + return Optional.of(new ProxyResponse(proxyRequest, content, status, uri, contentType)); } catch (Exception e) { errorBuilder.append("Talking to server ").append(uri.getHost()); errorBuilder.append(" got exception ").append(e.getMessage()); @@ -179,20 +134,12 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { private static String getContent(CloseableHttpResponse response) { return Optional.ofNullable(response.getEntity()) - .map(entity -> - { - try { - return EntityUtils.toString(entity); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - ).orElse(""); + .map(entity -> uncheck(() -> EntityUtils.toString(entity))) + .orElse(""); } - private static HttpRequestBase createHttpBaseRequest(String method, String uri, InputStream data) throws ProxyException { - Method enumMethod = Method.valueOf(method); - switch (enumMethod) { + private static HttpRequestBase createHttpBaseRequest(Method method, URI uri, InputStream data) throws ProxyException { + switch (method) { case GET: return new HttpGet(uri); case POST: @@ -235,7 +182,7 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { * if it is not responding, we try the other servers first. False positive/negatives are not critical, * but will increase latency to some extent. */ - private boolean queueFirstServerIfDown(List<URI> allServers, ProxyRequest proxyRequest) { + private boolean queueFirstServerIfDown(List<URI> allServers, HostnameVerifier hostnameVerifier) { if (allServers.size() < 2) { return false; } @@ -248,9 +195,8 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { .setConnectionRequestTimeout(timeout) .setSocketTimeout(timeout).build(); try ( - CloseableHttpClient client = createHttpClient(config, sslContextProvider, zoneRegistry, proxyRequest); + CloseableHttpClient client = createHttpClient(config, sslContextProvider, hostnameVerifier); CloseableHttpResponse response = client.execute(httpget) - ) { if (response.getStatusLine().getStatusCode() == 200) { return false; @@ -260,61 +206,23 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { // We ignore this, if server is restarting this might happen. } // Some error happened, move this server to the back. The other servers should be running. - allServers.remove(0); - allServers.add(uri); + Collections.rotate(allServers, -1); return true; } - @SuppressWarnings("deprecation") + private HostnameVerifier createHostnameVerifier(ZoneId zoneId) { + return new AthenzIdentityVerifier(Set.of(zoneRegistry.getConfigServerHttpsIdentity(zoneId))); + } + private static CloseableHttpClient createHttpClient(RequestConfig config, ServiceIdentityProvider sslContextProvider, - ZoneRegistry zoneRegistry, - ProxyRequest proxyRequest) { - AthenzIdentityVerifier hostnameVerifier = - new AthenzIdentityVerifier( - singleton( - zoneRegistry.getConfigServerHttpsIdentity( - ZoneId.from(proxyRequest.getEnvironment(), proxyRequest.getRegion())))); + HostnameVerifier hostnameVerifier) { return HttpClientBuilder.create() .setUserAgent("config-server-proxy-client") .setSslcontext(sslContextProvider.getIdentitySslContext()) - .setHostnameVerifier(new AthenzIdentityVerifierAdapter(hostnameVerifier)) + .setSSLHostnameVerifier(hostnameVerifier) .setDefaultRequestConfig(config) .build(); } - @SuppressWarnings("deprecation") - private static class AthenzIdentityVerifierAdapter implements X509HostnameVerifier { - - private final AthenzIdentityVerifier verifier; - - AthenzIdentityVerifierAdapter(AthenzIdentityVerifier verifier) { - this.verifier = verifier; - } - - @Override - public boolean verify(String hostname, SSLSession sslSession) { - return verifier.verify(hostname, sslSession); - } - - @Override - public void verify(String host, SSLSocket ssl) { /* All sockets accepted */} - - @Override - public void verify(String hostname, X509Certificate certificate) throws SSLException { - AthenzIdentity identity = AthenzIdentities.from(certificate); - if (!verifier.isTrusted(identity)) { - throw new SSLException("Athenz identity is not trusted: " + identity.getFullName()); - } - } - - @Override - public void verify(String hostname, String[] cns, String[] subjectAlts) throws SSLException { - AthenzIdentity identity = AthenzIdentities.from(cns[0]); - if (!verifier.isTrusted(identity)) { - throw new SSLException("Athenz identity is not trusted: " + identity.getFullName()); - } - } - } - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java index 6854d583222..100292a0bdc 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java @@ -1,15 +1,17 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.proxy; +import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.container.jdisc.HttpRequest; -import com.yahoo.net.HostName; -import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.net.URLDecoder; +import java.net.URISyntaxException; import java.util.List; import java.util.Map; +import java.util.Objects; + +import static com.yahoo.jdisc.http.HttpRequest.Method; /** * Keeping information about the calls that are being proxied. @@ -19,101 +21,84 @@ import java.util.Map; */ public class ProxyRequest { - private final String environment; - private final String region; - private final String configServerRequest; - private final InputStream requestData; + private final Method method; + private final URI requestUri; private final Map<String, List<String>> headers; - private final String method; - private final String controllerPrefix; - private final String scheme; + private final InputStream requestData; + + private final ZoneId zoneId; + private final String proxyPath; /** * The constructor calls exception if the request is invalid. * * @param request the request from the jdisc framework. - * @param pathPrefix the path prefix of the proxy. + * @param zoneId the zone to proxy to. + * @param proxyPath the path to proxy to. * @throws ProxyException on errors */ - public ProxyRequest(HttpRequest request, String pathPrefix) throws ProxyException, IOException { - this(request.getUri(), request.getJDiscRequest().headers(), request.getData(), request.getMethod().name(), - pathPrefix); + public ProxyRequest(HttpRequest request, ZoneId zoneId, String proxyPath) throws ProxyException { + this(request.getMethod(), request.getUri(), request.getJDiscRequest().headers(), request.getData(), + zoneId, proxyPath); } - ProxyRequest(URI requestUri, Map<String, List<String>> headers, InputStream body, String method, - String pathPrefix) throws ProxyException, IOException { - if (requestUri == null) { - throw new ProxyException(ErrorResponse.badRequest("Request not set.")); - } - final String path = URLDecoder.decode(requestUri.getPath(),"UTF-8"); - if (! path.startsWith(pathPrefix)) { - // This has to be caused by wrong mapping of path in services.xml. - throw new ProxyException(ErrorResponse.notFoundError("Request not starting with " + pathPrefix)); - } - final String uriNoPrefix = path.replaceFirst(pathPrefix, "") - + (requestUri.getRawQuery() == null ? "" : "?" + requestUri.getRawQuery()); - - final String[] parts = uriNoPrefix.split("/"); + ProxyRequest(Method method, URI requestUri, Map<String, List<String>> headers, InputStream body, + ZoneId zoneId, String proxyPath) throws ProxyException { + Objects.requireNonNull(requestUri, "Request must be non-null"); + if (!requestUri.getPath().endsWith(proxyPath)) + throw new ProxyException(ErrorResponse.badRequest(String.format( + "Request path '%s' does not end with proxy path '%s'", requestUri.getPath(), proxyPath))); - this.environment = parts.length > 0 ? parts[0] : ""; - this.region = parts.length > 1 ? parts[1] : ""; - this.configServerRequest = parts.length > 2 ? uriNoPrefix.replace(environment + "/" + region, "") : ""; + this.method = Objects.requireNonNull(method); + this.requestUri = Objects.requireNonNull(requestUri); + this.headers = Objects.requireNonNull(headers); this.requestData = body; - this.headers = headers; - this.method = method; - - String hostPort = headers.containsKey("host") - ? headers.get("host").get(0) - : HostName.getLocalhost() + ":" + requestUri.getPort(); - StringBuilder prefix = new StringBuilder(hostPort + pathPrefix); - if (! environment.isEmpty()) { - prefix.append(environment).append("/").append(region); - } - - this.controllerPrefix = prefix.toString(); - this.scheme = requestUri.getScheme(); - } - /** - * A discovery query lists environments and regions. - */ - public boolean isDiscoveryRequest() { - return region.isEmpty(); + this.zoneId = Objects.requireNonNull(zoneId); + this.proxyPath = proxyPath.startsWith("/") ? proxyPath : "/" + proxyPath; } - public String getRegion() { - return region; - } - public String getEnvironment() { - return environment; + public Method getMethod() { + return method; } - public String getConfigServerRequest() { - return configServerRequest; + public Map<String, List<String>> getHeaders() { + return headers; } public InputStream getData() { return requestData; } - @Override - public String toString() { - return "[ region: " + region + " env: " + environment + " request: " + configServerRequest + "]"; + public ZoneId getZoneId() { + return zoneId; } - public Map<String, List<String>> getHeaders() { - return headers; + public URI createConfigServerRequestUri(URI baseURI) { + try { + return new URI(baseURI.getScheme(), baseURI.getUserInfo(), baseURI.getHost(), + baseURI.getPort(), proxyPath, requestUri.getQuery(), requestUri.getFragment()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } } - public String getMethod() { - return method; + public URI getControllerPrefixUri() { + String prefixPath = proxyPath.equals("/") && !requestUri.getPath().endsWith("/") ? + requestUri.getPath() + proxyPath : + requestUri.getPath().substring(0, requestUri.getPath().length() - proxyPath.length() + 1); + try { + return new URI(requestUri.getScheme(), requestUri.getUserInfo(), requestUri.getHost(), + requestUri.getPort(), prefixPath, null, null); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } } - public String getControllerPrefix() { - return controllerPrefix; + @Override + public String toString() { + return "[zone: " + zoneId + " request: " + proxyPath + "]"; } - public String getScheme() { return scheme; } - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java index 3f878740ff0..b0b4f1a556a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java @@ -9,14 +9,13 @@ import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; -import java.util.Optional; /** * Response class that also rewrites URL from config server. * * @author Haakon Dybdahl */ -public class ProxyResponse extends HttpResponse { +public class ProxyResponse extends HttpResponse { private final String bodyResponseRewritten; private final String contentType; @@ -25,29 +24,21 @@ public class ProxyResponse extends HttpResponse { ProxyRequest controllerRequest, String bodyResponse, int statusResponse, - Optional<URI> configServer, + URI configServer, String contentType) { super(statusResponse); this.contentType = contentType; - if (! configServer.isPresent() || controllerRequest.getControllerPrefix().isEmpty()) { - bodyResponseRewritten = bodyResponse; - return; - } - final String configServerPrefix; final String controllerRequestPrefix; try { configServerPrefix = new URIBuilder() - .setScheme(configServer.get().getScheme()) - .setHost(configServer.get().getHost()) - .setPort(configServer.get().getPort()) - .build().toString(); - controllerRequestPrefix = new URIBuilder() - .setScheme(controllerRequest.getScheme()) - // controller prefix is more than host, so it is a bit hackish, but verified by tests. - .setHost(controllerRequest.getControllerPrefix()) + .setScheme(configServer.getScheme()) + .setHost(configServer.getHost()) + .setPort(configServer.getPort()) + .setPath("/") .build().toString(); + controllerRequestPrefix = controllerRequest.getControllerPrefixUri().toString(); } catch (URISyntaxException e) { throw new RuntimeException(e); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java index 5ce679276f7..be601511763 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java @@ -1,25 +1,24 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.restapi.zone.v2; +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.config.provision.zone.ZoneList; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.restapi.ErrorResponse; import com.yahoo.restapi.Path; +import com.yahoo.restapi.SlimeJsonResponse; import com.yahoo.slime.Cursor; import com.yahoo.slime.Slime; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.config.provision.zone.ZoneList; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler; import com.yahoo.vespa.hosted.controller.proxy.ConfigServerRestExecutor; import com.yahoo.vespa.hosted.controller.proxy.ProxyException; import com.yahoo.vespa.hosted.controller.proxy.ProxyRequest; -import com.yahoo.restapi.ErrorResponse; -import com.yahoo.restapi.SlimeJsonResponse; import com.yahoo.yolean.Exceptions; -import java.io.IOException; import java.util.logging.Level; /** @@ -83,8 +82,8 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler { throw new IllegalArgumentException("No such zone: " + zoneId.value()); } try { - return proxy.handle(new ProxyRequest(request, "/zone/v2/")); - } catch (ProxyException | IOException e) { + return proxy.handle(new ProxyRequest(request, zoneId, path.getRest())); + } catch (ProxyException e) { throw new RuntimeException(e); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java index 04a987d98d1..b9d68c2a3da 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java @@ -1,20 +1,16 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.proxy; +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.jdisc.http.HttpRequest; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import java.io.IOException; import java.net.URI; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; /** * @author Haakon Dybdahl @@ -26,58 +22,53 @@ public class ProxyRequestTest { @Test public void testEmpty() throws Exception { - exception.expectMessage("Request not set."); - testRequest(null, "/zone/v2/"); + exception.expectMessage("Request must be non-null"); + new ProxyRequest(HttpRequest.Method.GET, null, Map.of(), null, ZoneId.from("dev", "us-north-1"), "/zone/v2"); } @Test public void testBadUri() throws Exception { - exception.expectMessage("Request not starting with /zone/v2/"); - testRequest(URI.create("http://foo"), "/zone/v2/"); + exception.expectMessage("Request path '/path' does not end with proxy path '/zone/v2/'"); + testRequest("http://domain.tld/path", "/zone/v2/"); } @Test - public void testConfigRequestEmpty() throws Exception { - ProxyRequest proxyRequest = testRequest(URI.create("http://foo/zone/v2/foo/bar"), "/zone/v2/"); - assertEquals("foo", proxyRequest.getEnvironment()); - assertEquals("bar", proxyRequest.getRegion()); - assertFalse(proxyRequest.isDiscoveryRequest()); - assertTrue(proxyRequest.getConfigServerRequest().isEmpty()); - - } - - @Test - public void testDiscoveryRequest() throws Exception { - ProxyRequest proxyRequest = testRequest(URI.create("http://foo/zone/v2/foo"), "/zone/v2/"); - assertEquals("foo", proxyRequest.getEnvironment()); - assertTrue(proxyRequest.isDiscoveryRequest()); - - } - - @Test - public void testProxyRequest() throws Exception { - ProxyRequest proxyRequest = testRequest(URI.create("http://foo/zone/v2/foo/bar/bla/bla/v1/something"), - "/zone/v2/"); - assertEquals("foo", proxyRequest.getEnvironment()); - assertEquals("/bla/bla/v1/something", proxyRequest.getConfigServerRequest()); - } - - @Test - public void testProxyRequestWithParameters() throws Exception { - ProxyRequest proxyRequest = testRequest(URI.create("http://foo/zone/v2/foo/bar/something?p=v&q=y"), - "/zone/v2/"); - assertEquals("foo", proxyRequest.getEnvironment()); - assertEquals("/something?p=v&q=y", proxyRequest.getConfigServerRequest()); + public void testUris() throws Exception { + { + // Root request + ProxyRequest request = testRequest("http://controller.domain.tld/my/path", ""); + assertEquals(URI.create("http://controller.domain.tld/my/path/"), request.getControllerPrefixUri()); + assertEquals(URI.create("https://cfg.prod.us-north-1.domain.tld:1234/"), + request.createConfigServerRequestUri(URI.create("https://cfg.prod.us-north-1.domain.tld:1234/"))); + } + + { + // Root request with trailing / + ProxyRequest request = testRequest("http://controller.domain.tld/my/path/", "/"); + assertEquals(URI.create("http://controller.domain.tld/my/path/"), request.getControllerPrefixUri()); + assertEquals(URI.create("https://cfg.prod.us-north-1.domain.tld:1234/"), + request.createConfigServerRequestUri(URI.create("https://cfg.prod.us-north-1.domain.tld:1234/"))); + } + + { + // API path test + ProxyRequest request = testRequest("http://controller.domain.tld:1234/my/path/nodes/v2", "/nodes/v2"); + assertEquals(URI.create("http://controller.domain.tld:1234/my/path/"), request.getControllerPrefixUri()); + assertEquals(URI.create("https://cfg.prod.us-north-1.domain.tld/nodes/v2"), + request.createConfigServerRequestUri(URI.create("https://cfg.prod.us-north-1.domain.tld"))); + } + + { + // API path test with query + ProxyRequest request = testRequest("http://controller.domain.tld:1234/my/path/nodes/v2/?some=thing", "/nodes/v2/"); + assertEquals(URI.create("http://controller.domain.tld:1234/my/path/"), request.getControllerPrefixUri()); + assertEquals(URI.create("https://cfg.prod.us-north-1.domain.tld/nodes/v2/?some=thing"), + request.createConfigServerRequestUri(URI.create("https://cfg.prod.us-north-1.domain.tld"))); + } } - private static ProxyRequest testRequest(URI url, String pathPrefix) throws IOException, ProxyException { - return new ProxyRequest(url, headers("controller:49152"), null, "GET", pathPrefix); + private static ProxyRequest testRequest(String url, String pathPrefix) throws ProxyException { + return new ProxyRequest( + HttpRequest.Method.GET, URI.create(url), Map.of(), null, ZoneId.from("dev", "us-north-1"), pathPrefix); } - - private static Map<String, List<String>> headers(String hostPort) { - Map<String, List<String>> headers = new HashMap<>(); - headers.put("host", Collections.singletonList(hostPort)); - return Collections.unmodifiableMap(headers); - } - } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java index 8dbd1c4ef61..efe7e17c58e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java @@ -1,15 +1,14 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.proxy; +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.jdisc.http.HttpRequest; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.net.URI; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; +import java.nio.charset.StandardCharsets; import java.util.Map; -import java.util.Optional; import static org.junit.Assert.assertEquals; @@ -20,50 +19,37 @@ public class ProxyResponseTest { @Test public void testRewriteUrl() throws Exception { - String controllerPrefix = "/zone/v2/"; - URI configServer = URI.create("http://configserver:1234"); - ProxyRequest request = new ProxyRequest(URI.create("http://foo/zone/v2/env/region/configserver"), - headers("controller:49152"), null, "GET", - controllerPrefix); + ProxyRequest request = new ProxyRequest(HttpRequest.Method.GET, URI.create("http://domain.tld/zone/v2/dev/us-north-1/configserver"), + Map.of(), null, ZoneId.from("dev", "us-north-1"), "configserver"); ProxyResponse proxyResponse = new ProxyResponse( request, "response link is http://configserver:1234/bla/bla/", 200, - Optional.of(configServer), + URI.create("http://configserver:1234"), "application/json"); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); proxyResponse.render(outputStream); - String document = new String(outputStream.toByteArray(),"UTF-8"); - assertEquals("response link is http://controller:49152/zone/v2/env/region/bla/bla/", document); + String document = new String(outputStream.toByteArray(), StandardCharsets.UTF_8); + assertEquals("response link is http://domain.tld/zone/v2/dev/us-north-1/bla/bla/", document); } @Test public void testRewriteSecureUrl() throws Exception { - String controllerPrefix = "/zone/v2/"; - URI configServer = URI.create("http://configserver:1234"); - ProxyRequest request = new ProxyRequest(URI.create("https://foo/zone/v2/env/region/configserver"), - headers("controller:49152"), null, "GET", - controllerPrefix); + ProxyRequest request = new ProxyRequest(HttpRequest.Method.GET, URI.create("https://domain.tld/zone/v2/prod/eu-south-3/configserver"), + Map.of(), null, ZoneId.from("prod", "eu-south-3"), "configserver"); ProxyResponse proxyResponse = new ProxyResponse( request, "response link is http://configserver:1234/bla/bla/", 200, - Optional.of(configServer), + URI.create("http://configserver:1234"), "application/json"); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); proxyResponse.render(outputStream); - String document = new String(outputStream.toByteArray(),"UTF-8"); - assertEquals("response link is https://controller:49152/zone/v2/env/region/bla/bla/", document); + String document = new String(outputStream.toByteArray(), StandardCharsets.UTF_8); + assertEquals("response link is https://domain.tld/zone/v2/prod/eu-south-3/bla/bla/", document); } - - private static Map<String, List<String>> headers(String hostPort) { - Map<String, List<String>> headers = new HashMap<>(); - headers.put("host", Collections.singletonList(hostPort)); - return Collections.unmodifiableMap(headers); - } - } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java index 19061b61431..33ea538e9b6 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java @@ -6,12 +6,14 @@ import com.yahoo.application.container.handler.Request.Method; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.zone.ZoneApi; +import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.text.Utf8; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.hosted.controller.integration.ConfigServerProxyMock; import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock; import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock; +import com.yahoo.vespa.hosted.controller.proxy.ProxyRequest; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; import org.junit.Before; @@ -59,53 +61,33 @@ public class ZoneApiTest extends ControllerContainerTest { // GET /zone/v2/prod/us-north-1 tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v2/prod/us-north-1"), "ok"); - assertEquals("prod", proxy.lastReceived().get().getEnvironment()); - assertEquals("us-north-1", proxy.lastReceived().get().getRegion()); - assertEquals("", proxy.lastReceived().get().getConfigServerRequest()); - assertEquals("GET", proxy.lastReceived().get().getMethod()); + assertLastRequest(ZoneId.from("prod", "us-north-1"), "GET"); // GET /zone/v2/nodes/v2/node/?recursive=true tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/?recursive=true"), "ok"); - - assertEquals("prod", proxy.lastReceived().get().getEnvironment()); - assertEquals("us-north-1", proxy.lastReceived().get().getRegion()); - assertEquals("/nodes/v2/node/?recursive=true", proxy.lastReceived().get().getConfigServerRequest()); - assertEquals("GET", proxy.lastReceived().get().getMethod()); + assertLastRequest(ZoneId.from("prod", "us-north-1"), "GET"); // POST /zone/v2/dev/us-north-2/nodes/v2/command/restart?hostname=node1 tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/dev/us-north-2/nodes/v2/command/restart?hostname=node1", new byte[0], Method.POST), "ok"); - assertEquals("dev", proxy.lastReceived().get().getEnvironment()); - assertEquals("us-north-2", proxy.lastReceived().get().getRegion()); - assertEquals("/nodes/v2/command/restart?hostname=node1", proxy.lastReceived().get().getConfigServerRequest()); - assertEquals("POST", proxy.lastReceived().get().getMethod()); // PUT /zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1 tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1", new byte[0], Method.PUT), "ok"); - assertEquals("prod", proxy.lastReceived().get().getEnvironment()); - assertEquals("us-north-1", proxy.lastReceived().get().getRegion()); - assertEquals("/nodes/v2/state/dirty/node1", proxy.lastReceived().get().getConfigServerRequest()); - assertEquals("PUT", proxy.lastReceived().get().getMethod()); + assertLastRequest(ZoneId.from("prod", "us-north-1"), "PUT"); // DELETE /zone/v2/prod/us-north-1/nodes/v2/node/node1 tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1", new byte[0], Method.DELETE), "ok"); - assertEquals("prod", proxy.lastReceived().get().getEnvironment()); - assertEquals("us-north-1", proxy.lastReceived().get().getRegion()); - assertEquals("/nodes/v2/node/node1", proxy.lastReceived().get().getConfigServerRequest()); - assertEquals("DELETE", proxy.lastReceived().get().getMethod()); + assertLastRequest(ZoneId.from("prod", "us-north-1"), "DELETE"); // PATCH /zone/v2/prod/us-north-1/nodes/v2/node/node1 tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1", Utf8.toBytes("{\"currentRestartGeneration\": 1}"), Method.PATCH), "ok"); - assertEquals("prod", proxy.lastReceived().get().getEnvironment()); - assertEquals("us-north-1", proxy.lastReceived().get().getRegion()); - assertEquals("/nodes/v2/node/node1", proxy.lastReceived().get().getConfigServerRequest()); - assertEquals("PATCH", proxy.lastReceived().get().getMethod()); + assertLastRequest(ZoneId.from("prod", "us-north-1"), "PATCH"); assertEquals("{\"currentRestartGeneration\": 1}", proxy.lastRequestBody().get()); assertFalse("Actions are logged to audit log", tester.controller().auditLogger().readLog().entries().isEmpty()); @@ -120,6 +102,12 @@ public class ZoneApiTest extends ControllerContainerTest { assertFalse(proxy.lastReceived().isPresent()); } + private void assertLastRequest(ZoneId zoneId, String method) { + ProxyRequest last = proxy.lastReceived().orElseThrow(); + assertEquals(zoneId, last.getZoneId()); + assertEquals(com.yahoo.jdisc.http.HttpRequest.Method.valueOf(method), last.getMethod()); + } + private static Request hostedOperatorRequest(String uri, byte[] body, Request.Method method) { return addIdentityToRequest(new Request(uri, body, method), HOSTED_VESPA_OPERATOR); } |