diff options
Diffstat (limited to 'configserver/src')
8 files changed, 76 insertions, 18 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java index 8f3bc83984a..09a687657c6 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java @@ -418,8 +418,9 @@ public class TenantApplications implements RequestHandler, HostValidator { /** * Waiter for removing application. Will wait for some time for all servers to remove application, - * but will accept majority of servers to have removed app if it takes a long time. + * but will accept the majority of servers to have removed app if it takes a long time. */ + // TODO: Merge with CuratorCompletionWaiter static class RemoveApplicationWaiter implements CompletionWaiter { private static final java.util.logging.Logger log = Logger.getLogger(RemoveApplicationWaiter.class.getName()); @@ -485,7 +486,7 @@ public class TenantApplications implements RequestHandler, HostValidator { gotQuorumTime = clock.instant(); // Give up if more than some time has passed since we got quorum, otherwise continue - if (Duration.between(Instant.now(), gotQuorumTime.plus(waitForAll)).isNegative()) { + if (Duration.between(clock.instant(), gotQuorumTime.plus(waitForAll)).isNegative()) { logBarrierCompleted(respondents, startTime); break; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpConfigRequest.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpConfigRequest.java index 3baa57b2b01..e043afdbf43 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpConfigRequest.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpConfigRequest.java @@ -30,15 +30,18 @@ import java.util.Set; */ public class HttpConfigRequest implements GetConfigRequest, TenantRequest { - private static final String HTTP_PROPERTY_NOCACHE = "noCache"; + private static final String NOCACHE = "noCache"; + private static final String REQUIRED_GENERATION = "requiredGeneration"; private final ConfigKey<?> key; private final ApplicationId appId; private final boolean noCache; + private final Optional<Long> requiredGeneration; - private HttpConfigRequest(ConfigKey<?> key, ApplicationId appId, boolean noCache) { + private HttpConfigRequest(ConfigKey<?> key, ApplicationId appId, boolean noCache, Optional<Long> requiredGeneration) { this.key = key; this.appId = appId; this.noCache = noCache; + this.requiredGeneration = requiredGeneration; } private static ConfigKey<?> fromRequestV1(HttpRequest req) { @@ -59,7 +62,10 @@ public class HttpConfigRequest implements GetConfigRequest, TenantRequest { } public static HttpConfigRequest createFromRequestV1(HttpRequest req) { - return new HttpConfigRequest(fromRequestV1(req), ApplicationId.defaultId(), req.getBooleanProperty(HTTP_PROPERTY_NOCACHE)); + return new HttpConfigRequest(fromRequestV1(req), + ApplicationId.defaultId(), + req.getBooleanProperty(NOCACHE), + requiredGeneration(req)); } public static HttpConfigRequest createFromRequestV2(HttpRequest req) { @@ -89,7 +95,8 @@ public class HttpConfigRequest implements GetConfigRequest, TenantRequest { cNamespace = nns.second; return new HttpConfigRequest(new ConfigKey<>(cName, cId, cNamespace), new ApplicationId.Builder().applicationName(application).tenant(tenant).build(), - req.getBooleanProperty(HTTP_PROPERTY_NOCACHE)); + req.getBooleanProperty(NOCACHE), + requiredGeneration(req)); } // The URL pattern with full app id given @@ -117,7 +124,10 @@ public class HttpConfigRequest implements GetConfigRequest, TenantRequest { .applicationName(application) .instanceName(instance) .build(); - return new HttpConfigRequest(new ConfigKey<>(cName, cId, cNamespace), appId, req.getBooleanProperty(HTTP_PROPERTY_NOCACHE)); + return new HttpConfigRequest(new ConfigKey<>(cName, cId, cNamespace), + appId, + req.getBooleanProperty(NOCACHE), + requiredGeneration(req)); } /** @@ -144,7 +154,11 @@ public class HttpConfigRequest implements GetConfigRequest, TenantRequest { public static void throwModelNotReady() { throw new NotFoundException("Config not available, verify that an application package has been deployed and activated."); } - + + public static void throwPreconditionFailed(long requiredGeneration) { + throw new PreconditionFailedException("Config for required generation " + requiredGeneration + " could not be found."); + } + /** * If the given config is produced by the model at all * @@ -199,4 +213,11 @@ public class HttpConfigRequest implements GetConfigRequest, TenantRequest { @Override public PayloadChecksums configPayloadChecksums() { return PayloadChecksums.empty(); } + public Optional<Long> requiredGeneration() { return requiredGeneration; } + + static Optional<Long> requiredGeneration(HttpRequest req) { + Optional<String> requiredGeneration = Optional.ofNullable(req.getProperty(REQUIRED_GENERATION)); + return requiredGeneration.map(Long::parseLong); + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java index 40ce16145e7..3b5269cdf11 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java @@ -16,6 +16,7 @@ import static com.yahoo.jdisc.Response.Status.CONFLICT; import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; import static com.yahoo.jdisc.Response.Status.NOT_FOUND; +import static com.yahoo.jdisc.Response.Status.PRECONDITION_FAILED; import static com.yahoo.jdisc.Response.Status.REQUEST_TIMEOUT; /** @@ -51,7 +52,8 @@ public class HttpErrorResponse extends HttpResponse { CERTIFICATE_NOT_READY, LOAD_BALANCER_NOT_READY, CONFIG_NOT_CONVERGED, - REINDEXING_STATUS_UNAVAILABLE + REINDEXING_STATUS_UNAVAILABLE, + PRECONDITION_FAILED } public static HttpErrorResponse notFoundError(String msg) { @@ -114,6 +116,10 @@ public class HttpErrorResponse extends HttpResponse { return new HttpErrorResponse(CONFLICT, ErrorCode.REINDEXING_STATUS_UNAVAILABLE.name(), msg); } + public static HttpResponse preconditionFailed(String msg) { + return new HttpErrorResponse(PRECONDITION_FAILED, ErrorCode.PRECONDITION_FAILED.name(), msg); + } + @Override public void render(OutputStream stream) throws IOException { new JsonFormat(true).encode(stream, slime); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java index dc3a05e65f9..a0e814f32d8 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java @@ -71,6 +71,8 @@ public class HttpHandler extends ThreadedHttpRequestHandler { return HttpErrorResponse.loadBalancerNotReady(getMessage(e, request)); } catch (ReindexingStatusException e) { return HttpErrorResponse.reindexingStatusUnavailable(getMessage(e, request)); + } catch (PreconditionFailedException e) { + return HttpErrorResponse.preconditionFailed(getMessage(e, request)); } catch (Exception e) { log.log(Level.WARNING, "Unexpected exception handling a config server request", e); return HttpErrorResponse.internalServerError(getMessage(e, request)); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/NotFoundException.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/NotFoundException.java index eb008de6ee5..688890c75b2 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/NotFoundException.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/NotFoundException.java @@ -5,7 +5,6 @@ package com.yahoo.vespa.config.server.http; * Exception that will create a http response with NOT_FOUND response code (404) * * @author hmusum - * @since 5.1.17 */ public class NotFoundException extends RuntimeException { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/PreconditionFailedException.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/PreconditionFailedException.java new file mode 100644 index 00000000000..ef3924425c2 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/PreconditionFailedException.java @@ -0,0 +1,16 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.http; + +/** + * Exception that will create a http response with NOT_FOUND response code (404) + * + * @author hmusum + */ +public class PreconditionFailedException extends RuntimeException { + + public PreconditionFailedException(String message) { + super(message); + } + +} + diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandler.java index 3ab3df99a10..0389b2a6c98 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandler.java @@ -4,31 +4,27 @@ package com.yahoo.vespa.config.server.http.v2; import com.yahoo.component.annotation.Inject; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; -import java.util.logging.Level; import com.yahoo.vespa.config.protocol.ConfigResponse; import com.yahoo.vespa.config.server.RequestHandler; -import com.yahoo.vespa.config.server.http.v2.request.HttpConfigRequests; -import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.http.HttpConfigRequest; import com.yahoo.vespa.config.server.http.HttpConfigResponse; import com.yahoo.vespa.config.server.http.HttpHandler; - +import com.yahoo.vespa.config.server.http.v2.request.HttpConfigRequests; +import com.yahoo.vespa.config.server.tenant.TenantRepository; import java.util.Optional; +import java.util.logging.Level; /** * HTTP handler for a getConfig operation * * @author Ulf Lilleengen - * @since 5.1 */ public class HttpGetConfigHandler extends HttpHandler { private final TenantRepository tenantRepository; @Inject - public HttpGetConfigHandler(HttpHandler.Context ctx, - TenantRepository tenantRepository) - { + public HttpGetConfigHandler(HttpHandler.Context ctx, TenantRepository tenantRepository) { super(ctx); this.tenantRepository = tenantRepository; } @@ -45,6 +41,8 @@ public class HttpGetConfigHandler extends HttpHandler { log.log(Level.FINE, () -> "nocache=" + request.noCache()); ConfigResponse config = requestHandler.resolveConfig(request.getApplicationId(), request, Optional.empty()); if (config == null) HttpConfigRequest.throwModelNotReady(); + if (request.requiredGeneration().isPresent() && request.requiredGeneration().get() != config.getGeneration()) + HttpConfigRequest.throwPreconditionFailed(request.requiredGeneration().get()); return config; } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java index a0b5b879e45..401bd1ae55b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java @@ -25,6 +25,7 @@ import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; import java.util.Collections; +import java.util.Map; import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; import static com.yahoo.jdisc.Response.Status.NOT_FOUND; @@ -131,6 +132,20 @@ public class HttpGetConfigHandlerTest { assertTrue(renderedString, renderedString.startsWith(expected)); } + @Test + public void require_that_required_generation_property_works() throws IOException { + HttpRequest request = HttpRequest.createTestRequest(configUri, GET, null, Map.of("requiredGeneration", "2")); + HttpResponse response = handler.handle(request); + String renderedString = SessionHandlerTest.getRenderedString(response); + assertTrue(renderedString, renderedString.startsWith(expected)); + + request = HttpRequest.createTestRequest(configUri, GET, null, Map.of("requiredGeneration", "3")); + response = handler.handle(request); + assertEquals(412, response.getStatus()); + renderedString = SessionHandlerTest.getRenderedString(response); + assertEquals("{\"error-code\":\"PRECONDITION_FAILED\",\"message\":\"Config for required generation 3 could not be found.\"}", renderedString); + } + private PrepareParams prepareParams() { return new PrepareParams.Builder().applicationId(applicationId).build(); } |