aboutsummaryrefslogtreecommitdiffstats
path: root/configserver
diff options
context:
space:
mode:
Diffstat (limited to 'configserver')
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java20
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java116
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java100
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java143
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java174
5 files changed, 382 insertions, 171 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
index 02ca4ce14c4..80194337daa 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
@@ -111,6 +111,8 @@ import java.util.stream.Collectors;
import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER;
import static com.yahoo.config.model.api.container.ContainerServiceType.LOGSERVER_CONTAINER;
+import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceResponse;
+import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceListResponse;
import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.fileReferenceExistsOnDisk;
import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.getFileReferencesOnDisk;
import static com.yahoo.vespa.config.server.tenant.TenantRepository.HOSTED_VESPA_TENANT;
@@ -737,16 +739,22 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
// ---------------- Convergence ----------------------------------------------------------------
- public HttpResponse checkServiceForConfigConvergence(ApplicationId applicationId, String hostAndPort, URI uri,
- Duration timeout, Optional<Version> vespaVersion) {
- return convergeChecker.getServiceConfigGenerationResponse(getApplication(applicationId, vespaVersion), hostAndPort, uri, timeout);
+ public ServiceResponse checkServiceForConfigConvergence(ApplicationId applicationId,
+ String hostAndPort,
+ Duration timeout,
+ Optional<Version> vespaVersion) {
+ return convergeChecker.getServiceConfigGeneration(getApplication(applicationId, vespaVersion), hostAndPort, timeout);
}
- public HttpResponse servicesToCheckForConfigConvergence(ApplicationId applicationId, URI uri,
- Duration timeoutPerService, Optional<Version> vespaVersion) {
- return convergeChecker.getServiceConfigGenerationsResponse(getApplication(applicationId, vespaVersion), uri, timeoutPerService);
+ public ServiceListResponse servicesToCheckForConfigConvergence(ApplicationId applicationId,
+ URI uri,
+ Duration timeoutPerService,
+ Optional<Version> vespaVersion) {
+ return convergeChecker.getServiceConfigGenerations(getApplication(applicationId, vespaVersion), uri, timeoutPerService);
}
+ public ConfigConvergenceChecker configConvergenceChecker() { return convergeChecker; }
+
// ---------------- Logs ----------------------------------------------------------------
public HttpResponse getLogs(ApplicationId applicationId, Optional<String> hostname, String apiParams) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java
index 24744a1b3b2..ad14cf4aab6 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java
@@ -10,8 +10,6 @@ import com.yahoo.concurrent.DaemonThreadFactory;
import com.yahoo.config.model.api.HostInfo;
import com.yahoo.config.model.api.PortInfo;
import com.yahoo.config.model.api.ServiceInfo;
-import com.yahoo.slime.Cursor;
-import com.yahoo.vespa.config.server.http.JSONResponse;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
@@ -92,27 +90,26 @@ public class ConfigConvergenceChecker extends AbstractComponent {
}
/** Check all services in given application. Returns the minimum current generation of all services */
- public JSONResponse getServiceConfigGenerationsResponse(Application application, URI requestUrl, Duration timeoutPerService) {
+ public ServiceListResponse getServiceConfigGenerations(Application application, URI uri, Duration timeoutPerService) {
Map<ServiceInfo, Long> currentGenerations = getServiceConfigGenerations(application, timeoutPerService);
long currentGeneration = currentGenerations.values().stream().mapToLong(Long::longValue).min().orElse(-1);
- return new ServiceListResponse(200, currentGenerations, requestUrl, application.getApplicationGeneration(),
- currentGeneration);
+ return new ServiceListResponse(currentGenerations, uri, application.getApplicationGeneration(), currentGeneration);
}
/** Check service identified by host and port in given application */
- public JSONResponse getServiceConfigGenerationResponse(Application application, String hostAndPortToCheck, URI requestUrl, Duration timeout) {
+ public ServiceResponse getServiceConfigGeneration(Application application, String hostAndPortToCheck, Duration timeout) {
Long wantedGeneration = application.getApplicationGeneration();
try (CloseableHttpAsyncClient client = createHttpClient()) {
client.start();
if ( ! hostInApplication(application, hostAndPortToCheck))
- return ServiceResponse.createHostNotFoundInAppResponse(requestUrl, hostAndPortToCheck, wantedGeneration);
+ return new ServiceResponse(ServiceResponse.Status.hostNotFound, wantedGeneration);
long currentGeneration = getServiceGeneration(client, URI.create("http://" + hostAndPortToCheck), timeout).get();
boolean converged = currentGeneration >= wantedGeneration;
- return ServiceResponse.createOkResponse(requestUrl, hostAndPortToCheck, wantedGeneration, currentGeneration, converged);
+ return new ServiceResponse(ServiceResponse.Status.ok, wantedGeneration, currentGeneration, converged);
} catch (InterruptedException | ExecutionException | CancellationException e) { // e.g. if we cannot connect to the service to find generation
- return ServiceResponse.createNotFoundResponse(requestUrl, hostAndPortToCheck, wantedGeneration, e.getMessage());
+ return new ServiceResponse(ServiceResponse.Status.notFound, wantedGeneration, e.getMessage());
} catch (Exception e) {
- return ServiceResponse.createErrorResponse(requestUrl, hostAndPortToCheck, wantedGeneration, e.getMessage());
+ return new ServiceResponse(ServiceResponse.Status.error, wantedGeneration, e.getMessage());
}
}
@@ -192,7 +189,7 @@ public class ConfigConvergenceChecker extends AbstractComponent {
return false;
}
- private static Optional<Integer> getStatePort(ServiceInfo service) {
+ public static Optional<Integer> getStatePort(ServiceInfo service) {
return service.getPorts().stream()
.filter(port -> port.getTags().contains("state"))
.map(PortInfo::getPort)
@@ -249,63 +246,70 @@ public class ConfigConvergenceChecker extends AbstractComponent {
.build();
}
- private static class ServiceListResponse extends JSONResponse {
-
- // Pre-condition: servicesToCheck has a state port
- private ServiceListResponse(int status, Map<ServiceInfo, Long> servicesToCheck, URI uri, long wantedGeneration,
- long currentGeneration) {
- super(status);
- Cursor serviceArray = object.setArray("services");
- servicesToCheck.forEach((service, generation) -> {
- Cursor serviceObject = serviceArray.addObject();
- String hostName = service.getHostName();
- int statePort = getStatePort(service).get();
- serviceObject.setString("host", hostName);
- serviceObject.setLong("port", statePort);
- serviceObject.setString("type", service.getServiceType());
- serviceObject.setString("url", uri.toString() + "/" + hostName + ":" + statePort);
- serviceObject.setLong("currentGeneration", generation);
- });
- object.setString("url", uri.toString());
- object.setLong("currentGeneration", currentGeneration);
- object.setLong("wantedGeneration", wantedGeneration);
- object.setBool("converged", currentGeneration >= wantedGeneration);
+ public static class ServiceResponse {
+
+ public enum Status { ok, notFound, hostNotFound, error }
+
+ public final Status status;
+ public final Long wantedGeneration;
+ public final Long currentGeneration;
+ public final boolean converged;
+ public final Optional<String> errorMessage;
+
+ public ServiceResponse(Status status, Long wantedGeneration) {
+ this(status, wantedGeneration, 0L);
}
- }
- private static class ServiceResponse extends JSONResponse {
+ public ServiceResponse(Status status, Long wantedGeneration, Long currentGeneration) {
+ this(status, wantedGeneration, currentGeneration, false);
+ }
- private ServiceResponse(int status, URI uri, String hostname, Long wantedGeneration) {
- super(status);
- object.setString("url", uri.toString());
- object.setString("host", hostname);
- object.setLong("wantedGeneration", wantedGeneration);
+ public ServiceResponse(Status status, Long wantedGeneration, Long currentGeneration, boolean converged) {
+ this(status, wantedGeneration, currentGeneration, converged, Optional.empty());
}
- static ServiceResponse createOkResponse(URI uri, String hostname, Long wantedGeneration, Long currentGeneration, boolean converged) {
- ServiceResponse serviceResponse = new ServiceResponse(200, uri, hostname, wantedGeneration);
- serviceResponse.object.setBool("converged", converged);
- serviceResponse.object.setLong("currentGeneration", currentGeneration);
- return serviceResponse;
+ public ServiceResponse(Status status, Long wantedGeneration, String errorMessage) {
+ this(status, wantedGeneration, 0L, false, Optional.ofNullable(errorMessage));
}
- static ServiceResponse createHostNotFoundInAppResponse(URI uri, String hostname, Long wantedGeneration) {
- ServiceResponse serviceResponse = new ServiceResponse(410, uri, hostname, wantedGeneration);
- serviceResponse.object.setString("problem", "Host:port (service) no longer part of application, refetch list of services.");
- return serviceResponse;
+ private ServiceResponse(Status status, Long wantedGeneration, Long currentGeneration, boolean converged, Optional<String> errorMessage) {
+ this.status = status;
+ this.wantedGeneration = wantedGeneration;
+ this.currentGeneration = currentGeneration;
+ this.converged = converged;
+ this.errorMessage = errorMessage;
}
- static ServiceResponse createErrorResponse(URI uri, String hostname, Long wantedGeneration, String error) {
- ServiceResponse serviceResponse = new ServiceResponse(500, uri, hostname, wantedGeneration);
- serviceResponse.object.setString("error", error);
- return serviceResponse;
+ }
+
+ public static class ServiceListResponse {
+
+ public final List<Service> services = new ArrayList<>();
+ public final URI uri;
+ public final long wantedGeneration;
+ public final long currentGeneration;
+
+ public ServiceListResponse(Map<ServiceInfo, Long> services, URI uri, long wantedGeneration, long currentGeneration) {
+ services.forEach((key, value) -> this.services.add(new Service(key, value)));
+ this.uri = uri;
+ this.wantedGeneration = wantedGeneration;
+ this.currentGeneration = currentGeneration;
}
- static ServiceResponse createNotFoundResponse(URI uri, String hostname, Long wantedGeneration, String error) {
- ServiceResponse serviceResponse = new ServiceResponse(404, uri, hostname, wantedGeneration);
- serviceResponse.object.setString("error", error);
- return serviceResponse;
+ public List<Service> services() { return services; }
+
+ public static class Service {
+
+ public final ServiceInfo serviceInfo;
+ public final Long currentGeneration;
+
+ public Service(ServiceInfo serviceInfo, Long currentGeneration) {
+ this.serviceInfo = serviceInfo;
+ this.currentGeneration = currentGeneration;
+ }
+
}
+
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
index 4dda141491c..0131517818d 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
@@ -5,6 +5,7 @@ import com.google.inject.Inject;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.model.api.Model;
+import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostFilter;
import com.yahoo.config.provision.InstanceName;
@@ -16,13 +17,15 @@ import com.yahoo.jdisc.Response;
import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
+import com.yahoo.slime.Cursor;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.text.StringUtilities;
import com.yahoo.vespa.config.server.ApplicationRepository;
-import com.yahoo.vespa.config.server.application.ApplicationReindexing;
+import com.yahoo.vespa.config.server.application.ConfigConvergenceChecker;
import com.yahoo.vespa.config.server.http.ContentHandler;
import com.yahoo.vespa.config.server.http.ContentRequest;
import com.yahoo.vespa.config.server.http.HttpHandler;
+import com.yahoo.vespa.config.server.http.JSONResponse;
import com.yahoo.vespa.config.server.http.NotFoundException;
import com.yahoo.vespa.config.server.http.v2.request.ApplicationContentRequest;
import com.yahoo.vespa.config.server.http.v2.response.ApplicationSuspendedResponse;
@@ -33,6 +36,7 @@ import com.yahoo.vespa.config.server.http.v2.response.ReindexingResponse;
import com.yahoo.vespa.config.server.tenant.Tenant;
import java.io.IOException;
+import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
@@ -43,8 +47,11 @@ import java.util.StringJoiner;
import java.util.TreeMap;
import java.util.TreeSet;
+import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceListResponse;
+import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceResponse;
import static com.yahoo.yolean.Exceptions.uncheck;
+
/**
* Operations on applications (delete, wait for config convergence, restart, application content etc.)
*
@@ -108,13 +115,21 @@ public class ApplicationHandler extends HttpHandler {
}
private HttpResponse listServiceConverge(ApplicationId applicationId, HttpRequest request) {
- return applicationRepository.servicesToCheckForConfigConvergence(applicationId, request.getUri(),
- getTimeoutFromRequest(request), getVespaVersionFromRequest(request));
+ ServiceListResponse response =
+ applicationRepository.servicesToCheckForConfigConvergence(applicationId,
+ request.getUri(),
+ getTimeoutFromRequest(request),
+ getVespaVersionFromRequest(request));
+ return new HttpServiceListResponse(response);
}
private HttpResponse checkServiceConverge(ApplicationId applicationId, String hostAndPort, HttpRequest request) {
- return applicationRepository.checkServiceForConfigConvergence(applicationId, hostAndPort, request.getUri(),
- getTimeoutFromRequest(request), getVespaVersionFromRequest(request));
+ ServiceResponse response =
+ applicationRepository.checkServiceForConfigConvergence(applicationId,
+ hostAndPort,
+ getTimeoutFromRequest(request),
+ getVespaVersionFromRequest(request));
+ return HttpServiceResponse.createResponse(response, hostAndPort, request.getUri());
}
private HttpResponse serviceStatusPage(ApplicationId applicationId, String service, String hostname, String pathSuffix) {
@@ -301,4 +316,79 @@ public class ApplicationHandler extends HttpHandler {
.map(Version::fromString);
}
+ static class HttpServiceResponse extends JSONResponse {
+
+ public static HttpServiceResponse createResponse(ConfigConvergenceChecker.ServiceResponse serviceResponse, String hostAndPort, URI uri) {
+ switch (serviceResponse.status) {
+ case ok:
+ return createOkResponse(uri, hostAndPort, serviceResponse.wantedGeneration, serviceResponse.currentGeneration, serviceResponse.converged);
+ case hostNotFound:
+ return createHostNotFoundInAppResponse(uri, hostAndPort, serviceResponse.wantedGeneration);
+ case notFound:
+ return createNotFoundResponse(uri, hostAndPort, serviceResponse.wantedGeneration, serviceResponse.errorMessage.orElse(""));
+ case error:
+ return createErrorResponse(uri, hostAndPort, serviceResponse.wantedGeneration, serviceResponse.errorMessage.orElse(""));
+ default:
+ throw new IllegalArgumentException("Unknown status " + serviceResponse.status);
+ }
+ }
+
+ private HttpServiceResponse(int status, URI uri, String hostname, Long wantedGeneration) {
+ super(status);
+ object.setString("url", uri.toString());
+ object.setString("host", hostname);
+ object.setLong("wantedGeneration", wantedGeneration);
+ }
+
+ private static HttpServiceResponse createOkResponse(URI uri, String hostname, Long wantedGeneration, Long currentGeneration, boolean converged) {
+ HttpServiceResponse serviceResponse = new HttpServiceResponse(200, uri, hostname, wantedGeneration);
+ serviceResponse.object.setBool("converged", converged);
+ serviceResponse.object.setLong("currentGeneration", currentGeneration);
+ return serviceResponse;
+ }
+
+ private static HttpServiceResponse createHostNotFoundInAppResponse(URI uri, String hostname, Long wantedGeneration) {
+ HttpServiceResponse serviceResponse = new HttpServiceResponse(410, uri, hostname, wantedGeneration);
+ serviceResponse.object.setString("problem", "Host:port (service) no longer part of application, refetch list of services.");
+ return serviceResponse;
+ }
+
+ private static HttpServiceResponse createErrorResponse(URI uri, String hostname, Long wantedGeneration, String error) {
+ HttpServiceResponse serviceResponse = new HttpServiceResponse(500, uri, hostname, wantedGeneration);
+ serviceResponse.object.setString("error", error);
+ return serviceResponse;
+ }
+
+ private static HttpServiceResponse createNotFoundResponse(URI uri, String hostname, Long wantedGeneration, String error) {
+ HttpServiceResponse serviceResponse = new HttpServiceResponse(404, uri, hostname, wantedGeneration);
+ serviceResponse.object.setString("error", error);
+ return serviceResponse;
+ }
+
+ }
+
+ static class HttpServiceListResponse extends JSONResponse {
+
+ // Pre-condition: servicesToCheck has a state port
+ public HttpServiceListResponse(ConfigConvergenceChecker.ServiceListResponse response) {
+ super(200);
+ Cursor serviceArray = object.setArray("services");
+ response.services().forEach((service) -> {
+ ServiceInfo serviceInfo = service.serviceInfo;
+ Cursor serviceObject = serviceArray.addObject();
+ String hostName = serviceInfo.getHostName();
+ int statePort = ConfigConvergenceChecker.getStatePort(serviceInfo).get();
+ serviceObject.setString("host", hostName);
+ serviceObject.setLong("port", statePort);
+ serviceObject.setString("type", serviceInfo.getServiceType());
+ serviceObject.setString("url", response.uri.toString() + "/" + hostName + ":" + statePort);
+ serviceObject.setLong("currentGeneration", service.currentGeneration);
+ });
+ object.setString("url", response.uri.toString());
+ object.setLong("currentGeneration", response.currentGeneration);
+ object.setLong("wantedGeneration", response.wantedGeneration);
+ object.setBool("converged", response.currentGeneration >= response.wantedGeneration);
+ }
+ }
+
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java
index 8b21e9c3916..6afb9ef086d 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java
@@ -8,7 +8,6 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.vespa.config.server.ServerCache;
import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
import org.junit.Before;
@@ -16,22 +15,20 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
import java.net.URI;
import java.time.Duration;
import java.util.Arrays;
-import java.util.function.Consumer;
+import java.util.List;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
-import static com.yahoo.test.json.JsonTestHelper.assertJsonEquals;
-import static org.assertj.core.api.Assertions.assertThat;
+import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceListResponse;
+import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceResponse;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* @author Ulf Lilleengen
@@ -72,63 +69,35 @@ public class ConfigConvergenceCheckerTest {
@Test
public void service_convergence() {
{ // Known service
- String serviceName = hostAndPort(this.service);
- URI requestUrl = testServer().resolve("/serviceconverge/" + serviceName);
wireMock.stubFor(get(urlEqualTo("/state/v1/config")).willReturn(okJson("{\"config\":{\"generation\":3}}")));
- HttpResponse serviceResponse = checker.getServiceConfigGenerationResponse(application, hostAndPort(this.service), requestUrl, clientTimeout);
- assertResponse("{\n" +
- " \"url\": \"" + requestUrl.toString() + "\",\n" +
- " \"host\": \"" + hostAndPort(this.service) + "\",\n" +
- " \"wantedGeneration\": 3,\n" +
- " \"converged\": true,\n" +
- " \"currentGeneration\": 3\n" +
- "}",
- 200,
- serviceResponse);
+
+ ServiceResponse response = checker.getServiceConfigGeneration(application, hostAndPort(this.service), clientTimeout);
+ assertEquals(3, response.wantedGeneration.longValue());
+ assertEquals(3, response.currentGeneration.longValue());
+ assertTrue(response.converged);
+ assertEquals(ServiceResponse.Status.ok, response.status);
}
{ // Missing service
- String serviceName = "notPresent:1337";
- URI requestUrl = testServer().resolve("/serviceconverge/" + serviceName);
- HttpResponse response = checker.getServiceConfigGenerationResponse(application, "notPresent:1337", requestUrl, clientTimeout);
- assertResponse("{\n" +
- " \"url\": \"" + requestUrl.toString() + "\",\n" +
- " \"host\": \"" + serviceName + "\",\n" +
- " \"wantedGeneration\": 3,\n" +
- " \"problem\": \"Host:port (service) no longer part of application, refetch list of services.\"\n" +
- "}",
- 410,
- response);
+ ServiceResponse response = checker.getServiceConfigGeneration(application, "notPresent:1337", clientTimeout);
+ assertEquals(3, response.wantedGeneration.longValue());
+ assertEquals(ServiceResponse.Status.hostNotFound, response.status);
}
}
@Test
public void service_list_convergence() {
{
- String serviceName = hostAndPort(this.service);
URI requestUrl = testServer().resolve("/serviceconverge");
- URI serviceUrl = testServer().resolve("/serviceconverge/" + serviceName);
wireMock.stubFor(get(urlEqualTo("/state/v1/config")).willReturn(okJson("{\"config\":{\"generation\":3}}")));
- HttpResponse response = checker.getServiceConfigGenerationsResponse(application, requestUrl, clientTimeout);
- assertResponse("{\n" +
- " \"services\": [\n" +
- " {\n" +
- " \"host\": \"" + serviceUrl.getHost() + "\",\n" +
- " \"port\": " + serviceUrl.getPort() + ",\n" +
- " \"type\": \"container\",\n" +
- " \"url\": \"" + serviceUrl.toString() + "\",\n" +
- " \"currentGeneration\":" + 3 + "\n" +
- " }\n" +
- " ],\n" +
- " \"url\": \"" + requestUrl.toString() + "\",\n" +
- " \"currentGeneration\": 3,\n" +
- " \"wantedGeneration\": 3,\n" +
- " \"converged\": true\n" +
- "}",
- 200,
- response);
- }
+ ServiceListResponse response = checker.getServiceConfigGenerations(application, requestUrl, clientTimeout);
+ assertEquals(3, response.wantedGeneration);
+ assertEquals(3, response.currentGeneration);
+ List<ServiceListResponse.Service> services = response.services;
+ assertEquals(1, services.size());
+ assertService(this.service, services.get(0), 3);
+ }
{ // Model with two hosts on different generations
MockModel model = new MockModel(Arrays.asList(
@@ -143,53 +112,29 @@ public class ConfigConvergenceCheckerTest {
wireMock2.stubFor(get(urlEqualTo("/state/v1/config")).willReturn(okJson("{\"config\":{\"generation\":3}}")));
URI requestUrl = testServer().resolve("/serviceconverge");
- URI serviceUrl = testServer().resolve("/serviceconverge/" + hostAndPort(service));
- URI serviceUrl2 = testServer().resolve("/serviceconverge/" + hostAndPort(service2));
- HttpResponse response = checker.getServiceConfigGenerationsResponse(application, requestUrl, clientTimeout);
- assertResponse("{\n" +
- " \"services\": [\n" +
- " {\n" +
- " \"host\": \"" + service.getHost() + "\",\n" +
- " \"port\": " + service.getPort() + ",\n" +
- " \"type\": \"container\",\n" +
- " \"url\": \"" + serviceUrl.toString() + "\",\n" +
- " \"currentGeneration\":" + 4 + "\n" +
- " },\n" +
- " {\n" +
- " \"host\": \"" + service2.getHost() + "\",\n" +
- " \"port\": " + service2.getPort() + ",\n" +
- " \"type\": \"container\",\n" +
- " \"url\": \"" + serviceUrl2.toString() + "\",\n" +
- " \"currentGeneration\":" + 3 + "\n" +
- " }\n" +
- " ],\n" +
- " \"url\": \"" + requestUrl.toString() + "\",\n" +
- " \"currentGeneration\": 3,\n" +
- " \"wantedGeneration\": 4,\n" +
- " \"converged\": false\n" +
- "}",
- 200,
- response);
+
+ ServiceListResponse response = checker.getServiceConfigGenerations(application, requestUrl, clientTimeout);
+ assertEquals(4, response.wantedGeneration);
+ assertEquals(3, response.currentGeneration);
+
+ List<ServiceListResponse.Service> services = response.services;
+ assertEquals(2, services.size());
+ assertService(this.service, services.get(0), 4);
+ assertService(this.service2, services.get(1), 3);
}
}
+
@Test
public void service_convergence_timeout() {
- URI requestUrl = testServer().resolve("/serviceconverge");
wireMock.stubFor(get(urlEqualTo("/state/v1/config")).willReturn(aResponse()
.withFixedDelay((int) clientTimeout.plus(Duration.ofSeconds(1)).toMillis())
.withBody("response too slow")));
- HttpResponse response = checker.getServiceConfigGenerationResponse(application, hostAndPort(service), requestUrl, Duration.ofMillis(1));
- // Message contained in a SocketTimeoutException may differ across platforms, so we do a partial match of the response here
- assertResponse(
- responseBody ->
- assertThat(responseBody)
- .startsWith("{\"url\":\"" + requestUrl.toString() + "\",\"host\":\"" + hostAndPort(requestUrl) +
- "\",\"wantedGeneration\":3,\"error\":\"")
- .contains("java.net.SocketTimeoutException: 1 MILLISECONDS")
- .endsWith("\"}"),
- 404,
- response);
+ ServiceResponse response = checker.getServiceConfigGeneration(application, hostAndPort(service), Duration.ofMillis(1));
+
+ assertEquals(3, response.wantedGeneration.longValue());
+ assertEquals(ServiceResponse.Status.notFound, response.status);
+ assertTrue(response.errorMessage.get().contains("java.net.SocketTimeoutException: 1 MILLISECONDS"));
}
private URI testServer() {
@@ -204,19 +149,11 @@ public class ConfigConvergenceCheckerTest {
return uri.getHost() + ":" + uri.getPort();
}
- private static void assertResponse(String expectedJson, int status, HttpResponse response) {
- assertResponse((responseBody) -> assertJsonEquals(new String(responseBody.getBytes()), expectedJson), status, response);
- }
-
- private static void assertResponse(Consumer<String> assertFunc, int status, HttpResponse response) {
- ByteArrayOutputStream responseBody = new ByteArrayOutputStream();
- try {
- response.render(responseBody);
- assertFunc.accept(responseBody.toString());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- assertEquals(status, response.getStatus());
+ private void assertService(URI uri, ServiceListResponse.Service service1, long expectedGeneration) {
+ assertEquals(expectedGeneration, service1.currentGeneration.longValue());
+ assertEquals(uri.getHost(), service1.serviceInfo.getHostName());
+ assertEquals(uri.getPort(), ConfigConvergenceChecker.getStatePort(service1.serviceInfo).get().intValue());
+ assertEquals("container", service1.serviceInfo.getServiceType());
}
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
index 0b8bf8e84bd..04483e0191d 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
@@ -4,6 +4,8 @@ package com.yahoo.vespa.config.server.http.v2;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.component.Version;
import com.yahoo.config.model.api.ModelFactory;
+import com.yahoo.config.model.api.PortInfo;
+import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
@@ -50,14 +52,18 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.function.Consumer;
import java.util.stream.Stream;
import static com.yahoo.container.jdisc.HttpRequest.createTestRequest;
@@ -65,8 +71,12 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.DELETE;
import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
import static com.yahoo.jdisc.http.HttpRequest.Method.POST;
import static com.yahoo.test.json.JsonTestHelper.assertJsonEquals;
+import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceListResponse;
+import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceResponse;
import static com.yahoo.vespa.config.server.http.HandlerTest.assertHttpStatusCodeAndMessage;
import static com.yahoo.vespa.config.server.http.SessionHandlerTest.getRenderedString;
+import static com.yahoo.vespa.config.server.http.v2.ApplicationHandler.HttpServiceListResponse;
+import static com.yahoo.vespa.config.server.http.v2.ApplicationHandler.HttpServiceResponse.createResponse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -146,7 +156,7 @@ public class ApplicationHandlerTest {
Tenant mytenant = applicationRepository.getTenant(applicationId);
deleteAndAssertOKResponse(mytenant, applicationId);
}
-
+
{
applicationRepository.deploy(testApp, prepareParams(applicationId));
deleteAndAssertOKResponseMocked(applicationId, true);
@@ -539,6 +549,143 @@ public class ApplicationHandlerTest {
"}\n");
}
+ @Test
+ public void service_convergence() {
+ String hostAndPort = "localhost:1234";
+ URI uri = URI.create("https://" + hostAndPort + "/serviceconvergence/container");
+
+ { // Known service
+ HttpResponse response = createResponse(new ServiceResponse(ServiceResponse.Status.ok,
+ 3L,
+ 3L,
+ true),
+ hostAndPort,
+ uri);
+ assertResponse("{\n" +
+ " \"url\": \"" + uri.toString() + "\",\n" +
+ " \"host\": \"" + hostAndPort + "\",\n" +
+ " \"wantedGeneration\": 3,\n" +
+ " \"converged\": true,\n" +
+ " \"currentGeneration\": 3\n" +
+ "}",
+ 200,
+ response);
+ }
+
+ { // Missing service
+ HttpResponse response = createResponse(new ServiceResponse(ServiceResponse.Status.hostNotFound,
+ 3L),
+ hostAndPort,
+ uri);
+
+ assertResponse("{\n" +
+ " \"url\": \"" + uri.toString() + "\",\n" +
+ " \"host\": \"" + hostAndPort + "\",\n" +
+ " \"wantedGeneration\": 3,\n" +
+ " \"problem\": \"Host:port (service) no longer part of application, refetch list of services.\"\n" +
+ "}",
+ 410,
+ response);
+ }
+ }
+
+ @Test
+ public void service_list_convergence() {
+ URI requestUrl = URI.create("https://configserver/serviceconvergence");
+
+ String hostname = "localhost";
+ int port = 1234;
+ String hostAndPort = hostname + ":" + port;
+ URI serviceUrl = URI.create("https://configserver/serviceconvergence/" + hostAndPort);
+
+ {
+ HttpServiceListResponse response =
+ new HttpServiceListResponse(new ServiceListResponse(Map.of(createServiceInfo(hostname, port), 3L),
+ requestUrl,
+ 3L,
+ 3L));
+ assertResponse("{\n" +
+ " \"services\": [\n" +
+ " {\n" +
+ " \"host\": \"" + hostname + "\",\n" +
+ " \"port\": " + port + ",\n" +
+ " \"type\": \"container\",\n" +
+ " \"url\": \"" + serviceUrl.toString() + "\",\n" +
+ " \"currentGeneration\":" + 3 + "\n" +
+ " }\n" +
+ " ],\n" +
+ " \"url\": \"" + requestUrl.toString() + "\",\n" +
+ " \"currentGeneration\": 3,\n" +
+ " \"wantedGeneration\": 3,\n" +
+ " \"converged\": true\n" +
+ "}",
+ 200,
+ response);
+ }
+
+ { // Two hosts on different generations
+ String hostname2 = "localhost2";
+ int port2 = 5678;
+ String hostAndPort2 = hostname2 + ":" + port2;
+ URI serviceUrl2 = URI.create("https://configserver/serviceconvergence/" + hostAndPort2);
+
+ Map<ServiceInfo, Long> serviceInfos = new HashMap<>();
+ serviceInfos.put(createServiceInfo(hostname, port), 4L);
+ serviceInfos.put(createServiceInfo(hostname2, port2), 3L);
+
+ HttpServiceListResponse response =
+ new HttpServiceListResponse(new ServiceListResponse(serviceInfos,
+ requestUrl,
+ 4L,
+ 3L));
+ assertResponse("{\n" +
+ " \"services\": [\n" +
+ " {\n" +
+ " \"host\": \"" + hostname + "\",\n" +
+ " \"port\": " + port + ",\n" +
+ " \"type\": \"container\",\n" +
+ " \"url\": \"" + serviceUrl.toString() + "\",\n" +
+ " \"currentGeneration\":" + 4 + "\n" +
+ " },\n" +
+ " {\n" +
+ " \"host\": \"" + hostname2 + "\",\n" +
+ " \"port\": " + port2 + ",\n" +
+ " \"type\": \"container\",\n" +
+ " \"url\": \"" + serviceUrl2.toString() + "\",\n" +
+ " \"currentGeneration\":" + 3 + "\n" +
+ " }\n" +
+ " ],\n" +
+ " \"url\": \"" + requestUrl.toString() + "\",\n" +
+ " \"currentGeneration\": 3,\n" +
+ " \"wantedGeneration\": 4,\n" +
+ " \"converged\": false\n" +
+ "}",
+ 200,
+ response);
+ }
+ }
+
+ @Test
+ public void service_convergence_timeout() {
+ String hostAndPort = "localhost:1234";
+ URI uri = URI.create("https://" + hostAndPort + "/serviceconvergence/container");
+
+ HttpResponse response = createResponse(new ServiceResponse(ServiceResponse.Status.notFound,
+ 3L,
+ "some error message"),
+ hostAndPort,
+ uri);
+
+ assertResponse("{\n" +
+ " \"url\": \"" + uri.toString() + "\",\n" +
+ " \"host\": \"" + hostAndPort + "\",\n" +
+ " \"wantedGeneration\": 3,\n" +
+ " \"error\": \"some error message\"" +
+ "}",
+ 404,
+ response);
+ }
+
private void assertNotAllowed(Method method) throws IOException {
String url = "http://myhost:14000/application/v2/tenant/" + mytenantName + "/application/default";
deleteAndAssertResponse(url, Response.Status.METHOD_NOT_ALLOWED, HttpErrorResponse.ErrorCode.METHOD_NOT_ALLOWED, "{\"error-code\":\"METHOD_NOT_ALLOWED\",\"message\":\"Method '" + method + "' is not supported\"}",
@@ -668,4 +815,29 @@ public class ApplicationHandlerTest {
return new PrepareParams.Builder().applicationId(applicationId).build();
}
+ private static void assertResponse(String expectedJson, int status, HttpResponse response) {
+ assertResponse((responseBody) -> assertJsonEquals(new String(responseBody
+ .getBytes()), expectedJson), status, response);
+ }
+
+ private static void assertResponse(Consumer<String> assertFunc, int status, HttpResponse response) {
+ ByteArrayOutputStream responseBody = new ByteArrayOutputStream();
+ try {
+ response.render(responseBody);
+ assertFunc.accept(responseBody.toString());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ assertEquals(status, response.getStatus());
+ }
+
+ private ServiceInfo createServiceInfo(String hostname, int port) {
+ return new ServiceInfo("container",
+ "container",
+ List.of(new PortInfo(port, List.of("state"))),
+ Map.of(),
+ "configId",
+ hostname);
+ }
+
}