aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorValerij Fredriksen <freva@users.noreply.github.com>2019-10-31 14:52:51 +0100
committerGitHub <noreply@github.com>2019-10-31 14:52:51 +0100
commit2caa198f03f94ce82dd3bac879b03a2084a781c7 (patch)
tree45dfe1527bc0e07aa296897f52fa4b0f6fb787b8 /controller-server
parent7799f40156642d588112ce765188583659bf4f42 (diff)
parentd1ec7b663b51ad2013f38d7ee03fbf2e3aa677c3 (diff)
Merge pull request #11172 from vespa-engine/freva/configserver-v1
Simplify ConfigServerRestExecutorImpl
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java146
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java121
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java91
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java40
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java38
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);
}