From 7ddab351998d8e6d067dbd65dfb66714e8bfdf7a Mon Sep 17 00:00:00 2001 From: Harald Musum Date: Mon, 10 Jan 2022 15:15:16 +0100 Subject: Use an intermediate representation for service convergence WAnt an intermediate representation that can be used e.g. for checking convergence before restarting. This is the first step, should have no changes for clients checking service/config convergence --- .../vespa/config/server/ApplicationRepository.java | 20 ++-- .../application/ConfigConvergenceChecker.java | 116 +++++++++++---------- .../config/server/http/v2/ApplicationHandler.java | 100 +++++++++++++++++- 3 files changed, 169 insertions(+), 67 deletions(-) (limited to 'configserver/src/main/java/com/yahoo') 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 vespaVersion) { - return convergeChecker.getServiceConfigGenerationResponse(getApplication(applicationId, vespaVersion), hostAndPort, uri, timeout); + public ServiceResponse checkServiceForConfigConvergence(ApplicationId applicationId, + String hostAndPort, + Duration timeout, + Optional vespaVersion) { + return convergeChecker.getServiceConfigGeneration(getApplication(applicationId, vespaVersion), hostAndPort, timeout); } - public HttpResponse servicesToCheckForConfigConvergence(ApplicationId applicationId, URI uri, - Duration timeoutPerService, Optional vespaVersion) { - return convergeChecker.getServiceConfigGenerationsResponse(getApplication(applicationId, vespaVersion), uri, timeoutPerService); + public ServiceListResponse servicesToCheckForConfigConvergence(ApplicationId applicationId, + URI uri, + Duration timeoutPerService, + Optional vespaVersion) { + return convergeChecker.getServiceConfigGenerations(getApplication(applicationId, vespaVersion), uri, timeoutPerService); } + public ConfigConvergenceChecker configConvergenceChecker() { return convergeChecker; } + // ---------------- Logs ---------------------------------------------------------------- public HttpResponse getLogs(ApplicationId applicationId, Optional 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 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 getStatePort(ServiceInfo service) { + public static Optional 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 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 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 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 services = new ArrayList<>(); + public final URI uri; + public final long wantedGeneration; + public final long currentGeneration; + + public ServiceListResponse(Map 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 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); + } + } + } -- cgit v1.2.3