diff options
author | Andreas Eriksen <andreer@verizonmedia.com> | 2019-06-27 10:25:51 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-27 10:25:51 +0200 |
commit | 70a894e986198368909f4d3ad39678224df1567c (patch) | |
tree | b07993bf21612f94170c216b6fbe13a4cc96def3 | |
parent | 835688ddc6f02b256dfe12a31b57d761ce66c234 (diff) | |
parent | 6b330dbe13745eed26c60cab48b6121527266f7c (diff) |
Merge pull request #9885 from vespa-engine/andreer/retry-deploy-on-missing-certificate
retry deployment on missing certificate
7 files changed, 143 insertions, 1 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidator.java new file mode 100644 index 00000000000..1018099cf05 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidator.java @@ -0,0 +1,17 @@ +package com.yahoo.vespa.model.application.validation; + +import com.yahoo.config.model.api.TlsSecrets; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.provision.CertificateNotReadyException; +import com.yahoo.vespa.model.VespaModel; + +public class TlsSecretsValidator extends Validator { + + /** This check is delayed until validation to allow node provisioning to complete while we are waiting for cert */ + @Override + public void validate(VespaModel model, DeployState deployState) { + if (deployState.tlsSecrets().isPresent() && deployState.tlsSecrets().get() == TlsSecrets.MISSING) { + throw new CertificateNotReadyException("TLS enabled, but could not retrieve certificate yet"); + } + } +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java index e44acf61466..042c7cc867c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java @@ -56,6 +56,7 @@ public class Validation { new DeploymentFileValidator().validate(model, deployState); new RankingConstantsValidator().validate(model, deployState); new SecretStoreValidator().validate(model, deployState); + new TlsSecretsValidator().validate(model, deployState); List<ConfigChangeAction> result = Collections.emptyList(); if (deployState.getProperties().isFirstTimeDeployment()) { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidatorTest.java new file mode 100644 index 00000000000..cdb4ce955e2 --- /dev/null +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidatorTest.java @@ -0,0 +1,88 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.application.validation; + +import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.config.model.NullConfigModelRegistry; +import com.yahoo.config.model.api.TlsSecrets; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.model.deploy.TestProperties; +import com.yahoo.config.model.test.MockApplicationPackage; +import com.yahoo.config.provision.CertificateNotReadyException; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.Zone; +import com.yahoo.vespa.model.VespaModel; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.Optional; + +import static com.yahoo.config.model.test.TestUtil.joinLines; +import static org.junit.Assert.assertTrue; + +/** + * @author andreer + */ +public class TlsSecretsValidatorTest { + @Rule + public final ExpectedException exceptionRule = ExpectedException.none(); + + private static String servicesXml() { + return joinLines("<services version='1.0'>", + " <container id='default' version='1.0'>", + " </container>", + "</services>"); + } + + private static String deploymentXml() { + return joinLines("<deployment version='1.0' >", + " <prod />", + "</deployment>"); + } + + @Test + public void missing_certificate_fails_validation() throws Exception { + DeployState deployState = deployState(servicesXml(), deploymentXml(), Optional.of(TlsSecrets.MISSING)); + VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState); + + exceptionRule.expect(CertificateNotReadyException.class); + exceptionRule.expectMessage("TLS enabled, but could not retrieve certificate yet"); + + new TlsSecretsValidator().validate(model, deployState); + } + + @Test + public void validation_succeeds_with_certificate() throws Exception { + DeployState deployState = deployState(servicesXml(), deploymentXml(), Optional.of(new TlsSecrets("cert", "key"))); + VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState); + + new TlsSecretsValidator().validate(model, deployState); + } + + @Test + public void validation_succeeds_without_certificate() throws Exception { + DeployState deployState = deployState(servicesXml(), deploymentXml(), Optional.empty()); + VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState); + + new TlsSecretsValidator().validate(model, deployState); + } + + private static DeployState deployState(String servicesXml, String deploymentXml, Optional<TlsSecrets> tlsSecrets) { + ApplicationPackage app = new MockApplicationPackage.Builder() + .withServices(servicesXml) + .withDeploymentSpec(deploymentXml) + .build(); + DeployState.Builder builder = new DeployState.Builder() + .applicationPackage(app) + .zone(new Zone(Environment.prod, RegionName.from("foo"))) + .properties( + new TestProperties() + .setHostedVespa(true) + .setTlsSecrets(tlsSecrets)); + final DeployState deployState = builder.build(); + + assertTrue("Test must emulate a hosted deployment.", deployState.isHosted()); + return deployState; + } +} diff --git a/config-provisioning/abi-spec.json b/config-provisioning/abi-spec.json index ffeb5dce112..a9b45fe6bb4 100644 --- a/config-provisioning/abi-spec.json +++ b/config-provisioning/abi-spec.json @@ -149,6 +149,17 @@ ], "fields": [] }, + "com.yahoo.config.provision.CertificateNotReadyException": { + "superClass": "com.yahoo.config.provision.TransientException", + "interfaces": [], + "attributes": [ + "public" + ], + "methods": [ + "public void <init>(java.lang.String)" + ], + "fields": [] + }, "com.yahoo.config.provision.CloudName": { "superClass": "java.lang.Object", "interfaces": [ diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/CertificateNotReadyException.java b/config-provisioning/src/main/java/com/yahoo/config/provision/CertificateNotReadyException.java new file mode 100644 index 00000000000..0d88a7aa435 --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/CertificateNotReadyException.java @@ -0,0 +1,17 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision; + +/** + * Exception thrown when trying to validate an application which is configured + * with a certificate that is not yet retrievable + * + * @author andreer + * + */ +public class CertificateNotReadyException extends TransientException { + + public CertificateNotReadyException(String message) { + super(message); + } + +} 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 a29891ae764..3d2ecd4a2ca 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 @@ -48,7 +48,8 @@ public class HttpErrorResponse extends HttpResponse { OUT_OF_CAPACITY, REQUEST_TIMEOUT, UNKNOWN_VESPA_VERSION, - PARENT_HOST_NOT_READY + PARENT_HOST_NOT_READY, + CERTIFICATE_NOT_READY } public static HttpErrorResponse notFoundError(String msg) { @@ -95,6 +96,10 @@ public class HttpErrorResponse extends HttpResponse { return new HttpErrorResponse(CONFLICT, errorCodes.PARENT_HOST_NOT_READY.name(), msg); } + public static HttpErrorResponse certificateNotReady(String msg) { + return new HttpErrorResponse(CONFLICT, errorCodes.CERTIFICATE_NOT_READY.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 cd2052653ed..20ee77be9fe 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 @@ -2,6 +2,7 @@ package com.yahoo.vespa.config.server.http; import com.yahoo.config.provision.ApplicationLockException; +import com.yahoo.config.provision.CertificateNotReadyException; import com.yahoo.config.provision.ParentHostUnavailableException; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; @@ -64,6 +65,8 @@ public class HttpHandler extends LoggingRequestHandler { return HttpErrorResponse.applicationLockFailure(getMessage(e, request)); } catch (ParentHostUnavailableException e) { return HttpErrorResponse.parentHostNotReady(getMessage(e, request)); + } catch (CertificateNotReadyException e) { + return HttpErrorResponse.certificateNotReady(getMessage(e, request)); } catch (Exception e) { log.log(LogLevel.WARNING, "Unexpected exception handling a config server request", e); return HttpErrorResponse.internalServerError(getMessage(e, request)); |