diff options
author | Harald Musum <musum@yahooinc.com> | 2021-09-28 14:49:56 +0200 |
---|---|---|
committer | Harald Musum <musum@yahooinc.com> | 2021-09-28 14:49:56 +0200 |
commit | b9a11e9c3d78562fc2e6b433fd9196c5255eac8f (patch) | |
tree | 7a456201539969de8ebe2f13aef0c8a0c90c565c | |
parent | 731a1b3aed770e34c2ec1f2941573f5c7ec11b49 (diff) |
Wire in dryRun deployment option
9 files changed, 86 insertions, 30 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java index 1608e72b0b6..1f1f8577a32 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java @@ -1,4 +1,4 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.application.v4.model; import com.yahoo.component.Version; @@ -6,7 +6,6 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.athenz.api.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.integration.aws.TenantRoles; import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint; @@ -37,7 +36,9 @@ public class DeploymentData { private final Quota quota; private final List<TenantSecretStore> tenantSecretStores; private final List<X509Certificate> operatorCertificates; + private final boolean dryRun; + // TODO: Remove when users have been updated to use constructor below public DeploymentData(ApplicationId instance, ZoneId zone, byte[] applicationPackage, Version platform, Set<ContainerEndpoint> containerEndpoints, Optional<EndpointCertificateMetadata> endpointCertificateMetadata, @@ -46,6 +47,19 @@ public class DeploymentData { Quota quota, List<TenantSecretStore> tenantSecretStores, List<X509Certificate> operatorCertificates) { + this(instance, zone, applicationPackage, platform, containerEndpoints, endpointCertificateMetadata, + dockerImageRepo, athenzDomain, quota, tenantSecretStores, operatorCertificates, false); + } + + public DeploymentData(ApplicationId instance, ZoneId zone, byte[] applicationPackage, Version platform, + Set<ContainerEndpoint> containerEndpoints, + Optional<EndpointCertificateMetadata> endpointCertificateMetadata, + Optional<DockerImage> dockerImageRepo, + Optional<AthenzDomain> athenzDomain, + Quota quota, + List<TenantSecretStore> tenantSecretStores, + List<X509Certificate> operatorCertificates, + boolean dryRun) { this.instance = requireNonNull(instance); this.zone = requireNonNull(zone); this.applicationPackage = requireNonNull(applicationPackage); @@ -57,6 +71,7 @@ public class DeploymentData { this.quota = quota; this.tenantSecretStores = tenantSecretStores; this.operatorCertificates = operatorCertificates; + this.dryRun = dryRun; } public ApplicationId instance() { @@ -102,4 +117,7 @@ public class DeploymentData { public List<X509Certificate> operatorCertificates() { return operatorCertificates; } + + public boolean isDryRun() { return dryRun; } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index fe5fc90df60..861d101ae3f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -384,7 +384,8 @@ public class ApplicationController { } // Release application lock while doing the deployment, which is a lengthy task. // Carry out deployment without holding the application lock. - ActivateResult result = deploy(job.application(), applicationPackage, zone, platform, containerEndpoints, endpointCertificateMetadata); + ActivateResult result = deploy(job.application(), applicationPackage, zone, platform, containerEndpoints, + endpointCertificateMetadata, run.isDryRun()); // Record the quota usage for this application var quotaUsage = deploymentQuotaUsage(zone, job.application()); @@ -466,7 +467,7 @@ public class ApplicationController { ApplicationPackage applicationPackage = new ApplicationPackage( artifactRepository.getSystemApplicationPackage(application.id(), zone, version) ); - return deploy(application.id(), applicationPackage, zone, version, Set.of(), /* No application cert */ Optional.empty()); + return deploy(application.id(), applicationPackage, zone, version, Set.of(), /* No application cert */ Optional.empty(), false); } else { throw new RuntimeException("This system application does not have an application package: " + application.id().toShortString()); } @@ -474,12 +475,13 @@ public class ApplicationController { /** Deploys the given tester application to the given zone. */ public ActivateResult deployTester(TesterId tester, ApplicationPackage applicationPackage, ZoneId zone, Version platform) { - return deploy(tester.id(), applicationPackage, zone, platform, Set.of(), /* No application cert for tester*/ Optional.empty()); + return deploy(tester.id(), applicationPackage, zone, platform, Set.of(), /* No application cert for tester*/ Optional.empty(), false); } private ActivateResult deploy(ApplicationId application, ApplicationPackage applicationPackage, ZoneId zone, Version platform, Set<ContainerEndpoint> endpoints, - Optional<EndpointCertificateMetadata> endpointCertificateMetadata) { + Optional<EndpointCertificateMetadata> endpointCertificateMetadata, + boolean dryRun) { try { Optional<DockerImage> dockerImageRepo = Optional.ofNullable( dockerImageRepoFlag @@ -513,7 +515,8 @@ public class ApplicationController { ConfigServer.PreparedApplication preparedApplication = configServer.deploy(new DeploymentData(application, zone, applicationPackage.zippedContent(), platform, endpoints, endpointCertificateMetadata, dockerImageRepo, domain, - deploymentQuota, tenantSecretStores, operatorCertificates)); + deploymentQuota, tenantSecretStores, operatorCertificates, + dryRun)); return new ActivateResult(new RevisionId(applicationPackage.hash()), preparedApplication.prepareResponse(), applicationPackage.zippedContent().length); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java index 88d440b52b9..4076013b2c5 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java @@ -475,24 +475,30 @@ public class JobController { /** Orders a run of the given type, or throws an IllegalStateException if that job type is already running. */ public void start(ApplicationId id, JobType type, Versions versions, boolean isRedeployment) { - start(id, type, versions, isRedeployment, JobProfile.of(type)); + start(id, type, versions, isRedeployment, JobProfile.of(type), false); } /** Orders a run of the given type, or throws an IllegalStateException if that job type is already running. */ - public void start(ApplicationId id, JobType type, Versions versions, boolean isRedeployment, JobProfile profile) { + public void start(ApplicationId id, JobType type, Versions versions, boolean isRedeployment, JobProfile profile, boolean dryRun) { locked(id, type, __ -> { Optional<Run> last = last(id, type); if (last.flatMap(run -> active(run.id())).isPresent()) throw new IllegalStateException("Can not start " + type + " for " + id + "; it is already running!"); RunId newId = new RunId(id, type, last.map(run -> run.id().number()).orElse(0L) + 1); - curator.writeLastRun(Run.initial(newId, versions, isRedeployment, controller.clock().instant(), profile)); + curator.writeLastRun(Run.initial(newId, versions, isRedeployment, controller.clock().instant(), profile, dryRun)); metric.jobStarted(newId.job()); }); } + /** Stores the given package and starts a deployment of it, after aborting any such ongoing deployment. */ public void deploy(ApplicationId id, JobType type, Optional<Version> platform, ApplicationPackage applicationPackage) { + deploy(id, type, platform, applicationPackage, false); + } + + /** Stores the given package and starts a deployment of it, after aborting any such ongoing deployment.*/ + public void deploy(ApplicationId id, JobType type, Optional<Version> platform, ApplicationPackage applicationPackage, boolean dryRun) { controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> { if ( ! application.get().instances().containsKey(id.instance())) application = controller.applications().withNewInstance(application, id); @@ -525,7 +531,8 @@ public class JobController { lastRun.map(run -> run.versions().targetPlatform()), lastRun.map(run -> run.versions().targetApplication())), false, - JobProfile.development); + JobProfile.development, + dryRun); }); locked(id, type, __ -> { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java index d93a0133c97..b3768178b1e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java @@ -39,11 +39,12 @@ public class Run { private final Optional<Instant> noNodesDownSince; private final Optional<ConvergenceSummary> convergenceSummary; private final Optional<X509Certificate> testerCertificate; + private final boolean dryRun; // For deserialisation only -- do not use! public Run(RunId id, Map<Step, StepInfo> steps, Versions versions, boolean isRedeployment, Instant start, Optional<Instant> end, RunStatus status, long lastTestRecord, Instant lastVespaLogTimestamp, Optional<Instant> noNodesDownSince, - Optional<ConvergenceSummary> convergenceSummary, Optional<X509Certificate> testerCertificate) { + Optional<ConvergenceSummary> convergenceSummary, Optional<X509Certificate> testerCertificate, boolean dryRun) { this.id = id; this.steps = Collections.unmodifiableMap(new EnumMap<>(steps)); this.versions = versions; @@ -56,13 +57,14 @@ public class Run { this.noNodesDownSince = noNodesDownSince; this.convergenceSummary = convergenceSummary; this.testerCertificate = testerCertificate; + this.dryRun = dryRun; } - public static Run initial(RunId id, Versions versions, boolean isRedeployment, Instant now, JobProfile profile) { + public static Run initial(RunId id, Versions versions, boolean isRedeployment, Instant now, JobProfile profile, boolean dryRun) { EnumMap<Step, StepInfo> steps = new EnumMap<>(Step.class); profile.steps().forEach(step -> steps.put(step, StepInfo.initial(step))); return new Run(id, steps, requireNonNull(versions), isRedeployment, requireNonNull(now), Optional.empty(), running, - -1, Instant.EPOCH, Optional.empty(), Optional.empty(), Optional.empty()); + -1, Instant.EPOCH, Optional.empty(), Optional.empty(), Optional.empty(), dryRun); } /** Returns a new Run with the status of the given completed step set accordingly. */ @@ -76,7 +78,7 @@ public class Run { EnumMap<Step, StepInfo> steps = new EnumMap<>(this.steps); steps.put(step.get(), stepInfo.with(Step.Status.of(status))); return new Run(id, steps, versions, isRedeployment, start, end, this.status == running ? status : this.status, - lastTestRecord, lastVespaLogTimestamp, noNodesDownSince, convergenceSummary, testerCertificate); + lastTestRecord, lastVespaLogTimestamp, noNodesDownSince, convergenceSummary, testerCertificate, dryRun); } /** Returns a new Run with a new start time*/ @@ -91,49 +93,49 @@ public class Run { steps.put(step.get(), stepInfo.with(startTime)); return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp, - noNodesDownSince, convergenceSummary, testerCertificate); + noNodesDownSince, convergenceSummary, testerCertificate, dryRun); } public Run finished(Instant now) { requireActive(); return new Run(id, steps, versions, isRedeployment, start, Optional.of(now), status == running ? success : status, - lastTestRecord, lastVespaLogTimestamp, noNodesDownSince, convergenceSummary, Optional.empty()); + lastTestRecord, lastVespaLogTimestamp, noNodesDownSince, convergenceSummary, Optional.empty(), dryRun); } public Run aborted() { requireActive(); return new Run(id, steps, versions, isRedeployment, start, end, aborted, lastTestRecord, lastVespaLogTimestamp, - noNodesDownSince, convergenceSummary, testerCertificate); + noNodesDownSince, convergenceSummary, testerCertificate, dryRun); } public Run with(long lastTestRecord) { requireActive(); return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp, - noNodesDownSince, convergenceSummary, testerCertificate); + noNodesDownSince, convergenceSummary, testerCertificate, dryRun); } public Run with(Instant lastVespaLogTimestamp) { requireActive(); return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp, - noNodesDownSince, convergenceSummary, testerCertificate); + noNodesDownSince, convergenceSummary, testerCertificate, dryRun); } public Run noNodesDownSince(Instant noNodesDownSince) { requireActive(); return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp, - Optional.ofNullable(noNodesDownSince), convergenceSummary, testerCertificate); + Optional.ofNullable(noNodesDownSince), convergenceSummary, testerCertificate, dryRun); } public Run withSummary(ConvergenceSummary convergenceSummary) { requireActive(); return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp, - noNodesDownSince, Optional.ofNullable(convergenceSummary), testerCertificate); + noNodesDownSince, Optional.ofNullable(convergenceSummary), testerCertificate, dryRun); } public Run with(X509Certificate testerCertificate) { requireActive(); return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp, - noNodesDownSince, convergenceSummary, Optional.of(testerCertificate)); + noNodesDownSince, convergenceSummary, Optional.of(testerCertificate), dryRun); } /** Returns the id of this run. */ @@ -229,6 +231,9 @@ public class Run { return isRedeployment; } + /** Whether this is a dry run deployment. */ + public boolean isDryRun() { return dryRun; } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java index b4a580a1562..6af3978d0cd 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java @@ -1,4 +1,4 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.persistence; import com.yahoo.component.Version; @@ -100,6 +100,7 @@ class RunSerializer { private static final String noNodesDownSinceField = "noNodesDownSince"; private static final String convergenceSummaryField = "convergenceSummaryV2"; private static final String testerCertificateField = "testerCertificate"; + private static final String isDryRunField = "isDryRun"; Run runFromSlime(Slime slime) { return runFromSlime(slime.get()); @@ -143,7 +144,8 @@ class RunSerializer { convergenceSummaryFrom(runObject.field(convergenceSummaryField)), Optional.of(runObject.field(testerCertificateField)) .filter(Inspector::valid) - .map(certificate -> X509CertificateUtils.fromPem(certificate.asString()))); + .map(certificate -> X509CertificateUtils.fromPem(certificate.asString())), + runObject.field(isDryRunField).valid() && runObject.field(isDryRunField).asBool()); } private Versions versionsFromSlime(Inspector versionsObject) { @@ -250,6 +252,7 @@ class RunSerializer { .orElseThrow(() -> new IllegalArgumentException("Source versions must be both present or absent.")), versionsObject.setObject(sourceField)); }); + runObject.setBool(isDryRunField, run.isDryRun()); } private void toSlime(Version platformVersion, ApplicationVersion applicationVersion, Cursor versionsObject) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 98ac789de04..1774694853b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -1969,7 +1969,13 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { ensureApplicationExists(TenantAndApplicationId.from(id), request); - controller.jobController().deploy(id, type, version, applicationPackage); + boolean dryRun = Optional.ofNullable(dataParts.get("deployOptions")) + .map(json -> SlimeUtils.jsonToSlime(json).get()) + .flatMap(options -> optional("dryRun", options)) + .map(Boolean::valueOf) + .orElse(false); + + controller.jobController().deploy(id, type, version, applicationPackage, dryRun); RunId runId = controller.jobController().last(id, type).get().id(); Slime slime = new Slime(); Cursor rootObject = slime.setObject(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java index c225dcbe49d..83e1a019109 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java @@ -1,4 +1,4 @@ -// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.deployment; import com.google.common.base.Supplier; @@ -323,7 +323,7 @@ public class DeploymentContext { /** Runs a deployment of the given package to the given dev/perf job, on the given version. */ public DeploymentContext runJob(JobType type, ApplicationPackage applicationPackage, Version vespaVersion) { - jobs.deploy(instanceId, type, Optional.ofNullable(vespaVersion), applicationPackage); + jobs.deploy(instanceId, type, Optional.ofNullable(vespaVersion), applicationPackage, false); return runJob(type); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java index 7623a02f6af..8bd22b5217f 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java @@ -1,4 +1,4 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.persistence; import com.google.common.collect.ImmutableMap; @@ -151,8 +151,9 @@ public class RunSerializerTest { assertEquals(run.testerCertificate(), phoenix.testerCertificate()); assertEquals(run.versions(), phoenix.versions()); assertEquals(run.steps(), phoenix.steps()); + assertEquals(run.isDryRun(), phoenix.isDryRun()); - Run initial = Run.initial(id, run.versions(), run.isRedeployment(), run.start(), JobProfile.production); + Run initial = Run.initial(id, run.versions(), run.isRedeployment(), run.start(), JobProfile.production, true); assertEquals(initial, serializer.runFromSlime(serializer.toSlime(initial))); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java index 3a9e2cf84d4..01dd1a58f44 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java @@ -176,6 +176,19 @@ public class JobControllerApiHandlerHelperTest { "jobs-direct-deployment.json"); } + @Test + public void testResponsesWithDryRunDeployment() { + var tester = new DeploymentTester(); + var app = tester.newDeploymentContext(); + tester.clock().setInstant(Instant.EPOCH); + var region = "us-west-1"; + var applicationPackage = new ApplicationPackageBuilder().region(region).build(); + // Deploy directly to production zone, like integration tests, with dryRun. + tester.controller().jobController().deploy(tester.instance().id(), productionUsWest1, Optional.empty(), applicationPackage, true); + assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.controller(), app.instanceId(), URI.create("https://some.url:43/root/")), + "jobs-direct-deployment.json"); + } + private void compare(HttpResponse response, String expected) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); response.render(baos); |