diff options
24 files changed, 261 insertions, 99 deletions
diff --git a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/cloud/impl/VespaTestRuntime.java b/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/cloud/impl/VespaTestRuntime.java index f4c8974a835..3088b5df74f 100644 --- a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/cloud/impl/VespaTestRuntime.java +++ b/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/cloud/impl/VespaTestRuntime.java @@ -22,6 +22,7 @@ import java.nio.file.Paths; * @author mortent */ public class VespaTestRuntime implements TestRuntime { + private final TestConfig config; private final Deployment deploymentToTest; @@ -42,7 +43,8 @@ public class VespaTestRuntime implements TestRuntime { private VespaTestRuntime(TestConfig config) { this.config = config; DefaultEndpointAuthenticator authenticator = new DefaultEndpointAuthenticator(config.system()); - this.deploymentToTest = new HttpDeployment(config.deployments().get(config.zone()), authenticator); + this.deploymentToTest = new HttpDeployment(config.platformVersion(), config.applicationVersion(), config.deployedAt(), + config.deployments().get(config.zone()), authenticator); FeedClientBuilder.setEndpointAuthenticator(authenticator); ai.vespa.feed.client.FeedClientBuilder.setFeedClientBuilderSupplier(FeedClientBuilder::new); } @@ -54,6 +56,13 @@ public class VespaTestRuntime implements TestRuntime { config.zone().region().value()); } + @Override + public ai.vespa.cloud.ApplicationId application() { + return new ai.vespa.cloud.ApplicationId(config.application().tenant().value(), + config.application().application().value(), + config.application().instance().value()); + } + /** Returns the deployment this is testing. */ @Override public Deployment deploymentToTest() { return deploymentToTest; } @@ -93,4 +102,5 @@ public class VespaTestRuntime implements TestRuntime { System.out.println("TestRuntime: Zone: " + zone.toString()); return controller.testConfig(id, zone); } + } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java index 5dfc1bbd3ef..dbc7b5512be 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java @@ -277,6 +277,32 @@ public class HostedDeployTest { * that are still using features that do not work on version 8.x) */ @Test + public void testFailingOnOldMajorButNotNewInUpgradeScenario() { + int oldMajor = 7; + int newMajor = 8; + Version wantedVersion = new Version(newMajor, 1, 2); + Version oldVersion = new Version(oldMajor, 2, 3); + List<Host> hosts = createHosts(9, oldVersion.toFullString(), wantedVersion.toFullString()); + + ModelFactory oldFactory = createFailingModelFactory(oldVersion); + ModelFactory newFactory = createHostedModelFactory(wantedVersion); + List<ModelFactory> modelFactories = List.of(oldFactory, newFactory); + + DeployTester tester = createTester(hosts, modelFactories, prodZone); + + tester.deployApp("src/test/apps/hosted/", wantedVersion.toFullString()); + + tester.deployApp("src/test/apps/hosted/", oldVersion.toFullString()); + + assertEquals(9, tester.getAllocatedHostsOf(tester.applicationId()).getHosts().size()); + } + + /** + * Tests that we create the minimal set of models and that version 7.x is created + * if creating version 8.x fails (to support upgrades to new major version for applications + * that are still using features that do not work on version 8.x) + */ + @Test public void testWantedVersionIsRequiredAlsoWhenThereIsAnOlderMajorThatDoesNotFailModelBuilding() { int oldMajor = 7; int newMajor = 8; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java index 3105adb2488..813e3454e80 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java @@ -157,7 +157,7 @@ public class InternalStepRunner implements StepRunner { } private Optional<RunStatus> deployInitialReal(RunId id, DualLogger logger) { - Versions versions = controller.jobController().run(id).get().versions(); + Versions versions = controller.jobController().run(id).versions(); logger.log("Deploying platform version " + versions.sourcePlatform().orElse(versions.targetPlatform()) + " and application " + @@ -166,16 +166,16 @@ public class InternalStepRunner implements StepRunner { } private Optional<RunStatus> deployReal(RunId id, DualLogger logger) { - Versions versions = controller.jobController().run(id).get().versions(); + Versions versions = controller.jobController().run(id).versions(); logger.log("Deploying platform version " + versions.targetPlatform() + " and application " + versions.targetRevision() + " ..."); return deployReal(id, false, logger); } private Optional<RunStatus> deployReal(RunId id, boolean setTheStage, DualLogger logger) { - Optional<X509Certificate> testerCertificate = controller.jobController().run(id).get().testerCertificate(); + Optional<X509Certificate> testerCertificate = controller.jobController().run(id).testerCertificate(); return deploy(() -> controller.applications().deploy(id.job(), setTheStage), - controller.jobController().run(id).get() + controller.jobController().run(id) .stepInfo(setTheStage ? deployInitialReal : deployReal).get() .startTime().get(), logger) @@ -184,8 +184,8 @@ public class InternalStepRunner implements StepRunner { if ( ! useTesterCertificate(id) || result != running) return true; // If tester cert, ensure real is deployed with the tester cert whose key was successfully deployed. - return controller.jobController().run(id).get().stepStatus(deployTester).get() == succeeded - && testerCertificate.equals(controller.jobController().run(id).get().testerCertificate()); + return controller.jobController().run(id).stepStatus(deployTester).get() == succeeded + && testerCertificate.equals(controller.jobController().run(id).testerCertificate()); }); } @@ -196,7 +196,7 @@ public class InternalStepRunner implements StepRunner { testerPackage(id), id.type().zone(), platform), - controller.jobController().run(id).get() + controller.jobController().run(id) .stepInfo(deployTester).get() .startTime().get(), logger); @@ -287,10 +287,10 @@ public class InternalStepRunner implements StepRunner { return Optional.of(installationFailed); } - Versions versions = controller.jobController().run(id).get().versions(); + Versions versions = controller.jobController().run(id).versions(); Version platform = setTheStage ? versions.sourcePlatform().orElse(versions.targetPlatform()) : versions.targetPlatform(); - Run run = controller.jobController().run(id).get(); + Run run = controller.jobController().run(id); Optional<ServiceConvergence> services = controller.serviceRegistry().configServer().serviceConvergence(new DeploymentId(id.application(), id.type().zone()), Optional.of(platform)); if (services.isEmpty()) { @@ -409,14 +409,14 @@ public class InternalStepRunner implements StepRunner { } private Version testerPlatformVersion(RunId id) { - Version targetPlatform = controller.jobController().run(id).get().versions().targetPlatform(); + Version targetPlatform = controller.jobController().run(id).versions().targetPlatform(); Version systemVersion = controller.readSystemVersion(); boolean incompatible = controller.applications().versionCompatibility(id.application()).refuse(targetPlatform, systemVersion); return incompatible || application(id.application()).change().isPinned() ? targetPlatform : systemVersion; } private Optional<RunStatus> installTester(RunId id, DualLogger logger) { - Run run = controller.jobController().run(id).get(); + Run run = controller.jobController().run(id); Version platform = testerPlatformVersion(id); ZoneId zone = id.type().zone(); ApplicationId testerId = id.tester().id(); @@ -608,6 +608,9 @@ public class InternalStepRunner implements StepRunner { byte[] config = testConfigSerializer.configJson(id.application(), id.type(), true, + deployment.get().version(), + deployment.get().revision(), + deployment.get().at(), endpoints, controller.applications().reachableContentClustersByZone(deployments)); controller.jobController().cloud().startTests(getTesterDeploymentId(id), suite, config); @@ -622,7 +625,7 @@ public class InternalStepRunner implements StepRunner { return Optional.of(error); } - Optional<X509Certificate> testerCertificate = controller.jobController().run(id).get().testerCertificate(); + Optional<X509Certificate> testerCertificate = controller.jobController().run(id).testerCertificate(); if (testerCertificate.isPresent()) { try { testerCertificate.get().checkValidity(Date.from(controller.clock().instant())); @@ -684,7 +687,7 @@ public class InternalStepRunner implements StepRunner { } // Hitting a config server which doesn't have this particular app loaded causes a 404. catch (ConfigServerException e) { - Instant doom = controller.jobController().run(id).get().stepInfo(copyVespaLogs).get().startTime().get() + Instant doom = controller.jobController().run(id).stepInfo(copyVespaLogs).get().startTime().get() .plus(Duration.ofMinutes(3)); if (e.code() == ConfigServerException.ErrorCode.NOT_FOUND && controller.clock().instant().isBefore(doom)) { logger.log(INFO, "Found no logs, but will retry"); @@ -710,7 +713,7 @@ public class InternalStepRunner implements StepRunner { } catch (RuntimeException e) { logger.log(WARNING, "Failed deleting application " + id.application(), e); - Instant startTime = controller.jobController().run(id).get().stepInfo(deactivateReal).get().startTime().get(); + Instant startTime = controller.jobController().run(id).stepInfo(deactivateReal).get().startTime().get(); return startTime.isBefore(controller.clock().instant().minus(Duration.ofHours(1))) ? Optional.of(error) : Optional.empty(); @@ -725,7 +728,7 @@ public class InternalStepRunner implements StepRunner { } catch (RuntimeException e) { logger.log(WARNING, "Failed deleting tester of " + id.application(), e); - Instant startTime = controller.jobController().run(id).get().stepInfo(deactivateTester).get().startTime().get(); + Instant startTime = controller.jobController().run(id).stepInfo(deactivateTester).get().startTime().get(); return startTime.isBefore(controller.clock().instant().minus(Duration.ofHours(1))) ? Optional.of(error) : Optional.empty(); @@ -749,7 +752,7 @@ public class InternalStepRunner implements StepRunner { return Optional.of(error); } catch (RuntimeException e) { - Instant start = controller.jobController().run(id).get().stepInfo(report).get().startTime().get(); + Instant start = controller.jobController().run(id).stepInfo(report).get().startTime().get(); return (controller.clock().instant().isAfter(start.plusSeconds(180))) ? Optional.empty() : Optional.of(error); @@ -871,7 +874,7 @@ public class InternalStepRunner implements StepRunner { private boolean timedOut(RunId id, Deployment deployment, Duration defaultTimeout) { // TODO jonmv: This is a workaround for new deployment writes not yet being visible in spite of Curator locking. // TODO Investigate what's going on here, and remove this workaround. - Run run = controller.jobController().run(id).get(); + Run run = controller.jobController().run(id); if ( ! controller.system().isCd() && run.start().isAfter(deployment.at())) return false; @@ -887,7 +890,7 @@ public class InternalStepRunner implements StepRunner { /** Returns the application package for the tester application, assembled from a generated config, fat-jar and services.xml. */ private ApplicationPackage testerPackage(RunId id) { - RevisionId revision = controller.jobController().run(id).get().versions().targetRevision(); + RevisionId revision = controller.jobController().run(id).versions().targetRevision(); DeploymentSpec spec = controller.applications().requireApplication(TenantAndApplicationId.from(id.application())).deploymentSpec(); byte[] testZip = controller.applications().applicationStore().getTester(id.application().tenant(), id.application().application(), revision); 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 9331c9942b6..3d4a2f40303 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 @@ -47,6 +47,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NavigableMap; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.OptionalLong; import java.util.Set; @@ -298,11 +299,12 @@ public class JobController { return runs.build(); } - /** Returns the run with the given id, if it exists. */ - public Optional<Run> run(RunId id) { + /** Returns the run with the given id, or throws if no such run exists. */ + public Run run(RunId id) { return runs(id.application(), id.type()).values().stream() .filter(run -> run.id().equals(id)) - .findAny(); + .findAny() + .orElseThrow(() -> new NoSuchElementException("no run with id '" + id + "' exists")); } /** Returns the last run of the given type, for the given application, if one has been run. */ @@ -412,7 +414,7 @@ public class JobController { Deque<Mutex> locks = new ArrayDeque<>(); try { // Ensure no step is still running before we finish the run — report depends transitively on all the other steps. - Run unlockedRun = run(id).get(); + Run unlockedRun = run(id); locks.push(curator.lock(id.application(), id.type(), report)); for (Step step : report.allPrerequisites(unlockedRun.steps().keySet())) locks.push(curator.lock(id.application(), id.type(), step)); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java index 1680e064234..2394f293170 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java @@ -1,6 +1,7 @@ // 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.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; @@ -8,10 +9,12 @@ import com.yahoo.slime.Cursor; import com.yahoo.slime.Slime; import com.yahoo.slime.SlimeUtils; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId; import com.yahoo.vespa.hosted.controller.application.Endpoint; import java.io.IOException; import java.io.UncheckedIOException; +import java.time.Instant; import java.util.List; import java.util.Map; @@ -31,6 +34,9 @@ public class TestConfigSerializer { public Slime configSlime(ApplicationId id, JobType type, boolean isCI, + Version platform, + RevisionId revision, + Instant deployedAt, Map<ZoneId, List<Endpoint>> deployments, Map<ZoneId, List<String>> clusters) { Slime slime = new Slime(); @@ -40,6 +46,9 @@ public class TestConfigSerializer { root.setString("zone", type.zone().value()); root.setString("system", system.value()); root.setBool("isCI", isCI); + root.setString("platform", platform.toFullString()); + root.setLong("revision", revision.number()); + root.setLong("deployedAt", deployedAt.toEpochMilli()); // TODO jvenstad: remove when clients can be updated Cursor endpointsObject = root.setObject("endpoints"); @@ -72,10 +81,13 @@ public class TestConfigSerializer { public byte[] configJson(ApplicationId id, JobType type, boolean isCI, + Version platform, + RevisionId revision, + Instant deployedAt, Map<ZoneId, List<Endpoint>> deployments, Map<ZoneId, List<String>> clusters) { try { - return SlimeUtils.toJsonBytes(configSlime(id, type, isCI, deployments, clusters)); + return SlimeUtils.toJsonBytes(configSlime(id, type, isCI, platform, revision, deployedAt, deployments, clusters)); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java index b4fac8074aa..94ec4129744 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java @@ -81,7 +81,7 @@ public class JobRunner extends ControllerMaintainer { && controller().clock().instant().isAfter(run.sleepUntil().orElse(run.start()).plus(jobTimeout))) executors.execute(() -> { jobs.abort(run.id(), "job timeout of " + jobTimeout + " reached"); - advance(jobs.run(run.id()).get()); + advance(jobs.run(run.id())); }); else if (run.readySteps().isEmpty()) executors.execute(() -> finish(run.id())); 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 ef7c7063355..63f33540721 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 @@ -2345,16 +2345,24 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { .flatMap(instance -> instance.productionDeployments().keySet().stream()) .map(zone -> new DeploymentId(prodInstanceId, zone)) .collect(Collectors.toCollection(HashSet::new)); - ZoneId testedZone = type.zone(); + // If a production job is specified, the production deployment of the orchestrated instance is the relevant one, // as user instances should not exist in prod. + ApplicationId toTest = type.isProduction() ? prodInstanceId : id; if ( ! type.isProduction()) - deployments.add(new DeploymentId(id, testedZone)); + deployments.add(new DeploymentId(toTest, type.zone())); + + Deployment deployment = application.require(toTest.instance()).deployments().get(type.zone()); + if (deployment == null) + throw new NotExistsException(toTest + " is not deployed in " + type.zone()); return new SlimeJsonResponse(testConfigSerializer.configSlime(id, type, false, + deployment.version(), + deployment.revision(), + deployment.at(), controller.routing().readTestRunnerEndpointsOf(deployments), controller.applications().reachableContentClustersByZone(deployments))); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java index 80425609aa6..8c49df43c30 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java @@ -112,8 +112,7 @@ class JobControllerApiHandlerHelper { Slime slime = new Slime(); Cursor detailsObject = slime.setObject(); - Run run = jobController.run(runId) - .orElseThrow(() -> new IllegalStateException("Unknown run '" + runId + "'")); + Run run = jobController.run(runId); detailsObject.setBool("active", ! run.hasEnded()); detailsObject.setString("status", nameOf(run.status())); try { 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 27951d95689..b3db4a8b845 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 @@ -355,7 +355,7 @@ public class DeploymentContext { var job = jobId(type); RunId id = currentRun(job).id(); runner.advance(currentRun(job)); - Run run = jobs.run(id).get(); + Run run = jobs.run(id); assertTrue(run.hasFailed()); assertTrue(run.hasEnded()); if (messagePart.isPresent()) { @@ -441,10 +441,10 @@ public class DeploymentContext { RunId id = currentRun(job).id(); - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endTests)); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.endTests)); tester.cloud().set(noTests ? Status.NO_TESTS : Status.FAILURE); runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasEnded()); + assertTrue(jobs.run(id).hasEnded()); assertTrue(configServer().nodeRepository().list(job.type().zone(), NodeFilter.all().applications(TesterId.of(instanceId).id())).isEmpty()); return this; @@ -481,7 +481,7 @@ public class DeploymentContext { assertSame(RunStatus.aborted, run.status()); assertFalse(run.hasEnded()); runner.advance(run); - assertTrue(jobs.run(run.id()).get().hasEnded()); + assertTrue(jobs.run(run.id()).hasEnded()); return this; } @@ -493,8 +493,8 @@ public class DeploymentContext { doDeploy(job); tester.clock().advance(Timeouts.of(tester.controller().system()).noNodesDown().plusSeconds(1)); runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasFailed()); - assertTrue(jobs.run(id).get().hasEnded()); + assertTrue(jobs.run(id).hasFailed()); + assertTrue(jobs.run(id).hasEnded()); return this; } @@ -507,8 +507,8 @@ public class DeploymentContext { doUpgrade(job); tester.clock().advance(Timeouts.of(tester.controller().system()).noNodesDown().plusSeconds(1)); runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasFailed()); - assertTrue(jobs.run(id).get().hasEnded()); + assertTrue(jobs.run(id).hasFailed()); + assertTrue(jobs.run(id).hasEnded()); return this; } @@ -540,8 +540,8 @@ public class DeploymentContext { configServer().convergeServices(instanceId, testZone); configServer().convergeServices(testerId.id(), testZone); runner.run(); - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endTests)); - assertTrue(jobs.run(id).get().steps().get(Step.endTests).startTime().isPresent()); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.endTests)); + assertTrue(jobs.run(id).steps().get(Step.endTests).startTime().isPresent()); return id; } @@ -571,21 +571,21 @@ public class DeploymentContext { doInstallTester(job); if (job.type().equals(stagingTest)) { // Do the initial deployment and installation of the real application. - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installInitialReal)); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installInitialReal)); tester.configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), tester.configServer().application(job.application(), zone).get().version().get()); configServer().convergeServices(id.application(), zone); runner.advance(currentRun(job)); - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installInitialReal)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installInitialReal)); // All installation is complete and endpoints are ready, so setup may begin. - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installInitialReal)); - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installTester)); - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.startStagingSetup)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installInitialReal)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installTester)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.startStagingSetup)); - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endStagingSetup)); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.endStagingSetup)); tester.cloud().set(Status.SUCCESS); runner.advance(currentRun(job)); - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.endStagingSetup)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.endStagingSetup)); } } @@ -595,7 +595,7 @@ public class DeploymentContext { ZoneId zone = job.type().zone(); DeploymentId deployment = new DeploymentId(job.application(), zone); - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installReal)); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installReal)); configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), tester.configServer().application(job.application(), zone).get().version().get()); runner.advance(currentRun(job)); } @@ -615,15 +615,15 @@ public class DeploymentContext { RunId id = currentRun(job).id(); ZoneId zone = job.type().zone(); - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installReal)); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installReal)); configServer().convergeServices(id.application(), zone); runner.advance(currentRun(job)); if (job.type().environment().isManuallyDeployed()) { - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installReal)); - assertTrue(jobs.run(id).get().hasEnded()); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installReal)); + assertTrue(jobs.run(id).hasEnded()); return; } - assertEquals("Status of " + id, succeeded, jobs.run(id).get().stepStatuses().get(Step.installReal)); + assertEquals("Status of " + id, succeeded, jobs.run(id).stepStatuses().get(Step.installReal)); } /** Installs tester and starts tests. */ @@ -631,13 +631,13 @@ public class DeploymentContext { RunId id = currentRun(job).id(); ZoneId zone = job.type().zone(); - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installTester)); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installTester)); configServer().nodeRepository().doUpgrade(new DeploymentId(TesterId.of(job.application()).id(), zone), Optional.empty(), tester.configServer().application(id.tester().id(), zone).get().version().get()); runner.advance(currentRun(job)); - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installTester)); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installTester)); configServer().convergeServices(TesterId.of(id.application()).id(), zone); runner.advance(currentRun(job)); - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installTester)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installTester)); runner.advance(currentRun(job)); } @@ -648,15 +648,15 @@ public class DeploymentContext { // All installation is complete and endpoints are ready, so tests may begin. if (job.type().isDeployment()) - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installReal)); - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installTester)); - assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.startTests)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installReal)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installTester)); + assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.startTests)); - assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endTests)); + assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.endTests)); tester.cloud().set(Status.SUCCESS); runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasEnded()); - assertFalse(jobs.run(id).get().hasFailed()); + assertTrue(jobs.run(id).hasEnded()); + assertFalse(jobs.run(id).hasFailed()); Instance instance = tester.application(TenantAndApplicationId.from(instanceId)).require(id.application().instance()); assertEquals(job.type().isProduction(), instance.deployments().containsKey(zone)); assertTrue(configServer().nodeRepository().list(zone, NodeFilter.all().applications(TesterId.of(instance.id()).id())).isEmpty()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java index 4219c52be20..78e7606d7c6 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java @@ -136,8 +136,8 @@ public class DeploymentTester { triggerJobs(); for (Run run : jobs.active()) { jobs.abort(run.id(), "DeploymentTester.abortAll"); - runner.advance(jobs.run(run.id()).get()); - assertTrue(jobs.run(run.id()).get().hasEnded()); + runner.advance(jobs.run(run.id())); + assertTrue(jobs.run(run.id()).hasEnded()); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java index ae08d1b3a22..d781b1f1d3f 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java @@ -120,19 +120,19 @@ public class InternalStepRunnerTest { HostName host = tester.configServer().hostFor(instanceId, zone); tester.runner().run(); - assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.deployReal)); + assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.deployReal)); tester.configServer().convergeServices(app.instanceId(), zone); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.installReal)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.installReal)); tester.configServer().nodeRepository().doRestart(app.deploymentIdIn(zone), Optional.of(host)); tester.configServer().nodeRepository().requestReboot(app.deploymentIdIn(zone), Optional.of(host)); tester.runner().run(); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.installReal)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.installReal)); tester.clock().advance(InternalStepRunner.Timeouts.of(system()).noNodesDown().plus(Duration.ofSeconds(1))); tester.runner().run(); - assertEquals(installationFailed, tester.jobs().run(id).get().status()); + assertEquals(installationFailed, tester.jobs().run(id).status()); } @Test @@ -251,7 +251,7 @@ public class InternalStepRunnerTest { @Test public void noTestsThenErrorIsError() { RunId id = app.startSystemTestTests(); - Run run = tester.jobs().run(id).get(); + Run run = tester.jobs().run(id); run = run.with(noTests, new LockedStep(() -> { }, Step.endTests)); assertFalse(run.hasFailed()); run = run.with(RunStatus.error, new LockedStep(() -> { }, Step.deactivateReal)); @@ -264,8 +264,8 @@ public class InternalStepRunnerTest { RunId id = app.startSystemTestTests(); tester.cloud().set(Status.NO_TESTS); tester.runner().run(); - assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); - Run run = tester.jobs().run(id).get(); + assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.endTests)); + Run run = tester.jobs().run(id); assertEquals(noTests, run.status()); } @@ -274,7 +274,7 @@ public class InternalStepRunnerTest { RunId id = app.startSystemTestTests(); tester.cloud().set(TesterCloud.Status.NOT_STARTED); tester.runner().run(); - assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); + assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests)); } @Test @@ -288,7 +288,7 @@ public class InternalStepRunnerTest { assertTestLogEntries(id, Step.endTests, new LogEntry(lastId + 1, Instant.ofEpochMilli(321), error, "Failure!"), new LogEntry(lastId + 2, tester.clock().instant(), info, "Tests failed.")); - assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); + assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests)); } @Test @@ -299,7 +299,7 @@ public class InternalStepRunnerTest { long lastId = tester.jobs().details(id).get().lastId().getAsLong(); tester.runner().run(); - assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); + assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests)); assertTestLogEntries(id, Step.endTests, new LogEntry(lastId + 1, Instant.ofEpochMilli(123), error, "Error!"), new LogEntry(lastId + 2, tester.clock().instant(), info, "Tester failed running its tests!")); @@ -309,7 +309,7 @@ public class InternalStepRunnerTest { public void testsSucceedWhenTheyDoRemotely() { RunId id = app.startSystemTestTests(); tester.runner().run(); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.endTests)); var testZone = DeploymentContext.systemTest.zone(); Inspector configObject = SlimeUtils.jsonToSlime(tester.cloud().config()).get(); assertEquals(app.instanceId().serializedForm(), configObject.field("application").asString()); @@ -338,7 +338,7 @@ public class InternalStepRunnerTest { new LogEntry(lastId + 2, Instant.ofEpochMilli(1234), info, "Steady!"), new LogEntry(lastId + 3, Instant.ofEpochMilli(12345), info, "Success!"), new LogEntry(lastId + 4, tester.clock().instant(), info, "Tests completed successfully.")); - assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); + assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.endTests)); } @Test @@ -351,16 +351,16 @@ public class InternalStepRunnerTest { long lastId1 = tester.jobs().details(id).get().lastId().getAsLong(); Instant instant1 = tester.clock().instant(); tester.runner().run(); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); - assertEquals(running, tester.jobs().run(id).get().status()); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.endTests)); + assertEquals(running, tester.jobs().run(id).status()); tester.cloud().clearLog(); // Test sleeps for a while. tester.runner().run(); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.deployTester)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.deployTester)); tester.clock().advance(Duration.ofSeconds(899)); tester.runner().run(); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.deployTester)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.deployTester)); tester.clock().advance(JobRunner.jobTimeout); var testZone = DeploymentContext.systemTest.zone(); @@ -369,14 +369,14 @@ public class InternalStepRunnerTest { tester.configServer().convergeServices(app.instanceId(), testZone); tester.configServer().convergeServices(app.testerId().id(), testZone); tester.runner().run(); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); - assertTrue(tester.jobs().run(id).get().steps().get(Step.endTests).startTime().isPresent()); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.endTests)); + assertTrue(tester.jobs().run(id).steps().get(Step.endTests).startTime().isPresent()); tester.cloud().set(TesterCloud.Status.SUCCESS); tester.cloud().testReport(TestReport.fromJson("{\"bar\":2}")); long lastId2 = tester.jobs().details(id).get().lastId().getAsLong(); tester.runner().run(); - assertEquals(success, tester.jobs().run(id).get().status()); + assertEquals(success, tester.jobs().run(id).status()); assertTestLogEntries(id, Step.endTests, new LogEntry(lastId1 + 1, Instant.ofEpochMilli(123), info, "Not enough data!"), @@ -394,7 +394,7 @@ public class InternalStepRunnerTest { tester.jobs().deploy(app.instanceId(), DeploymentContext.devUsEast1, Optional.empty(), applicationPackage()); tester.runner().run(); RunId id = tester.jobs().last(app.instanceId(), DeploymentContext.devUsEast1).get().id(); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.installReal)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.installReal)); Version version = new Version("7.8.9"); Future<?> concurrentDeployment = Executors.newSingleThreadExecutor().submit(() -> { @@ -409,7 +409,7 @@ public class InternalStepRunnerTest { tester.runner().run(); // Job run order determined by JobType enum order per application. tester.configServer().convergeServices(app.instanceId(), zone); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.installReal)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.installReal)); assertEquals(applicationPackage().hash(), tester.configServer().application(app.instanceId(), zone).get().applicationPackage().hash()); assertEquals(otherPackage.hash(), tester.configServer().application(app.instanceId(), DeploymentContext.perfUsEast3.zone()).get().applicationPackage().hash()); @@ -450,8 +450,8 @@ public class InternalStepRunnerTest { tester.configServer().setLogStream(() -> { throw new ConfigServerException(ConfigServerException.ErrorCode.NOT_FOUND, "404", "context"); }); long lastId = tester.jobs().details(id).get().lastId().getAsLong(); tester.runner().run(); - assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.copyVespaLogs)); + assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.copyVespaLogs)); assertTestLogEntries(id, Step.copyVespaLogs, new LogEntry(lastId + 2, tester.clock().instant(), info, "Found no logs, but will retry")); @@ -459,7 +459,7 @@ public class InternalStepRunnerTest { // Config servers now provide the log, and we get it. tester.configServer().setLogStream(() -> vespaLog(tester.clock().instant())); tester.runner().run(); - assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests)); + assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests)); assertTestLogEntries(id, Step.copyVespaLogs, new LogEntry(lastId + 2, tester.clock().instant(), info, "Found no logs, but will retry"), @@ -511,21 +511,21 @@ public class InternalStepRunnerTest { throw new ConfigServerException(ConfigServerException.ErrorCode.PARENT_HOST_NOT_READY, "provisioning", "deploy tester"); }); tester.runner().run(); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.deployTester)); - assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.deployReal)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.deployTester)); + assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.deployReal)); List<X509Certificate> oldTrusted = new ArrayList<>(DeploymentContext.publicApplicationPackage().trustedCertificates()); - X509Certificate oldCert = tester.jobs().run(id).get().testerCertificate().get(); + X509Certificate oldCert = tester.jobs().run(id).testerCertificate().get(); oldTrusted.add(oldCert); assertEquals(oldTrusted, tester.configServer().application(app.instanceId(), id.type().zone()).get().applicationPackage().trustedCertificates()); tester.configServer().throwOnNextPrepare(null); tester.runner().run(); - assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.deployTester)); - assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.deployReal)); + assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.deployTester)); + assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.deployReal)); List<X509Certificate> newTrusted = new ArrayList<>(DeploymentContext.publicApplicationPackage().trustedCertificates()); - X509Certificate newCert = tester.jobs().run(id).get().testerCertificate().get(); + X509Certificate newCert = tester.jobs().run(id).testerCertificate().get(); newTrusted.add(newCert); assertEquals(newTrusted, tester.configServer().application(app.instanceId(), id.type().zone()).get().applicationPackage().trustedCertificates()); assertNotEquals(oldCert, newCert); @@ -538,12 +538,12 @@ public class InternalStepRunnerTest { RunId id = app.startSystemTestTests(); List<X509Certificate> trusted = new ArrayList<>(DeploymentContext.publicApplicationPackage().trustedCertificates()); - trusted.add(tester.jobs().run(id).get().testerCertificate().get()); + trusted.add(tester.jobs().run(id).testerCertificate().get()); assertEquals(trusted, tester.configServer().application(app.instanceId(), id.type().zone()).get().applicationPackage().trustedCertificates()); tester.clock().advance(InternalStepRunner.Timeouts.of(system()).testerCertificate().plus(Duration.ofSeconds(1))); tester.runner().run(); - assertEquals(RunStatus.error, tester.jobs().run(id).get().status()); + assertEquals(RunStatus.error, tester.jobs().run(id).status()); } private void assertTestLogEntries(RunId id, Step step, LogEntry... entries) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java index 59ee8cc6eae..67583891765 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java @@ -1,12 +1,14 @@ // 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.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.slime.SlimeUtils; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId; import com.yahoo.vespa.hosted.controller.application.Endpoint; import com.yahoo.vespa.hosted.controller.application.EndpointId; import org.junit.Test; @@ -14,6 +16,7 @@ import org.junit.Test; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.time.Instant; import java.util.List; import java.util.Map; @@ -31,6 +34,9 @@ public class TestConfigSerializerTest { byte[] json = new TestConfigSerializer(SystemName.PublicCd).configJson(instanceId, DeploymentContext.systemTest, true, + Version.fromString("1.2.3"), + RevisionId.forProduction(321), + Instant.ofEpochMilli(222), Map.of(zone, List.of(Endpoint.of(ApplicationId.defaultId()) .target(EndpointId.of("ai"), ClusterSpec.Id.from("qrs"), List.of(new DeploymentId(ApplicationId.defaultId(), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json index 3a5e6dc5dc3..ac0f2b9f740 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json @@ -3,6 +3,9 @@ "zone": "dev.us-east-1", "system": "main", "isCI": false, + "platform": "6.1.0", + "revision": 1, + "deployedAt": 1600000000000, "endpoints": { "dev.us-east-1": [ "https://my-user.application1.tenant1.us-east-1.dev.vespa.oath.cloud/" diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json index 0a9236655ba..671c34cb2c0 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json @@ -3,6 +3,9 @@ "zone": "prod.us-central-1", "system": "main", "isCI": false, + "platform": "6.1.0", + "revision": 1, + "deployedAt": 1600000000000, "endpoints": { "prod.us-central-1": [ "https://application1.tenant1.us-central-1.vespa.oath.cloud/" diff --git a/controller-server/src/test/resources/testConfig.json b/controller-server/src/test/resources/testConfig.json index 5c3d5942001..0ea4b163992 100644 --- a/controller-server/src/test/resources/testConfig.json +++ b/controller-server/src/test/resources/testConfig.json @@ -3,6 +3,9 @@ "zone": "test.us-east-1", "system": "publiccd", "isCI": true, + "platform": "1.2.3", + "revision": 321, + "deployedAt": 222, "endpoints": { "test.us-east-1": [ "https://ai.default.default.global.vespa.oath.cloud/" diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/TestConfig.java b/hosted-api/src/main/java/ai/vespa/hosted/api/TestConfig.java index 937e961c69b..d8bd08773ee 100644 --- a/hosted-api/src/main/java/ai/vespa/hosted/api/TestConfig.java +++ b/hosted-api/src/main/java/ai/vespa/hosted/api/TestConfig.java @@ -10,6 +10,7 @@ import com.yahoo.slime.ObjectTraverser; import com.yahoo.slime.SlimeUtils; import java.net.URI; +import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -29,10 +30,14 @@ public class TestConfig { private final ZoneId zone; private final SystemName system; private final boolean isCI; + private final String platform; + private final long revision; + private final Instant deployedAt; private final Map<ZoneId, Map<String, URI>> deployments; private final Map<ZoneId, List<String>> contentClusters; public TestConfig(ApplicationId application, ZoneId zone, SystemName system, boolean isCI, + String platform, long revision, Instant deployedAt, Map<ZoneId, Map<String, URI>> deployments, Map<ZoneId, List<String>> contentClusters) { if ( ! deployments.containsKey(zone)) throw new IllegalArgumentException("Config must contain a deployment for its zone, but only does for " + deployments.keySet()); @@ -40,6 +45,9 @@ public class TestConfig { this.zone = requireNonNull(zone); this.system = requireNonNull(system); this.isCI = isCI; + this.platform = platform; + this.revision = revision; + this.deployedAt = deployedAt; this.deployments = deployments.entrySet().stream() .collect(Collectors.toUnmodifiableMap(entry -> entry.getKey(), entry -> Map.copyOf(entry.getValue()))); @@ -59,10 +67,17 @@ public class TestConfig { if (config.field("localEndpoints").valid()) return TestConfig.fromEndpointsOnly(toClusterMap(config.field("localEndpoints"))); + if (config.field("deployments").valid()) { + + } + ApplicationId application = ApplicationId.fromSerializedForm(config.field("application").asString()); ZoneId zone = ZoneId.from(config.field("zone").asString()); SystemName system = SystemName.from(config.field("system").asString()); boolean isCI = config.field("isCI").asBool(); + String platform = config.field("platform").asString(); + long revision = config.field("revision").asLong(); + Instant deployedAt = Instant.ofEpochMilli(config.field("deployedAt").asLong()); Map<ZoneId, Map<String, URI>> deployments = new HashMap<>(); config.field("zoneEndpoints").traverse((ObjectTraverser) (zoneId, clustersObject) -> { deployments.put(ZoneId.from(zoneId), toClusterMap(clustersObject)); @@ -73,7 +88,7 @@ public class TestConfig { clustersArray.traverse((ArrayTraverser) (__, cluster) -> clusters.add(cluster.asString())); contentClusters.put(ZoneId.from(zoneId), clusters); })); - return new TestConfig(application, zone, system, isCI, deployments, contentClusters); + return new TestConfig(application, zone, system, isCI, platform, revision, deployedAt, deployments, contentClusters); } static Map<String, URI> toClusterMap(Inspector clustersObject) { @@ -91,6 +106,9 @@ public class TestConfig { ZoneId.defaultId(), SystemName.defaultSystem(), false, + "", + 0, + Instant.EPOCH, Map.of(ZoneId.defaultId(), endpoints), Map.of()); } @@ -113,4 +131,10 @@ public class TestConfig { /** Returns an immutable view of content clusters, per zone, of the application to test. */ public Map<ZoneId, List<String>> contentClusters() { return contentClusters; } + public String platformVersion() { return platform; } + + public long applicationVersion() { return revision; } + + public Instant deployedAt() { return deployedAt; } + } diff --git a/hosted-api/src/test/java/ai/vespa/hosted/api/TestConfigTest.java b/hosted-api/src/test/java/ai/vespa/hosted/api/TestConfigTest.java index 2cbc65c6b23..320bce678c3 100644 --- a/hosted-api/src/test/java/ai/vespa/hosted/api/TestConfigTest.java +++ b/hosted-api/src/test/java/ai/vespa/hosted/api/TestConfigTest.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; +import java.time.Instant; import java.util.List; import java.util.Map; @@ -32,6 +33,9 @@ public class TestConfigTest { assertEquals(SystemName.PublicCd, config.system()); assertTrue(config.isCI()); + assertEquals("1.2.3", config.platformVersion()); + assertEquals(321, config.applicationVersion()); + assertEquals(Instant.ofEpochMilli(1600000000L), config.deployedAt()); assertEquals(Map.of(ZoneId.from("dev", "aws-us-east-1c"), Map.of("default", URI.create("https://dev.endpoint:443/")), ZoneId.from("prod", "aws-us-east-1a"), diff --git a/hosted-api/src/test/resources/test-config.json b/hosted-api/src/test/resources/test-config.json index 07454efdb66..b1a7e2c2563 100644 --- a/hosted-api/src/test/resources/test-config.json +++ b/hosted-api/src/test/resources/test-config.json @@ -3,6 +3,9 @@ "zone": "dev.aws-us-east-1c", "system": "publiccd", "isCI": true, + "platform": "1.2.3", + "revision": 321, + "deployedAt": 1600000000, "zoneEndpoints": { "dev.aws-us-east-1c": { "default": "https://dev.endpoint:443/" diff --git a/tenant-cd-api/abi-spec.json b/tenant-cd-api/abi-spec.json index 48222a63e28..8eee085de08 100644 --- a/tenant-cd-api/abi-spec.json +++ b/tenant-cd-api/abi-spec.json @@ -8,7 +8,10 @@ "abstract" ], "methods": [ - "public abstract ai.vespa.hosted.cd.Endpoint endpoint(java.lang.String)" + "public abstract ai.vespa.hosted.cd.Endpoint endpoint(java.lang.String)", + "public abstract java.lang.String platformVersion()", + "public abstract long applicationVersion()", + "public abstract java.time.Instant deployedAt()" ], "fields": [] }, @@ -138,7 +141,8 @@ "methods": [ "public static ai.vespa.hosted.cd.TestRuntime get()", "public abstract ai.vespa.hosted.cd.Deployment deploymentToTest()", - "public abstract ai.vespa.cloud.Zone zone()" + "public abstract ai.vespa.cloud.Zone zone()", + "public abstract ai.vespa.cloud.ApplicationId application()" ], "fields": [ "public static final java.util.logging.Logger logger" diff --git a/tenant-cd-api/src/main/java/ai/vespa/hosted/cd/Deployment.java b/tenant-cd-api/src/main/java/ai/vespa/hosted/cd/Deployment.java index 9d42ba138bd..a04f07d5dae 100644 --- a/tenant-cd-api/src/main/java/ai/vespa/hosted/cd/Deployment.java +++ b/tenant-cd-api/src/main/java/ai/vespa/hosted/cd/Deployment.java @@ -1,6 +1,8 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.hosted.cd; +import java.time.Instant; + /** * A deployment of a Vespa application, which contains endpoints for document retrieval. * @@ -11,4 +13,13 @@ public interface Deployment { /** Returns an Endpoint in the cluster with the given id. */ Endpoint endpoint(String id); + /** The Vespa runtime version of the deployment, e.g., 8.16.32. */ + String platformVersion(); + + /** The build number assigned to the application revision of the deployment, e.g., 496. */ + long applicationVersion(); + + /** The time at which the deployment was last updated with a new platform or application version. */ + Instant deployedAt(); + } diff --git a/tenant-cd-api/src/main/java/ai/vespa/hosted/cd/TestRuntime.java b/tenant-cd-api/src/main/java/ai/vespa/hosted/cd/TestRuntime.java index d031054851c..b1aa2bb62ba 100644 --- a/tenant-cd-api/src/main/java/ai/vespa/hosted/cd/TestRuntime.java +++ b/tenant-cd-api/src/main/java/ai/vespa/hosted/cd/TestRuntime.java @@ -1,6 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.hosted.cd; +import ai.vespa.cloud.ApplicationId; import ai.vespa.cloud.Zone; import ai.vespa.hosted.cd.internal.TestRuntimeProvider; @@ -33,4 +34,6 @@ public interface TestRuntime { Zone zone(); + ApplicationId application(); + } diff --git a/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/HttpDeployment.java b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/HttpDeployment.java index 1abdc4d4297..15f6b1f009a 100644 --- a/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/HttpDeployment.java +++ b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/HttpDeployment.java @@ -6,6 +6,7 @@ import ai.vespa.hosted.cd.Endpoint; import ai.vespa.hosted.cd.EndpointAuthenticator; import java.net.URI; +import java.time.Instant; import java.util.Map; import java.util.NoSuchElementException; import java.util.stream.Collectors; @@ -18,9 +19,16 @@ import java.util.stream.Collectors; public class HttpDeployment implements Deployment { private final Map<String, Endpoint> endpoints; + private final String platform; + private final long revision; + private final Instant deployedAt; /** Creates a representation of the given deployment endpoints, using the authenticator for data plane access. */ - public HttpDeployment(Map<String, URI> endpoints, EndpointAuthenticator authenticator) { + public HttpDeployment(String platform, long revision, Instant deployedAt, + Map<String, URI> endpoints, EndpointAuthenticator authenticator) { + this.platform = platform; + this.revision = revision; + this.deployedAt = deployedAt; this.endpoints = endpoints.entrySet().stream() .collect(Collectors.toUnmodifiableMap(entry -> entry.getKey(), entry -> new HttpEndpoint(entry.getValue(), authenticator))); @@ -34,4 +42,19 @@ public class HttpDeployment implements Deployment { return endpoints.get(id); } + @Override + public String platformVersion() { + return platform; + } + + @Override + public long applicationVersion() { + return revision; + } + + @Override + public Instant deployedAt() { + return deployedAt; + } + } diff --git a/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/UsingTestRuntimeTest.java b/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/UsingTestRuntimeTest.java index acb41bb42be..be60ef554af 100644 --- a/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/UsingTestRuntimeTest.java +++ b/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/UsingTestRuntimeTest.java @@ -17,6 +17,9 @@ public class UsingTestRuntimeTest { assertEquals(Environment.test, runtime.zone().environment()); assertEquals("name", runtime.zone().region()); assertNull(runtime.deploymentToTest().endpoint("dummy")); + assertEquals("1.2.3", runtime.deploymentToTest().platformVersion()); + assertEquals(321, runtime.deploymentToTest().applicationVersion()); + assertEquals(1000, runtime.deploymentToTest().deployedAt().toEpochMilli()); } } diff --git a/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/JunitRunnerTest.java b/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/JunitRunnerTest.java index 8e330b0228e..51acd4b1fdf 100644 --- a/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/JunitRunnerTest.java +++ b/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/JunitRunnerTest.java @@ -1,8 +1,10 @@ package com.yahoo.vespa.testrunner; +import ai.vespa.cloud.ApplicationId; import ai.vespa.cloud.Environment; import ai.vespa.cloud.Zone; import ai.vespa.hosted.cd.Deployment; +import ai.vespa.hosted.cd.Endpoint; import ai.vespa.hosted.cd.TestRuntime; import com.yahoo.vespa.testrunner.TestReport.Status; import com.yahoo.vespa.testrunner.TestRunner.Suite; @@ -169,7 +171,12 @@ class JunitRunnerTest { @Override public Deployment deploymentToTest() { - return __ -> null; + return new Deployment() { + @Override public Endpoint endpoint(String id) { return null; } + @Override public String platformVersion() { return "1.2.3"; } + @Override public long applicationVersion() { return 321; } + @Override public Instant deployedAt() { return Instant.ofEpochMilli(1000); } + }; } @Override @@ -177,6 +184,11 @@ class JunitRunnerTest { return new Zone(Environment.test, "name"); } + @Override + public ApplicationId application() { + return null; + } + } } |