aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java153
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java337
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java131
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java149
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedInstance.java221
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java129
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobList.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java234
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java67
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java38
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java81
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java27
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java67
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java57
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java49
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java109
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java106
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java113
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java60
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java78
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java78
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java287
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java33
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java32
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java231
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java35
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java108
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainerTest.java9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java35
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java55
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java38
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java138
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java250
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/InstanceSerializerTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-application.json970
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-instance.json534
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java102
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java44
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java18
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java6
68 files changed, 3152 insertions, 2317 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java
index 600db544552..0f5b7f154e1 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java
@@ -8,6 +8,7 @@ import java.util.Objects;
/**
* @author jonmv
*/
+// TODO jonmv: Remove this.
public interface BuildService {
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index 43f9b851fa2..10ce3b93614 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -1,27 +1,24 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
-import com.google.common.collect.ImmutableMap;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.vespa.hosted.controller.application.ApplicationActivity;
-import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
-import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.time.Instant;
-import java.util.Collections;
+import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -29,7 +26,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
-import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -41,66 +37,80 @@ import java.util.stream.Collectors;
*/
public class Application {
- private final ApplicationId id;
+ private final TenantAndApplicationId id;
private final Instant createdAt;
private final DeploymentSpec deploymentSpec;
private final ValidationOverrides validationOverrides;
- private final Map<ZoneId, Deployment> deployments;
- private final DeploymentJobs deploymentJobs;
+ private final OptionalLong projectId;
+ private final boolean internal;
private final Change change;
private final Change outstandingChange;
+ private final Optional<IssueId> deploymentIssueId;
private final Optional<IssueId> ownershipIssueId;
private final Optional<User> owner;
private final OptionalInt majorVersion;
private final ApplicationMetrics metrics;
private final Optional<String> pemDeployKey;
- private final List<AssignedRotation> rotations;
- private final RotationStatus rotationStatus;
+ private final Map<InstanceName, Instance> instances;
/** Creates an empty application. */
- public Application(ApplicationId id, Instant now) {
- this(id, now, DeploymentSpec.empty, ValidationOverrides.empty, Collections.emptyMap(),
- new DeploymentJobs(OptionalLong.empty(), Collections.emptyList(), Optional.empty(), false),
- Change.empty(), Change.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(),
- new ApplicationMetrics(0, 0),
- Optional.empty(), Collections.emptyList(), RotationStatus.EMPTY);
- }
-
- /** Used from persistence layer: Do not use */
- public Application(ApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
- List<Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
- Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
- OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- List<AssignedRotation> rotations, RotationStatus rotationStatus) {
- this(id, createdAt, deploymentSpec, validationOverrides,
- deployments.stream().collect(Collectors.toMap(Deployment::zone, Function.identity())),
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotations, rotationStatus);
- }
-
- Application(ApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
- Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
- Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
- OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- List<AssignedRotation> rotations, RotationStatus rotationStatus) {
+ public Application(TenantAndApplicationId id, Instant now) {
+ this(id, now, DeploymentSpec.empty, ValidationOverrides.empty, Change.empty(), Change.empty(),
+ Optional.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(),
+ new ApplicationMetrics(0, 0), Optional.empty(), OptionalLong.empty(), true, List.of());
+ }
+
+ // DO NOT USE! For serialization purposes, only.
+ public Application(TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
+ Change change, Change outstandingChange, Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
+ OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey, OptionalLong projectId,
+ boolean internal, Collection<Instance> instances) {
this.id = Objects.requireNonNull(id, "id cannot be null");
this.createdAt = Objects.requireNonNull(createdAt, "instant of creation cannot be null");
this.deploymentSpec = Objects.requireNonNull(deploymentSpec, "deploymentSpec cannot be null");
this.validationOverrides = Objects.requireNonNull(validationOverrides, "validationOverrides cannot be null");
- this.deployments = ImmutableMap.copyOf(Objects.requireNonNull(deployments, "deployments cannot be null"));
- this.deploymentJobs = Objects.requireNonNull(deploymentJobs, "deploymentJobs cannot be null");
this.change = Objects.requireNonNull(change, "change cannot be null");
this.outstandingChange = Objects.requireNonNull(outstandingChange, "outstandingChange cannot be null");
+ this.deploymentIssueId = Objects.requireNonNull(deploymentIssueId, "deploymentIssueId cannot be null");
this.ownershipIssueId = Objects.requireNonNull(ownershipIssueId, "ownershipIssueId cannot be null");
this.owner = Objects.requireNonNull(owner, "owner cannot be null");
this.majorVersion = Objects.requireNonNull(majorVersion, "majorVersion cannot be null");
this.metrics = Objects.requireNonNull(metrics, "metrics cannot be null");
- this.pemDeployKey = pemDeployKey;
- this.rotations = List.copyOf(Objects.requireNonNull(rotations, "rotations cannot be null"));
- this.rotationStatus = Objects.requireNonNull(rotationStatus, "rotationStatus cannot be null");
+ this.pemDeployKey = Objects.requireNonNull(pemDeployKey, "pemDeployKey cannot be null");
+ this.projectId = Objects.requireNonNull(projectId, "projectId cannot be null");
+ this.internal = internal;
+ this.instances = instances.stream().collect(Collectors.toUnmodifiableMap(instance -> instance.id().instance(),
+ instance -> instance));
+ }
+
+ /** Returns an aggregate application, from the given instances, if at least one. */
+ public static Optional<Application> aggregate(List<Instance> instances) {
+ if (instances.isEmpty())
+ return Optional.empty();
+
+ Instance base = instances.stream()
+ .filter(instance -> instance.id().instance().isDefault())
+ .findFirst()
+ .orElse(instances.iterator().next());
+
+ return Optional.of(new Application(TenantAndApplicationId.from(base.id()), base.createdAt(), base.deploymentSpec(),
+ base.validationOverrides(), base.change(), base.outstandingChange(),
+ base.deploymentJobs().issueId(), base.ownershipIssueId(), base.owner(),
+ base.majorVersion(), base.metrics(), base.pemDeployKey(),
+ base.deploymentJobs().projectId(), base.deploymentJobs().deployedInternally(), instances));
+ }
+
+ /** Returns an old Instance representation of this and the given instance, for serialisation. */
+ public Instance legacy(InstanceName instance) {
+ Instance base = require(instance);
+
+ return new Instance(base.id(), createdAt, deploymentSpec, validationOverrides, base.deployments(),
+ new DeploymentJobs(projectId, base.deploymentJobs().jobStatus().values(), deploymentIssueId, internal),
+ change, outstandingChange, ownershipIssueId, owner,
+ majorVersion, metrics, pemDeployKey, base.rotations(), base.rotationStatus());
}
- public ApplicationId id() { return id; }
+ public TenantAndApplicationId id() { return id; }
public Instant createdAt() { return createdAt; }
@@ -110,6 +120,13 @@ public class Application {
*/
public DeploymentSpec deploymentSpec() { return deploymentSpec; }
+ /** Returns the project id of this application, if it has any. */
+ public OptionalLong projectId() { return projectId; }
+
+ /** Returns whether this application is run on the internal deployment pipeline. */
+ // TODO jonmv: Remove, as will be always true.
+ public boolean internal() { return internal; }
+
/**
* Returns the last deployed validation overrides of this application,
* or the empty validation overrides if it has never been deployed
@@ -117,20 +134,16 @@ public class Application {
*/
public ValidationOverrides validationOverrides() { return validationOverrides; }
- /** Returns an immutable map of the current deployments of this */
- public Map<ZoneId, Deployment> deployments() { return deployments; }
+ /** Returns the instances of this application */
+ public Map<InstanceName, Instance> instances() { return instances; }
- /**
- * Returns an immutable map of the current *production* deployments of this
- * (deployments also includes manually deployed environments)
- */
- public Map<ZoneId, Deployment> productionDeployments() {
- return ImmutableMap.copyOf(deployments.values().stream()
- .filter(deployment -> deployment.zone().environment() == Environment.prod)
- .collect(Collectors.toMap(Deployment::zone, Function.identity())));
- }
+ /** Returns the instance with the given name, if it exists. */
+ public Optional<Instance> get(InstanceName instance) { return Optional.ofNullable(instances.get(instance)); }
- public DeploymentJobs deploymentJobs() { return deploymentJobs; }
+ /** Returns the instance with the given name, or throws. */
+ public Instance require(InstanceName instance) {
+ return get(instance).orElseThrow(() -> new IllegalArgumentException("Unknown instance '" + instance + "'"));
+ }
/**
* Returns base change for this application, i.e., the change that is deployed outside block windows.
@@ -144,6 +157,11 @@ public class Application {
*/
public Change outstandingChange() { return outstandingChange; }
+ /** Returns ID of any open deployment issue filed for this */
+ public Optional<IssueId> deploymentIssueId() {
+ return deploymentIssueId;
+ }
+
/** Returns ID of the last ownership issue filed for this */
public Optional<IssueId> ownershipIssueId() {
return ownershipIssueId;
@@ -166,19 +184,26 @@ public class Application {
/** Returns activity for this */
public ApplicationActivity activity() {
- return ApplicationActivity.from(deployments.values());
+ return ApplicationActivity.from(instances.values().stream()
+ .flatMap(instance -> instance.deployments().values().stream())
+ .collect(Collectors.toUnmodifiableList()));
}
+ public Map<InstanceName, List<Deployment>> productionDeployments() {
+ return instances.values().stream()
+ .collect(Collectors.toUnmodifiableMap(Instance::name,
+ instance -> List.copyOf(instance.productionDeployments().values())));
+ }
/**
* Returns the oldest platform version this has deployed in a permanent zone (not test or staging).
*
- * This is unfortunately quite similar to {@link ApplicationController#oldestInstalledPlatform(ApplicationId)},
+ * This is unfortunately quite similar to {@link ApplicationController#oldestInstalledPlatform(TenantAndApplicationId)},
* but this checks only what the controller has deployed to the production zones, while that checks the node repository
* to see what's actually installed on each node. Thus, this is the right choice for, e.g., target Vespa versions for
* new deployments, while that is the right choice for version to compile against.
*/
public Optional<Version> oldestDeployedPlatform() {
- return productionDeployments().values().stream()
+ return productionDeployments().values().stream().flatMap(List::stream)
.map(Deployment::version)
.min(Comparator.naturalOrder());
}
@@ -187,23 +212,13 @@ public class Application {
* Returns the oldest application version this has deployed in a permanent zone (not test or staging).
*/
public Optional<ApplicationVersion> oldestDeployedApplication() {
- return productionDeployments().values().stream()
+ return productionDeployments().values().stream().flatMap(List::stream)
.map(Deployment::applicationVersion)
.min(Comparator.naturalOrder());
}
- /** Returns all rotations assigned to this */
- public List<AssignedRotation> rotations() {
- return rotations;
- }
-
public Optional<String> pemDeployKey() { return pemDeployKey; }
- /** Returns the status of the global rotation(s) assigned to this */
- public RotationStatus rotationStatus() {
- return rotationStatus;
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 5c629173f3d..ab0a110be29 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
@@ -10,6 +10,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
@@ -100,6 +101,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Node.State.active;
+import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Node.State.inactive;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Node.State.reserved;
import static java.util.Comparator.naturalOrder;
@@ -150,10 +152,11 @@ public class ApplicationController {
// Update serialization format of all applications
Once.after(Duration.ofMinutes(1), () -> {
+ curator.clearInstanceRoot();
Instant start = clock.instant();
int count = 0;
- for (Instance instance : curator.readInstances()) {
- lockIfPresent(instance.id(), this::store);
+ for (Application application : curator.readApplications()) {
+ lockApplicationIfPresent(application.id(), this::store);
count++;
}
log.log(Level.INFO, String.format("Wrote %d applications in %s", count,
@@ -164,13 +167,20 @@ public class ApplicationController {
}
/** Returns the application with the given id, or null if it is not present */
- public Optional<Application> getApplication(ApplicationId id) {
+ public Optional<Application> getApplication(TenantAndApplicationId id) {
return curator.readApplication(id);
}
+ /** Returns the application with the given id, or null if it is not present */
+ // TODO jonmv: remove
+ public Optional<Application> getApplication(ApplicationId id) {
+ return getApplication(TenantAndApplicationId.from(id));
+ }
+
/** Returns the instance with the given id, or null if it is not present */
- public Optional<Instance> get(ApplicationId id) {
- return curator.readInstance(id);
+ // TODO jonmv: remove or inline
+ public Optional<Instance> getInstance(ApplicationId id) {
+ return getApplication(id).flatMap(application -> application.get(id.instance()));
}
/**
@@ -178,7 +188,7 @@ public class ApplicationController {
*
* @throws IllegalArgumentException if it does not exist
*/
- public Application requireApplication(ApplicationId id) {
+ public Application requireApplication(TenantAndApplicationId id) {
return getApplication(id).orElseThrow(() -> new IllegalArgumentException(id + " not found"));
}
@@ -187,48 +197,37 @@ public class ApplicationController {
*
* @throws IllegalArgumentException if it does not exist
*/
- public Instance require(ApplicationId id) {
- return get(id).orElseThrow(() -> new IllegalArgumentException(id + " not found"));
+ // TODO jonvm: remove or inline
+ public Instance requireInstance(ApplicationId id) {
+ return getInstance(id).orElseThrow(() -> new IllegalArgumentException(id + " not found"));
}
/** Returns a snapshot of all applications */
- public List<Application> applicationList() {
+ public List<Application> asList() {
return curator.readApplications();
}
- /** Returns all applications of a tenant */
- public List<Application> applicationList(TenantName tenant) {
+ /** Returns a snapshot of all applications of a tenant */
+ public List<Application> asList(TenantName tenant) {
return curator.readApplications(tenant);
}
- /** Returns a snapshot of all instances */
- public List<Instance> asList() {
- return curator.readInstances();
- }
-
- /** Returns all instances of a tenant */
- public List<Instance> asList(TenantName tenant) {
- return curator.readInstances(tenant);
- }
-
- /** Returns all instances of an application */
- public List<Instance> asList(TenantAndApplicationId id) {
- return curator.readInstances(id);
- }
-
public ArtifactRepository artifacts() { return artifactRepository; }
public ApplicationStore applicationStore() { return applicationStore; }
/** Returns the oldest Vespa version installed on any active or reserved production node for the given application. */
- public Version oldestInstalledPlatform(ApplicationId id) {
- return asList(TenantAndApplicationId.from(id)).stream()
- .flatMap(instance -> instance.productionDeployments().keySet().stream()
- .flatMap(zone -> configServer.nodeRepository().list(zone, id, EnumSet.of(active, reserved)).stream())
- .map(Node::currentVersion)
- .filter(version -> ! version.isEmpty()))
- .min(naturalOrder())
- .orElse(controller.systemVersion());
+ public Version oldestInstalledPlatform(TenantAndApplicationId id) {
+ return requireApplication(id).instances().values().stream()
+ .flatMap(instance -> instance.productionDeployments().keySet().stream()
+ .flatMap(zone -> configServer.nodeRepository().list(zone,
+ id.instance(instance.name()),
+ EnumSet.of(active, reserved))
+ .stream())
+ .map(Node::currentVersion)
+ .filter(version -> ! version.isEmpty()))
+ .min(naturalOrder())
+ .orElse(controller.systemVersion());
}
/** Change the global endpoint status for given deployment */
@@ -267,21 +266,21 @@ public class ApplicationController {
*
* @throws IllegalArgumentException if the application already exists
*/
+ // TODO jonmv: split in create application and create instance
public Application createApplication(ApplicationId id, Optional<Credentials> credentials) {
if (id.instance().isTester())
throw new IllegalArgumentException("'" + id + "' is a tester application!");
- try (Lock lock = lock(TenantAndApplicationId.from(id));
- Lock oldLock = lock(id)) {
+ try (Lock lock = lock(TenantAndApplicationId.from(id))) {
// Validate only application names which do not already exist.
- if (asList(id.tenant()).stream().noneMatch(application -> application.id().application().equals(id.application())))
+ if (getApplication(TenantAndApplicationId.from(id)).isEmpty())
com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId.validate(id.application().value());
Optional<Tenant> tenant = controller.tenants().get(id.tenant());
if (tenant.isEmpty())
throw new IllegalArgumentException("Could not create '" + id + "': This tenant does not exist");
- if (get(id).isPresent())
+ if (getInstance(id).isPresent())
throw new IllegalArgumentException("Could not create '" + id + "': Application already exists");
- if (get(dashToUnderscore(id)).isPresent()) // VESPA-1945
+ if (getInstance(dashToUnderscore(id)).isPresent()) // VESPA-1945
throw new IllegalArgumentException("Could not create '" + id + "': Application " + dashToUnderscore(id) + " already exists");
if (tenant.get().type() != Tenant.Type.user) {
if (credentials.isEmpty())
@@ -290,7 +289,11 @@ public class ApplicationController {
if ( ! id.instance().isTester()) // Only store the application permits for non-user applications.
accessControl.createApplication(id, credentials.get());
}
- LockedApplication application = new LockedApplication(new Application(id, clock.instant()), lock);
+ List<Instance> instances = getApplication(TenantAndApplicationId.from(id)).map(application -> application.instances().values())
+ .map(ArrayList::new)
+ .orElse(new ArrayList<>());
+ instances.add(new Instance(id, clock.instant()));
+ LockedApplication application = new LockedApplication(Application.aggregate(instances).get(), lock);
store(application);
log.info("Created " + application);
return application.get();
@@ -316,7 +319,7 @@ public class ApplicationController {
// TODO jonmv: Change this to create instances on demand.
Tenant tenant = controller.tenants().require(applicationId.tenant());
- if (tenant.type() == Tenant.Type.user && get(applicationId).isEmpty())
+ if (tenant.type() == Tenant.Type.user && getInstance(applicationId).isEmpty())
createApplication(applicationId, Optional.empty());
try (Lock deploymentLock = lockForDeployment(applicationId, zone)) {
@@ -326,9 +329,9 @@ public class ApplicationController {
Set<ContainerEndpoint> endpoints;
Optional<ApplicationCertificate> applicationCertificate;
- try (Lock lock = lock(TenantAndApplicationId.from(applicationId));
- Lock oldLock = lock(applicationId)) {
- LockedInstance instance = new LockedInstance(require(applicationId), lock);
+ try (Lock lock = lock(TenantAndApplicationId.from(applicationId))) {
+ LockedApplication application = new LockedApplication(requireApplication(TenantAndApplicationId.from(applicationId)), lock);
+ InstanceName instance = applicationId.instance();
boolean manuallyDeployed = options.deployDirectly || zone.environment().isManuallyDeployed();
boolean preferOldestVersion = options.deployCurrentVersion;
@@ -338,14 +341,15 @@ public class ApplicationController {
applicationVersion = applicationVersionFromDeployer.orElse(ApplicationVersion.unknown);
applicationPackage = applicationPackageFromDeployer.orElseThrow(
() -> new IllegalArgumentException("Application package must be given when deploying to " + zone));
- platformVersion = options.vespaVersion.map(Version::new).orElse(applicationPackage.deploymentSpec().majorVersion()
- .flatMap(this::lastCompatibleVersion)
- .orElseGet(controller::systemVersion));
+ platformVersion = options.vespaVersion.map(Version::new)
+ .orElse(applicationPackage.deploymentSpec().majorVersion()
+ .flatMap(this::lastCompatibleVersion)
+ .orElseGet(controller::systemVersion));
}
else {
JobType jobType = JobType.from(controller.system(), zone)
.orElseThrow(() -> new IllegalArgumentException("No job is known for " + zone + "."));
- Optional<JobStatus> job = Optional.ofNullable(instance.get().deploymentJobs().jobStatus().get(jobType));
+ Optional<JobStatus> job = Optional.ofNullable(application.get().require(instance).deploymentJobs().jobStatus().get(jobType));
if ( job.isEmpty()
|| job.get().lastTriggered().isEmpty()
|| job.get().lastCompleted().isPresent() && job.get().lastCompleted().get().at().isAfter(job.get().lastTriggered().get().at()))
@@ -356,34 +360,32 @@ public class ApplicationController {
applicationVersion = preferOldestVersion ? triggered.sourceApplication().orElse(triggered.application())
: triggered.application();
- applicationPackage = getApplicationPackage(instance.get(), applicationVersion);
+ applicationPackage = getApplicationPackage(applicationId, application.get().internal(), applicationVersion);
applicationPackage = withTesterCertificate(applicationPackage, applicationId, jobType);
- validateRun(instance.get(), zone, platformVersion, applicationVersion);
+ validateRun(application.get(), instance, zone, platformVersion, applicationVersion);
}
// TODO jonmv: Remove this when all packages are validated upon submission, as in ApplicationApiHandler.submit(...).
verifyApplicationIdentityConfiguration(applicationId.tenant(), applicationPackage, deployingIdentity);
- // Assign and register endpoints
- instance = withRotation(instance, zone);
- endpoints = registerEndpointsInDns(instance.get(), zone);
+ if (zone.environment().isProduction()) // Assign and register endpoints
+ application = withRotation(application, instance);
+
+ endpoints = registerEndpointsInDns(application.get().deploymentSpec(), application.get().require(applicationId.instance()), zone);
+
if (controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) {
- // Get application certificate (provisions a new certificate if missing)
- List<? extends ZoneApi> zones = controller.zoneRegistry().zones().all().zones();
- applicationCertificate = getApplicationCertificate(instance.get());
+ // Provisions a new certificate if missing
+ applicationCertificate = getApplicationCertificate(application.get().require(instance));
} else {
applicationCertificate = Optional.empty();
}
// TODO jonmv: REMOVE! This is now irrelevant for non-CD-test deployments and non-unit tests.
if ( ! preferOldestVersion
- && ! instance.get().deploymentJobs().deployedInternally()
+ && ! application.get().internal()
&& ! zone.environment().isManuallyDeployed()) {
- // TODO(jvenstad): Store only on submissions
- ApplicationPackage finalApplicationPackage = applicationPackage;
- lockApplicationOrThrow(applicationId, lockedApplication ->
- storeWithUpdatedConfig(lockedApplication, finalApplicationPackage));
+ storeWithUpdatedConfig(application, applicationPackage);
}
} // Release application lock while doing the deployment, which is a lengthy task.
@@ -392,9 +394,10 @@ public class ApplicationController {
ActivateResult result = deploy(applicationId, applicationPackage, zone, options, endpoints,
applicationCertificate.orElse(null));
- lockOrThrow(applicationId, instance ->
- store(instance.withNewDeployment(zone, applicationVersion, platformVersion, clock.instant(),
- warningsFrom(result))));
+ lockApplicationOrThrow(TenantAndApplicationId.from(applicationId), application ->
+ store(application.with(applicationId.instance(),
+ instance -> instance.withNewDeployment(zone, applicationVersion, platformVersion,
+ clock.instant(), warningsFrom(result)))));
return result;
}
}
@@ -413,19 +416,19 @@ public class ApplicationController {
}
/** Fetches the requested application package from the artifact store(s). */
- public ApplicationPackage getApplicationPackage(Instance instance, ApplicationVersion version) {
+ public ApplicationPackage getApplicationPackage(ApplicationId id, boolean internal, ApplicationVersion version) {
try {
- return instance.deploymentJobs().deployedInternally()
- ? new ApplicationPackage(applicationStore.get(instance.id(), version))
- : new ApplicationPackage(artifactRepository.getApplicationPackage(instance.id(), version.id()));
+ return internal
+ ? new ApplicationPackage(applicationStore.get(id, version))
+ : new ApplicationPackage(artifactRepository.getApplicationPackage(id, version.id()));
}
catch (RuntimeException e) { // If application has switched deployment pipeline, artifacts stored prior to the switch are in the other artifact store.
try {
- log.info("Fetching application package for " + instance.id() + " from alternate repository; it is now deployed "
- + (instance.deploymentJobs().deployedInternally() ? "internally" : "externally") + "\nException was: " + Exceptions.toMessageString(e));
- return instance.deploymentJobs().deployedInternally()
- ? new ApplicationPackage(artifactRepository.getApplicationPackage(instance.id(), version.id()))
- : new ApplicationPackage(applicationStore.get(instance.id(), version));
+ log.info("Fetching application package for " + id + " from alternate repository; it is now deployed "
+ + (internal ? "internally" : "externally") + "\nException was: " + Exceptions.toMessageString(e));
+ return internal
+ ? new ApplicationPackage(artifactRepository.getApplicationPackage(id, version.id()))
+ : new ApplicationPackage(applicationStore.get(id, version));
}
catch (RuntimeException s) { // If this fails, too, the first failure is most likely the relevant one.
e.addSuppressed(s);
@@ -441,21 +444,17 @@ public class ApplicationController {
application = application.with(applicationPackage.deploymentSpec());
application = application.with(applicationPackage.validationOverrides());
- store(application);
-
// Delete zones not listed in DeploymentSpec, if allowed
// We do this at deployment time for externally built applications, and at submission time
// for internally built ones, to be able to return a validation failure message when necessary
- for (Instance instance : asList(TenantAndApplicationId.from(application.get().id()))) {
- lockOrThrow(instance.id(), locked -> {
- locked = withoutDeletedDeployments(locked, applicationPackage);
+ for (InstanceName instanceName : application.get().instances().keySet()) {
+ application = withoutDeletedDeployments(application, instanceName);
// Clean up deployment jobs that are no longer referenced by deployment spec
- locked = withoutUnreferencedDeploymentJobs(locked, applicationPackage);
-
- store(locked);
- });
+ DeploymentSpec deploymentSpec = application.get().deploymentSpec();
+ application = application.with(instanceName, instance -> withoutUnreferencedDeploymentJobs(deploymentSpec, instance));
}
+ store(application);
}
/** Deploy a system application to given zone */
@@ -503,13 +502,13 @@ public class ApplicationController {
}
/** Makes sure the application has a global rotation, if eligible. */
- private LockedInstance withRotation(LockedInstance application, ZoneId zone) {
- if (zone.environment() == Environment.prod) {
- try (RotationLock rotationLock = rotationRepository.lock()) {
- var rotations = rotationRepository.getOrAssignRotations(application.get(), rotationLock);
- application = application.with(rotations);
- store(application); // store assigned rotation even if deployment fails
- }
+ private LockedApplication withRotation(LockedApplication application, InstanceName instanceName) {
+ try (RotationLock rotationLock = rotationRepository.lock()) {
+ var rotations = rotationRepository.getOrAssignRotations(application.get().deploymentSpec(),
+ application.get().require(instanceName),
+ rotationLock);
+ application = application.with(instanceName, instance -> instance.with(rotations));
+ store(application); // store assigned rotation even if deployment fails
}
return application;
}
@@ -519,9 +518,9 @@ public class ApplicationController {
*
* @return the registered endpoints
*/
- private Set<ContainerEndpoint> registerEndpointsInDns(Instance instance, ZoneId zone) {
+ private Set<ContainerEndpoint> registerEndpointsInDns(DeploymentSpec deploymentSpec, Instance instance, ZoneId zone) {
var containerEndpoints = new HashSet<ContainerEndpoint>();
- var registerLegacyNames = instance.deploymentSpec().globalServiceId().isPresent();
+ var registerLegacyNames = deploymentSpec.globalServiceId().isPresent();
for (var assignedRotation : instance.rotations()) {
var names = new ArrayList<String>();
var endpoints = instance.endpointsIn(controller.system(), assignedRotation.endpointId())
@@ -610,16 +609,17 @@ public class ApplicationController {
return new ActivateResult(new RevisionId("0"), prepareResponse, 0);
}
- private LockedInstance withoutDeletedDeployments(LockedInstance instance, ApplicationPackage applicationPackage) {
- List<Deployment> deploymentsToRemove = instance.get().productionDeployments().values().stream()
- .filter(deployment -> ! applicationPackage.deploymentSpec().includes(deployment.zone().environment(),
- Optional.of(deployment.zone().region())))
- .collect(Collectors.toList());
+ private LockedApplication withoutDeletedDeployments(LockedApplication application, InstanceName instance) {
+ DeploymentSpec deploymentSpec = application.get().deploymentSpec();
+ List<Deployment> deploymentsToRemove = application.get().require(instance).productionDeployments().values().stream()
+ .filter(deployment -> ! deploymentSpec.includes(deployment.zone().environment(),
+ Optional.of(deployment.zone().region())))
+ .collect(Collectors.toList());
- if (deploymentsToRemove.isEmpty()) return instance;
+ if (deploymentsToRemove.isEmpty()) return application;
- if ( ! applicationPackage.validationOverrides().allows(ValidationId.deploymentRemoval, clock.instant()))
- throw new IllegalArgumentException(ValidationId.deploymentRemoval.value() + ": " + instance.get() +
+ if ( ! application.get().validationOverrides().allows(ValidationId.deploymentRemoval, clock.instant()))
+ throw new IllegalArgumentException(ValidationId.deploymentRemoval.value() + ": " + application.get().require(instance) +
" is deployed in " +
deploymentsToRemove.stream()
.map(deployment -> deployment.zone().region().value())
@@ -629,16 +629,15 @@ public class ApplicationController {
" in deployment.xml. " +
ValidationOverrides.toAllowMessage(ValidationId.deploymentRemoval));
- LockedInstance instanceWithRemoval = instance;
for (Deployment deployment : deploymentsToRemove)
- instanceWithRemoval = deactivate(instanceWithRemoval, deployment.zone());
- return instanceWithRemoval;
+ application = deactivate(application, instance, deployment.zone());
+ return application;
}
- private LockedInstance withoutUnreferencedDeploymentJobs(LockedInstance instance, ApplicationPackage applicationPackage) {
- for (JobType job : JobList.from(instance.get()).production().mapToList(JobStatus::type)) {
+ private Instance withoutUnreferencedDeploymentJobs(DeploymentSpec deploymentSpec, Instance instance) {
+ for (JobType job : JobList.from(instance).production().mapToList(JobStatus::type)) {
ZoneId zone = job.zone(controller.system());
- if (applicationPackage.deploymentSpec().includes(zone.environment(), Optional.of(zone.region())))
+ if (deploymentSpec.includes(zone.environment(), Optional.of(zone.region())))
continue;
instance = instance.withoutDeploymentJob(job);
}
@@ -654,7 +653,7 @@ public class ApplicationController {
/** Returns the endpoints of the deployment, or empty if the request fails */
public List<URI> getDeploymentEndpoints(DeploymentId deploymentId) {
- if ( ! get(deploymentId.applicationId())
+ if ( ! getInstance(deploymentId.applicationId())
.map(application -> application.deployments().containsKey(deploymentId.zoneId()))
.orElse(deploymentId.applicationId().instance().isTester()))
throw new NotExistsException("Deployment", deploymentId.toString());
@@ -673,7 +672,7 @@ public class ApplicationController {
/** Returns the non-empty endpoints per cluster in the given deployment, or empty if endpoints can't be found. */
public Map<ClusterSpec.Id, URI> clusterEndpoints(DeploymentId id) {
- if ( ! get(id.applicationId())
+ if ( ! getInstance(id.applicationId())
.map(application -> application.deployments().containsKey(id.zoneId()))
.orElse(id.applicationId().instance().isTester()))
throw new NotExistsException("Deployment", id.toString());
@@ -715,15 +714,16 @@ public class ApplicationController {
throw new IllegalArgumentException("Could not delete application '" + tenantName + "." + applicationName + "': No credentials provided");
// Find all instances of the application
- List<ApplicationId> instances = asList(tenantName).stream()
- .map(Instance::id)
- .filter(id -> id.application().equals(applicationName))
- .collect(Collectors.toList());
+ TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName);
+ List<ApplicationId> instances = requireApplication(id).instances().keySet().stream()
+ .map(id::instance)
+ .collect(Collectors.toUnmodifiableList());
if (instances.size() > 1)
throw new IllegalArgumentException("Could not delete application; more than one instance present: " + instances);
// TODO: Make this one transaction when database is moved to ZooKeeper
- instances.forEach(id -> deleteInstance(id, credentials));
+ for (ApplicationId instance : instances)
+ deleteInstance(instance, credentials);
}
/**
@@ -737,22 +737,22 @@ public class ApplicationController {
if (tenant.type() != Tenant.Type.user && credentials.isEmpty())
throw new IllegalArgumentException("Could not delete application '" + applicationId + "': No credentials provided");
- if (controller.applications().get(applicationId).isEmpty()) {
+ if (getInstance(applicationId).isEmpty())
throw new NotExistsException("Could not delete application '" + applicationId + "': Application not found");
- }
- lockOrThrow(applicationId, application -> {
- if ( ! application.get().deployments().isEmpty())
+ lockApplicationOrThrow(TenantAndApplicationId.from(applicationId), application -> {
+ if ( ! application.get().require(applicationId.instance()).deployments().isEmpty())
throw new IllegalArgumentException("Could not delete '" + application + "': It has active deployments in: " +
- application.get().deployments().keySet().stream().map(ZoneId::toString)
+ application.get().require(applicationId.instance()).deployments().keySet().stream().map(ZoneId::toString)
.sorted().collect(Collectors.joining(", ")));
- curator.removeInstance(applicationId);
+ curator.removeApplication(applicationId);
applicationStore.removeAll(applicationId);
applicationStore.removeAll(TesterId.of(applicationId));
- application.get().rotations().forEach(assignedRotation -> {
- var endpoints = application.get().endpointsIn(controller.system(), assignedRotation.endpointId());
+ Instance instance = application.get().require(applicationId.instance());
+ instance.rotations().forEach(assignedRotation -> {
+ var endpoints = instance.endpointsIn(controller.system(), assignedRotation.endpointId());
endpoints.asList().stream()
.map(Endpoint::dnsName)
.forEach(name -> {
@@ -764,10 +764,7 @@ public class ApplicationController {
});
- if ( tenant.type() != Tenant.Type.user
- && controller.applications().asList(applicationId.tenant()).stream()
- .map(application -> application.id().application())
- .noneMatch(applicationId.application()::equals))
+ if (tenant.type() != Tenant.Type.user && getApplication(applicationId).isEmpty())
// TODO jonmv: Implementations ignore the instance — refactor to provide tenant and application names only.
accessControl.deleteApplication(applicationId, credentials.get());
}
@@ -782,41 +779,18 @@ public class ApplicationController {
}
/**
- * Replace any previous version of this application by this instance
- *
- * @param application a locked application to store
- */
- public void store(LockedInstance application) {
- curator.writeInstance(application.get());
- }
-
- /**
* Acquire a locked application to modify and store, if there is an application with the given id.
*
* @param applicationId ID of the application to lock and get.
* @param action Function which acts on the locked application.
*/
- public void lockApplicationIfPresent(ApplicationId applicationId, Consumer<LockedApplication> action) {
- try (Lock lock = lock(TenantAndApplicationId.from(applicationId));
- Lock oldLock = lock(applicationId)) {
+ public void lockApplicationIfPresent(TenantAndApplicationId applicationId, Consumer<LockedApplication> action) {
+ try (Lock lock = lock(applicationId)) {
getApplication(applicationId).map(application -> new LockedApplication(application, lock)).ifPresent(action);
}
}
/**
- * Acquire a locked instance to modify and store, if there is an instance with the given id.
- *
- * @param applicationId ID of the instance to lock and get.
- * @param action Function which acts on the locked instance.
- */
- public void lockIfPresent(ApplicationId applicationId, Consumer<LockedInstance> action) {
- try (Lock lock = lock(TenantAndApplicationId.from(applicationId));
- Lock oldLock = lock(applicationId)) {
- get(applicationId).map(application -> new LockedInstance(application, lock)).ifPresent(action);
- }
- }
-
- /**
* Acquire a locked application to modify and store, or throw an exception if no application has the given id.
*
* WARNING: Uses only the "default" instance.
@@ -827,37 +801,8 @@ public class ApplicationController {
*/
public void lockApplicationOrThrow(TenantAndApplicationId applicationId, Consumer<LockedApplication> action) {
ApplicationId instanceId = applicationId.defaultInstance();
- try (Lock lock = lock(applicationId);
- Lock oldLock = lock(instanceId)) {
- action.accept(new LockedApplication(requireApplication(instanceId), lock));
- }
- }
-
- /**
- * Acquire a locked application to modify and store, or throw an exception if no application has the given id.
- *
- * @param applicationId ID of the application to lock and require.
- * @param action Function which acts on the locked application.
- * @throws IllegalArgumentException when application does not exist.
- */
- public void lockApplicationOrThrow(ApplicationId applicationId, Consumer<LockedApplication> action) {
- try (Lock lock = lock(TenantAndApplicationId.from(applicationId));
- Lock oldLock = lock(applicationId)) {
- action.accept(new LockedApplication(requireApplication(applicationId), lock));
- }
- }
-
- /**
- * Acquire a locked instance to modify and store, or throw an exception if no instance has the given id.
- *
- * @param applicationId ID of the instance to lock and require.
- * @param action Function which acts on the locked instance.
- * @throws IllegalArgumentException when instance does not exist.
- */
- public void lockOrThrow(ApplicationId applicationId, Consumer<LockedInstance> action) {
- try (Lock lock = lock(TenantAndApplicationId.from(applicationId));
- Lock oldLock = lock(applicationId)) {
- action.accept(new LockedInstance(require(applicationId), lock));
+ try (Lock lock = lock(applicationId)) {
+ action.accept(new LockedApplication(requireApplication(TenantAndApplicationId.from(instanceId)), lock));
}
}
@@ -886,8 +831,9 @@ public class ApplicationController {
}
/** Deactivate application in the given zone */
- public void deactivate(ApplicationId application, ZoneId zone) {
- lockOrThrow(application, lockedInstance -> store(deactivate(lockedInstance, zone)));
+ public void deactivate(ApplicationId id, ZoneId zone) {
+ lockApplicationOrThrow(TenantAndApplicationId.from(id),
+ application -> store(deactivate(application, id.instance(), zone)));
}
/**
@@ -895,15 +841,15 @@ public class ApplicationController {
*
* @return the application with the deployment in the given zone removed
*/
- private LockedInstance deactivate(LockedInstance instance, ZoneId zone) {
+ private LockedApplication deactivate(LockedApplication application, InstanceName instanceName, ZoneId zone) {
try {
- configServer.deactivate(new DeploymentId(instance.get().id(), zone));
+ configServer.deactivate(new DeploymentId(application.get().id().instance(instanceName), zone));
} catch (NotFoundException ignored) {
// ok; already gone
} finally {
- routingPolicies.refresh(instance.get().id(), instance.get().deploymentSpec(), zone);
+ routingPolicies.refresh(application.get().id().instance(instanceName), application.get().deploymentSpec(), zone);
}
- return instance.withoutDeploymentIn(zone);
+ return application.with(instanceName, instance -> instance.withoutDeploymentIn(zone));
}
public DeploymentTrigger deploymentTrigger() { return deploymentTrigger; }
@@ -924,15 +870,6 @@ public class ApplicationController {
}
/**
- * Returns a lock which provides exclusive rights to changing this application.
- * Any operation which stores an application need to first acquire this lock, then read, modify
- * and store the application, and finally release (close) the lock.
- */
- Lock lock(ApplicationId application) {
- return curator.lock(application);
- }
-
- /**
* Returns a lock which provides exclusive rights to deploying this application to the given zone.
*/
private Lock lockForDeployment(ApplicationId application, ZoneId zone) {
@@ -940,14 +877,14 @@ public class ApplicationController {
}
/** Verify that we don't downgrade an existing production deployment. */
- private void validateRun(Instance instance, ZoneId zone, Version platformVersion, ApplicationVersion applicationVersion) {
- Deployment deployment = instance.deployments().get(zone);
+ private void validateRun(Application application, InstanceName instance, ZoneId zone, Version platformVersion, ApplicationVersion applicationVersion) {
+ Deployment deployment = application.require(instance).deployments().get(zone);
if ( zone.environment().isProduction() && deployment != null
- && ( platformVersion.compareTo(deployment.version()) < 0 && ! instance.change().isPinned()
+ && ( platformVersion.compareTo(deployment.version()) < 0 && ! application.change().isPinned()
|| applicationVersion.compareTo(deployment.applicationVersion()) < 0))
- throw new IllegalArgumentException(String.format("Rejecting deployment of %s to %s, as the requested versions (platform: %s, application: %s)" +
+ throw new IllegalArgumentException(String.format("Rejecting deployment of application %s to %s, as the requested versions (platform: %s, application: %s)" +
" are older than the currently deployed (platform: %s, application: %s).",
- instance, zone, platformVersion, applicationVersion, deployment.version(), deployment.applicationVersion()));
+ application.id().instance(instance), zone, platformVersion, applicationVersion, deployment.version(), deployment.applicationVersion()));
}
/** Returns the rotation repository, used for managing global rotation assignments */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
index 60d74e3719d..70815beea8b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
@@ -6,25 +6,33 @@ import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.vespa.hosted.controller.application.ApplicationActivity;
import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
+import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
+import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
+import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
+import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
import java.time.Instant;
import java.util.Collections;
import java.util.Comparator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -68,6 +76,18 @@ public class Instance {
Optional.empty(), Collections.emptyList(), RotationStatus.EMPTY);
}
+ /** Creates an empty instance*/
+ public Instance(ApplicationId id, List<Deployment> deployments, DeploymentJobs deploymentJobs,
+ List<AssignedRotation> rotations, RotationStatus rotationStatus) {
+ this(id,
+ Instant.EPOCH, DeploymentSpec.empty, ValidationOverrides.empty,
+ deployments.stream().collect(Collectors.toMap(Deployment::zone, Function.identity())),
+ deploymentJobs,
+ Change.empty(), Change.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(),
+ new ApplicationMetrics(0, 0), Optional.empty(),
+ rotations, rotationStatus);
+ }
+
/** Used from persistence layer: Do not use */
public Instance(ApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
List<Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
@@ -102,8 +122,119 @@ public class Instance {
this.rotationStatus = Objects.requireNonNull(rotationStatus, "rotationStatus cannot be null");
}
+ public Instance withJobPause(JobType jobType, OptionalLong pausedUntil) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.withPause(jobType, pausedUntil), change, outstandingChange,
+ ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ rotations, rotationStatus);
+ }
+
+ public Instance withJobCompletion(JobType jobType, JobStatus.JobRun completion,
+ Optional<DeploymentJobs.JobError> jobError) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.withCompletion(jobType, completion, jobError),
+ change, outstandingChange, ownershipIssueId, owner, majorVersion, metrics,
+ pemDeployKey, rotations, rotationStatus);
+ }
+
+ public Instance withJobTriggering(JobType jobType, JobStatus.JobRun job) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.withTriggering(jobType, job), change, outstandingChange,
+ ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ rotations, rotationStatus);
+ }
+
+ public Instance withNewDeployment(ZoneId zone, ApplicationVersion applicationVersion, Version version,
+ Instant instant, Map<DeploymentMetrics.Warning, Integer> warnings) {
+ // Use info from previous deployment if available, otherwise create a new one.
+ Deployment previousDeployment = deployments.getOrDefault(zone, new Deployment(zone, applicationVersion,
+ version, instant));
+ Deployment newDeployment = new Deployment(zone, applicationVersion, version, instant,
+ previousDeployment.clusterUtils(),
+ previousDeployment.clusterInfo(),
+ previousDeployment.metrics().with(warnings),
+ previousDeployment.activity());
+ return with(newDeployment);
+ }
+
+ public Instance withClusterUtilization(ZoneId zone, Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization) {
+ Deployment deployment = deployments.get(zone);
+ if (deployment == null) return this; // No longer deployed in this zone.
+ return with(deployment.withClusterUtils(clusterUtilization));
+ }
+
+ public Instance withClusterInfo(ZoneId zone, Map<ClusterSpec.Id, ClusterInfo> clusterInfo) {
+ Deployment deployment = deployments.get(zone);
+ if (deployment == null) return this; // No longer deployed in this zone.
+ return with(deployment.withClusterInfo(clusterInfo));
+
+ }
+
+ public Instance recordActivityAt(Instant instant, ZoneId zone) {
+ Deployment deployment = deployments.get(zone);
+ if (deployment == null) return this;
+ return with(deployment.recordActivityAt(instant));
+ }
+
+ public Instance with(ZoneId zone, DeploymentMetrics deploymentMetrics) {
+ Deployment deployment = deployments.get(zone);
+ if (deployment == null) return this; // No longer deployed in this zone.
+ return with(deployment.withMetrics(deploymentMetrics));
+ }
+
+ public Instance withoutDeploymentIn(ZoneId zone) {
+ Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(this.deployments);
+ deployments.remove(zone);
+ return with(deployments);
+ }
+
+ public Instance withoutDeploymentJob(JobType jobType) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.without(jobType), change, outstandingChange,
+ ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ rotations, rotationStatus);
+ }
+
+ public Instance with(ApplicationMetrics metrics) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
+ metrics, pemDeployKey, rotations, rotationStatus);
+ }
+
+ public Instance withPemDeployKey(String pemDeployKey) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
+ metrics, Optional.ofNullable(pemDeployKey), rotations, rotationStatus);
+ }
+
+ public Instance with(List<AssignedRotation> assignedRotations) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
+ metrics, pemDeployKey, assignedRotations, rotationStatus);
+ }
+
+ public Instance with(RotationStatus rotationStatus) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
+ metrics, pemDeployKey, rotations, rotationStatus);
+ }
+
+ private Instance with(Deployment deployment) {
+ Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(this.deployments);
+ deployments.put(deployment.zone(), deployment);
+ return with(deployments);
+ }
+
+ private Instance with(Map<ZoneId, Deployment> deployments) {
+ return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
+ metrics, pemDeployKey, rotations, rotationStatus);
+ }
+
public ApplicationId id() { return id; }
+ public InstanceName name() { return id.instance(); }
+
public Instant createdAt() { return createdAt; }
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index cd5552ed62e..9aba8921860 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -6,7 +6,9 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.searchlib.rankingexpression.rule.Function;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
@@ -20,10 +22,12 @@ import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
import java.time.Instant;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -31,6 +35,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
+import java.util.function.BinaryOperator;
+import java.util.function.UnaryOperator;
/**
* An application that has been locked for modification. Provides methods for modifying an application's fields.
@@ -40,21 +46,21 @@ import java.util.OptionalLong;
public class LockedApplication {
private final Lock lock;
- private final ApplicationId id;
+ private final TenantAndApplicationId id;
private final Instant createdAt;
private final DeploymentSpec deploymentSpec;
private final ValidationOverrides validationOverrides;
- private final Map<ZoneId, Deployment> deployments;
- private final DeploymentJobs deploymentJobs;
private final Change change;
private final Change outstandingChange;
+ private final Optional<IssueId> deploymentIssueId;
private final Optional<IssueId> ownershipIssueId;
private final Optional<User> owner;
private final OptionalInt majorVersion;
private final ApplicationMetrics metrics;
private final Optional<String> pemDeployKey;
- private final List<AssignedRotation> rotations;
- private final RotationStatus rotationStatus;
+ private final OptionalLong projectId;
+ private final boolean internal;
+ private final Map<InstanceName, Instance> instances;
/**
* Used to create a locked application
@@ -64,129 +70,130 @@ public class LockedApplication {
*/
LockedApplication(Application application, Lock lock) {
this(Objects.requireNonNull(lock, "lock cannot be null"), application.id(), application.createdAt(),
- application.deploymentSpec(), application.validationOverrides(),
- application.deployments(),
- application.deploymentJobs(), application.change(), application.outstandingChange(),
- application.ownershipIssueId(), application.owner(), application.majorVersion(), application.metrics(),
- application.pemDeployKey(), application.rotations(), application.rotationStatus());
+ application.deploymentSpec(), application.validationOverrides(), application.change(),
+ application.outstandingChange(), application.deploymentIssueId(), application.ownershipIssueId(),
+ application.owner(), application.majorVersion(), application.metrics(), application.pemDeployKey(),
+ application.projectId(), application.internal(), application.instances());
}
- private LockedApplication(Lock lock, ApplicationId id, Instant createdAt,
- DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
- Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
- Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
+ private LockedApplication(Lock lock, TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec,
+ ValidationOverrides validationOverrides, Change change, Change outstandingChange,
+ Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- List<AssignedRotation> rotations, RotationStatus rotationStatus) {
+ OptionalLong projectId, boolean internal, Map<InstanceName, Instance> instances) {
this.lock = lock;
this.id = id;
this.createdAt = createdAt;
this.deploymentSpec = deploymentSpec;
this.validationOverrides = validationOverrides;
- this.deployments = deployments;
- this.deploymentJobs = deploymentJobs;
this.change = change;
this.outstandingChange = outstandingChange;
+ this.deploymentIssueId = deploymentIssueId;
this.ownershipIssueId = ownershipIssueId;
this.owner = owner;
this.majorVersion = majorVersion;
this.metrics = metrics;
this.pemDeployKey = pemDeployKey;
- this.rotations = rotations;
- this.rotationStatus = rotationStatus;
+ this.projectId = projectId;
+ this.internal = internal;
+ this.instances = Map.copyOf(instances);
}
/** Returns a read-only copy of this */
public Application get() {
- return new Application(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change,
- outstandingChange, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
+ return new Application(id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances.values());
+ }
+
+ public LockedApplication with(InstanceName instance, UnaryOperator<Instance> modification) {
+ var instances = new HashMap<>(this.instances);
+ instances.put(instance, modification.apply(instances.get(instance)));
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
+ }
+
+ private LockedApplication without(InstanceName instance) {
+ var instances = new HashMap<>(this.instances);
+ instances.remove(instance);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication withBuiltInternally(boolean builtInternally) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs.withBuiltInternally(builtInternally), change, outstandingChange,
- ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, builtInternally, instances);
}
public LockedApplication withProjectId(OptionalLong projectId) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs.withProjectId(projectId), change, outstandingChange,
- ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication withDeploymentIssueId(IssueId issueId) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs.with(issueId), change, outstandingChange,
- ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
- }
-
- public LockedApplication withJobCompletion(long projectId, JobType jobType) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs.withProjectId(jobType == JobType.component ? OptionalLong.of(projectId)
- : deploymentJobs.projectId()),
- change, outstandingChange, ownershipIssueId, owner, majorVersion, metrics,
- pemDeployKey, rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ Optional.ofNullable(issueId), ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication with(DeploymentSpec deploymentSpec) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange,
- ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication with(ValidationOverrides validationOverrides) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication withChange(Change change) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication withOutstandingChange(Change outstandingChange) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication withOwnershipIssueId(IssueId issueId) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, Optional.ofNullable(issueId), owner,
- majorVersion, metrics, pemDeployKey, rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, Optional.of(issueId), owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication withOwner(User owner) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId,
- Optional.ofNullable(owner), majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, Optional.of(owner), majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
/** Set a major version for this, or set to null to remove any major version override */
public LockedApplication withMajorVersion(Integer majorVersion) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner,
majorVersion == null ? OptionalInt.empty() : OptionalInt.of(majorVersion),
- metrics, pemDeployKey, rotations, rotationStatus);
+ metrics, pemDeployKey, projectId, internal, instances);
}
public LockedApplication with(ApplicationMetrics metrics) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
+ projectId, internal, instances);
}
public LockedApplication withPemDeployKey(String pemDeployKey) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, Optional.ofNullable(pemDeployKey), rotations, rotationStatus);
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, Optional.ofNullable(pemDeployKey),
+ projectId, internal, instances);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedInstance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedInstance.java
deleted file mode 100644
index f1f11be4f4d..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedInstance.java
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller;
-
-import com.yahoo.component.Version;
-import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.config.application.api.ValidationOverrides;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.curator.Lock;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
-import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
-import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
-import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
-import com.yahoo.vespa.hosted.controller.application.Change;
-import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
-import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
-import com.yahoo.vespa.hosted.controller.application.Deployment;
-import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
-import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
-import com.yahoo.vespa.hosted.controller.application.JobStatus;
-import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
-import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
-
-import java.time.Instant;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.OptionalInt;
-import java.util.OptionalLong;
-
-/**
- * An application that has been locked for modification. Provides methods for modifying an application's fields.
- *
- * @author mpolden
- * @author jonmv
- */
-public class LockedInstance {
-
- private final Lock lock;
- private final ApplicationId id;
- private final Instant createdAt;
- private final DeploymentSpec deploymentSpec;
- private final ValidationOverrides validationOverrides;
- private final Map<ZoneId, Deployment> deployments;
- private final DeploymentJobs deploymentJobs;
- private final Change change;
- private final Change outstandingChange;
- private final Optional<IssueId> ownershipIssueId;
- private final Optional<User> owner;
- private final OptionalInt majorVersion;
- private final ApplicationMetrics metrics;
- private final Optional<String> pemDeployKey;
- private final List<AssignedRotation> rotations;
- private final RotationStatus rotationStatus;
-
- /**
- * Used to create a locked application
- *
- * @param instance The application to lock.
- * @param lock The lock for the application.
- */
- LockedInstance(Instance instance, Lock lock) {
- this(Objects.requireNonNull(lock, "lock cannot be null"), instance.id(), instance.createdAt(),
- instance.deploymentSpec(), instance.validationOverrides(),
- instance.deployments(),
- instance.deploymentJobs(), instance.change(), instance.outstandingChange(),
- instance.ownershipIssueId(), instance.owner(), instance.majorVersion(), instance.metrics(),
- instance.pemDeployKey(), instance.rotations(), instance.rotationStatus());
- }
-
- private LockedInstance(Lock lock, ApplicationId id, Instant createdAt,
- DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
- Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
- Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
- OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- List<AssignedRotation> rotations, RotationStatus rotationStatus) {
- this.lock = lock;
- this.id = id;
- this.createdAt = createdAt;
- this.deploymentSpec = deploymentSpec;
- this.validationOverrides = validationOverrides;
- this.deployments = deployments;
- this.deploymentJobs = deploymentJobs;
- this.change = change;
- this.outstandingChange = outstandingChange;
- this.ownershipIssueId = ownershipIssueId;
- this.owner = owner;
- this.majorVersion = majorVersion;
- this.metrics = metrics;
- this.pemDeployKey = pemDeployKey;
- this.rotations = rotations;
- this.rotationStatus = rotationStatus;
- }
-
- /** Returns a read-only copy of this */
- public Instance get() {
- return new Instance(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change,
- outstandingChange, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
- }
-
- public LockedInstance withJobPause(JobType jobType, OptionalLong pausedUntil) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs.withPause(jobType, pausedUntil), change, outstandingChange,
- ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
- }
-
- public LockedInstance withJobCompletion(JobType jobType, JobStatus.JobRun completion,
- Optional<DeploymentJobs.JobError> jobError) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs.withCompletion(jobType, completion, jobError),
- change, outstandingChange, ownershipIssueId, owner, majorVersion, metrics,
- pemDeployKey, rotations, rotationStatus);
- }
-
- public LockedInstance withJobTriggering(JobType jobType, JobStatus.JobRun job) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs.withTriggering(jobType, job), change, outstandingChange,
- ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
- }
-
- public LockedInstance withNewDeployment(ZoneId zone, ApplicationVersion applicationVersion, Version version,
- Instant instant, Map<DeploymentMetrics.Warning, Integer> warnings) {
- // Use info from previous deployment if available, otherwise create a new one.
- Deployment previousDeployment = deployments.getOrDefault(zone, new Deployment(zone, applicationVersion,
- version, instant));
- Deployment newDeployment = new Deployment(zone, applicationVersion, version, instant,
- previousDeployment.clusterUtils(),
- previousDeployment.clusterInfo(),
- previousDeployment.metrics().with(warnings),
- previousDeployment.activity());
- return with(newDeployment);
- }
-
- public LockedInstance withClusterUtilization(ZoneId zone, Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization) {
- Deployment deployment = deployments.get(zone);
- if (deployment == null) return this; // No longer deployed in this zone.
- return with(deployment.withClusterUtils(clusterUtilization));
- }
-
- public LockedInstance withClusterInfo(ZoneId zone, Map<ClusterSpec.Id, ClusterInfo> clusterInfo) {
- Deployment deployment = deployments.get(zone);
- if (deployment == null) return this; // No longer deployed in this zone.
- return with(deployment.withClusterInfo(clusterInfo));
-
- }
-
- public LockedInstance recordActivityAt(Instant instant, ZoneId zone) {
- Deployment deployment = deployments.get(zone);
- if (deployment == null) return this;
- return with(deployment.recordActivityAt(instant));
- }
-
- public LockedInstance with(ZoneId zone, DeploymentMetrics deploymentMetrics) {
- Deployment deployment = deployments.get(zone);
- if (deployment == null) return this; // No longer deployed in this zone.
- return with(deployment.withMetrics(deploymentMetrics));
- }
-
- public LockedInstance withoutDeploymentIn(ZoneId zone) {
- Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(this.deployments);
- deployments.remove(zone);
- return with(deployments);
- }
-
- public LockedInstance withoutDeploymentJob(JobType jobType) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs.without(jobType), change, outstandingChange,
- ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotations, rotationStatus);
- }
-
- public LockedInstance with(ApplicationMetrics metrics) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotations, rotationStatus);
- }
-
- public LockedInstance withPemDeployKey(String pemDeployKey) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, Optional.ofNullable(pemDeployKey), rotations, rotationStatus);
- }
-
- public LockedInstance with(List<AssignedRotation> assignedRotations) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, assignedRotations, rotationStatus);
- }
-
- public LockedInstance with(RotationStatus rotationStatus) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotations, rotationStatus);
- }
-
- /** Don't expose non-leaf sub-objects. */
- private LockedInstance with(Deployment deployment) {
- Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(this.deployments);
- deployments.put(deployment.zone(), deployment);
- return with(deployments);
- }
-
- private LockedInstance with(Map<ZoneId, Deployment> deployments) {
- return new LockedInstance(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotations, rotationStatus);
- }
-
- @Override
- public String toString() {
- return "application '" + id + "'";
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
index 22274975104..6a15e49134a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
@@ -7,6 +7,7 @@ import com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
+import com.yahoo.vespa.hosted.controller.Instance;
import java.time.Instant;
import java.util.Collection;
@@ -14,7 +15,8 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
-import java.util.stream.Stream;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* A list of applications which can be filtered in various ways.
@@ -23,20 +25,24 @@ import java.util.stream.Stream;
*/
public class ApplicationList {
- private final ImmutableList<Application> list;
+ private final List<Application> list;
- private ApplicationList(Iterable<Application> applications) {
- this.list = ImmutableList.copyOf(applications);
+ private ApplicationList(List<Application> applications) {
+ this.list = applications;
}
// ----------------------------------- Factories
- public static ApplicationList from(Iterable<Application> applications) {
- return new ApplicationList(applications);
+ public static ApplicationList from(Collection<Application> applications) {
+ return new ApplicationList(List.copyOf(applications));
}
public static ApplicationList from(Collection<ApplicationId> ids, ApplicationController applications) {
- return listOf(ids.stream().map(applications::requireApplication));
+ return new ApplicationList(ids.stream()
+ .map(TenantAndApplicationId::from)
+ .distinct()
+ .map(applications::requireApplication)
+ .collect(Collectors.toUnmodifiableList()));
}
// ----------------------------------- Accessors
@@ -45,7 +51,7 @@ public class ApplicationList {
public List<Application> asList() { return list; }
/** Returns the ids of the applications in this as an immutable list */
- public List<ApplicationId> idList() { return ImmutableList.copyOf(list.stream().map(Application::id)::iterator); }
+ public List<TenantAndApplicationId> idList() { return list.stream().map(Application::id).collect(Collectors.toUnmodifiableList()); }
public boolean isEmpty() { return list.isEmpty(); }
@@ -55,17 +61,17 @@ public class ApplicationList {
/** Returns the subset of applications which are upgrading (to any version), not considering block windows. */
public ApplicationList upgrading() {
- return listOf(list.stream().filter(application -> application.change().platform().isPresent()));
+ return filteredOn(application -> application.change().platform().isPresent());
}
/** Returns the subset of applications which are currently upgrading to the given version */
public ApplicationList upgradingTo(Version version) {
- return listOf(list.stream().filter(application -> isUpgradingTo(version, application)));
+ return filteredOn(application -> isUpgradingTo(version, application));
}
/** Returns the subset of applications which are not pinned to a certain Vespa version. */
public ApplicationList unpinned() {
- return listOf(list.stream().filter(application -> ! application.change().isPinned()));
+ return filteredOn(application -> ! application.change().isPinned());
}
/** Returns the subset of applications which are currently not upgrading to the given version */
@@ -75,7 +81,7 @@ public class ApplicationList {
/** Returns the subset of applications which are currently not upgrading to any of the given versions */
public ApplicationList notUpgradingTo(Collection<Version> versions) {
- return listOf(list.stream().filter(application -> versions.stream().noneMatch(version -> isUpgradingTo(version, application))));
+ return filteredOn(application -> versions.stream().noneMatch(version -> isUpgradingTo(version, application)));
}
/**
@@ -89,79 +95,87 @@ public class ApplicationList {
/** Returns the subset of applications which have changes left to deploy; blocked, or deploying */
public ApplicationList withChanges() {
- return listOf(list.stream().filter(application -> application.change().hasTargets() || application.outstandingChange().hasTargets()));
+ return filteredOn(application -> application.change().hasTargets() || application.outstandingChange().hasTargets());
}
/** Returns the subset of applications which are currently not deploying a change */
public ApplicationList notDeploying() {
- return listOf(list.stream().filter(application -> ! application.change().hasTargets()));
+ return filteredOn(application -> ! application.change().hasTargets());
}
/** Returns the subset of applications which currently does not have any failing jobs */
public ApplicationList notFailing() {
- return listOf(list.stream().filter(application -> ! application.deploymentJobs().hasFailures()));
+ return filteredOn(application -> application.instances().values().stream()
+ .noneMatch(instance -> instance.deploymentJobs().hasFailures()));
}
/** Returns the subset of applications which currently have failing jobs */
public ApplicationList failing() {
- return listOf(list.stream().filter(application -> application.deploymentJobs().hasFailures()));
+ return filteredOn(application -> application.instances().values().stream()
+ .anyMatch(instance -> instance.deploymentJobs().hasFailures()));
}
/** Returns the subset of applications which have been failing an upgrade to the given version since the given instant */
public ApplicationList failingUpgradeToVersionSince(Version version, Instant threshold) {
- return listOf(list.stream().filter(application -> failingUpgradeToVersionSince(application, version, threshold)));
+ return filteredOn(application -> application.instances().values().stream()
+ .anyMatch(instance -> failingUpgradeToVersionSince(instance, version, threshold)));
}
/** Returns the subset of applications which have been failing an application change since the given instant */
public ApplicationList failingApplicationChangeSince(Instant threshold) {
- return listOf(list.stream().filter(application -> failingApplicationChangeSince(application, threshold)));
+ return filteredOn(application -> application.instances().values().stream()
+ .anyMatch(instance -> failingApplicationChangeSince(instance, threshold)));
}
/** Returns the subset of applications which currently does not have any failing jobs on the given version */
public ApplicationList notFailingOn(Version version) {
- return listOf(list.stream().filter(application -> ! failingOn(version, application)));
+ return filteredOn(application -> application.instances().values().stream()
+ .noneMatch(instance -> failingOn(version, instance)));
}
/** Returns the subset of applications which have at least one production deployment */
- public ApplicationList hasDeployment() {
- return listOf(list.stream().filter(a -> !a.productionDeployments().isEmpty()));
+ public ApplicationList withProductionDeployment() {
+ return filteredOn(application -> application.instances().values().stream()
+ .anyMatch(instance -> instance.productionDeployments().size() > 0));
}
/** Returns the subset of applications which started failing on the given version */
public ApplicationList startedFailingOn(Version version) {
- return listOf(list.stream().filter(application -> ! JobList.from(application).firstFailing().on(version).isEmpty()));
+ return filteredOn(application -> application.instances().values().stream()
+ .anyMatch(instance -> ! JobList.from(instance).firstFailing().on(version).isEmpty()));
}
/** Returns the subset of applications which has the given upgrade policy */
public ApplicationList with(UpgradePolicy policy) {
- return listOf(list.stream().filter(a -> a.deploymentSpec().upgradePolicy() == policy));
+ return filteredOn(application -> application.deploymentSpec().upgradePolicy() == policy);
}
/** Returns the subset of applications which does not have the given upgrade policy */
public ApplicationList without(UpgradePolicy policy) {
- return listOf(list.stream().filter(a -> a.deploymentSpec().upgradePolicy() != policy));
+ return filteredOn(application -> application.deploymentSpec().upgradePolicy() != policy);
}
/** Returns the subset of applications which have at least one deployment on a lower version than the given one */
public ApplicationList onLowerVersionThan(Version version) {
- return listOf(list.stream()
- .filter(a -> a.productionDeployments().values().stream()
- .anyMatch(d -> d.version().isBefore(version))));
+ return filteredOn(application -> application.instances().values().stream()
+ .flatMap(instance -> instance.productionDeployments().values().stream())
+ .anyMatch(deployment -> deployment.version().isBefore(version)));
}
/** Returns the subset of applications which have a project ID */
public ApplicationList withProjectId() {
- return listOf(list.stream().filter(a -> a.deploymentJobs().projectId().isPresent()));
- }
-
- /** Returns the subset of applications which have at least one production deployment */
- public ApplicationList hasProductionDeployment() {
- return listOf(list.stream().filter(a -> ! a.productionDeployments().isEmpty()));
+ return filteredOn(application -> application.projectId().isPresent());
}
/** Returns the subset of applications that are allowed to upgrade at the given time */
public ApplicationList canUpgradeAt(Instant instant) {
- return listOf(list.stream().filter(a -> a.deploymentSpec().canUpgradeAt(instant)));
+ return filteredOn(application -> application.deploymentSpec().canUpgradeAt(instant));
+ }
+
+ /** Returns the subset of applications that have at least one assigned rotation */
+ public ApplicationList hasRotation() {
+ return filteredOn(application -> application.instances().values().stream()
+ .anyMatch(instance -> ! instance.rotations().isEmpty()));
}
/**
@@ -171,8 +185,9 @@ public class ApplicationList {
* @param defaultMajorVersion the default major version to assume for applications not specifying one
*/
public ApplicationList allowMajorVersion(int targetMajorVersion, int defaultMajorVersion) {
- return listOf(list.stream().filter(a -> a.deploymentSpec().majorVersion().orElse(a.majorVersion().orElse(defaultMajorVersion))
- >= targetMajorVersion));
+ return filteredOn(application -> targetMajorVersion <= application.deploymentSpec().majorVersion()
+ .orElse(application.majorVersion()
+ .orElse(defaultMajorVersion)));
}
/** Returns the first n application in this (or all, if there are less than n). */
@@ -189,7 +204,10 @@ public class ApplicationList {
* Applications without any deployments are ordered first.
*/
public ApplicationList byIncreasingDeployedVersion() {
- return listOf(list.stream().sorted(Comparator.comparing(application -> application.oldestDeployedPlatform().orElse(Version.emptyVersion))));
+ return new ApplicationList(list.stream()
+ .sorted(Comparator.comparing(application -> application.oldestDeployedPlatform()
+ .orElse(Version.emptyVersion)))
+ .collect(Collectors.toUnmodifiableList()));
}
// ----------------------------------- Internal helpers
@@ -198,31 +216,30 @@ public class ApplicationList {
return application.change().platform().equals(Optional.of(version));
}
- private static boolean failingOn(Version version, Application application) {
- return ! JobList.from(application)
- .failing()
- .lastCompleted().on(version)
- .isEmpty();
+ private static boolean failingOn(Version version, Instance instance) {
+ return ! JobList.from(instance)
+ .failing()
+ .lastCompleted().on(version)
+ .isEmpty();
}
- private static boolean failingUpgradeToVersionSince(Application application, Version version, Instant threshold) {
- return ! JobList.from(application)
- .not().failingApplicationChange()
- .firstFailing().before(threshold)
- .lastCompleted().on(version)
- .isEmpty();
+ private static boolean failingUpgradeToVersionSince(Instance instance, Version version, Instant threshold) {
+ return ! JobList.from(instance)
+ .not().failingApplicationChange()
+ .firstFailing().before(threshold)
+ .lastCompleted().on(version)
+ .isEmpty();
}
- private static boolean failingApplicationChangeSince(Application application, Instant threshold) {
- return ! JobList.from(application)
- .failingApplicationChange()
- .firstFailing().before(threshold)
- .isEmpty();
+ private static boolean failingApplicationChangeSince(Instance instance, Instant threshold) {
+ return ! JobList.from(instance)
+ .failingApplicationChange()
+ .firstFailing().before(threshold)
+ .isEmpty();
}
- /** Convenience converter from a stream to an ApplicationList */
- private static ApplicationList listOf(Stream<Application> applications) {
- return from(applications::iterator);
+ private ApplicationList filteredOn(Predicate<Application> condition) {
+ return new ApplicationList(list.stream().filter(condition).collect(Collectors.toUnmodifiableList()));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
index 809e0a8f0fb..ea020010e3d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
@@ -37,7 +37,7 @@ public class InstanceList {
}
public static InstanceList from(Collection<ApplicationId> ids, ApplicationController applications) {
- return listOf(ids.stream().map(applications::require));
+ return listOf(ids.stream().map(applications::requireInstance));
}
// ----------------------------------- Accessors
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobList.java
index 497edf18dfc..5c2df9f039b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobList.java
@@ -40,10 +40,6 @@ public class JobList {
return new JobList(jobs);
}
- public static JobList from(Application application) {
- return from(application.deploymentJobs().jobStatus().values());
- }
-
public static JobList from(Instance instance) {
return from(instance.deploymentJobs().jobStatus().values());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java
index 081ce02e52d..0b537535315 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java
@@ -45,7 +45,11 @@ public class TenantAndApplicationId implements Comparable<TenantAndApplicationId
}
public ApplicationId defaultInstance() {
- return ApplicationId.from(tenant, application, InstanceName.defaultName());
+ return instance(InstanceName.defaultName());
+ }
+
+ public ApplicationId instance(InstanceName instance) {
+ return ApplicationId.from(tenant, application, instance);
}
public String serialized() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
index c9234b87a8b..91f9e2d56d7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
@@ -16,7 +16,7 @@ import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.athenz.client.zms.RoleAction;
import com.yahoo.vespa.athenz.client.zms.ZmsClient;
import com.yahoo.vespa.athenz.client.zts.ZtsClient;
-import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
import com.yahoo.vespa.hosted.controller.security.AccessControl;
@@ -91,7 +91,7 @@ public class AthenzFacade implements AccessControl {
}
@Override
- public Tenant updateTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing, List<Instance> instances) {
+ public Tenant updateTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing, List<Application> applications) {
AthenzTenantSpec spec = (AthenzTenantSpec) tenantSpec;
AthenzCredentials athenzCredentials = (AthenzCredentials) credentials;
AthenzDomain newDomain = spec.domain();
@@ -121,12 +121,12 @@ public class AthenzFacade implements AccessControl {
else { // Delete and recreate tenant, and optionally application, resources in Athenz otherwise.
log("createTenancy(tenantDomain=%s, service=%s)", newDomain, service);
zmsClient.createTenancy(newDomain, service, athenzCredentials.token());
- for (Instance instance : instances)
- createApplication(newDomain, instance.id().application(), athenzCredentials.token());
+ for (Application application : applications)
+ createApplication(newDomain, application.id().application(), athenzCredentials.token());
log("deleteTenancy(tenantDomain=%s, service=%s)", oldDomain, service);
- for (Instance instance : instances)
- deleteApplication(oldDomain, instance.id().application(), athenzCredentials.token());
+ for (Application application : applications)
+ deleteApplication(oldDomain, application.id().application(), athenzCredentials.token());
zmsClient.deleteTenancy(oldDomain, service, athenzCredentials.token());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index 92b5a5c54cb..1bba1baa91b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -15,7 +15,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.application.InstanceList;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport;
@@ -39,9 +39,11 @@ import java.util.OptionalLong;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.BuildJob;
+import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState.disabled;
import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState.idle;
import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState.queued;
import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState.running;
@@ -59,7 +61,7 @@ import static java.util.stream.Collectors.toList;
/**
* Responsible for scheduling deployment jobs in a build system and keeping
- * {@link Instance#change()} in sync with what is scheduled.
+ * {@link Application#change()} in sync with what is scheduled.
*
* This class is multi-thread safe.
*
@@ -100,23 +102,25 @@ public class DeploymentTrigger {
report.jobType(),
report.applicationId(),
report.projectId()));
- if (applications().get(report.applicationId()).isEmpty()) {
+ if (applications().getInstance(report.applicationId()).isEmpty()) {
log.log(LogLevel.WARNING, "Ignoring completion of job of project '" + report.projectId() +
"': Unknown application '" + report.applicationId() + "'");
return;
}
- applications().lockApplicationOrThrow(report.applicationId(), application -> {
+ applications().lockApplicationOrThrow(TenantAndApplicationId.from(report.applicationId()), application -> {
JobRun triggering;
+ // TODO jonmv: Remove this, and replace with a simple application version counter.
if (report.jobType() == component) {
ApplicationVersion applicationVersion = report.version().get();
- triggering = JobRun.triggering(applications().oldestInstalledPlatform(report.applicationId()), applicationVersion,
+ triggering = JobRun.triggering(applications().oldestInstalledPlatform(TenantAndApplicationId.from(report.applicationId())),
+ applicationVersion,
Optional.empty(), Optional.empty(), "Application commit", clock.instant());
if (report.success()) {
if (acceptNewApplicationVersion(application.get())) {
application = application.withChange(application.get().change().with(applicationVersion))
.withOutstandingChange(Change.empty());
- if (application.get().deploymentJobs().deployedInternally())
+ if (application.get().internal())
for (Run run : jobs.active())
if (run.id().application().equals(report.applicationId()))
jobs.abort(run.id());
@@ -124,9 +128,11 @@ public class DeploymentTrigger {
else
application = application.withOutstandingChange(Change.of(applicationVersion));
}
+ application = application.withProjectId(OptionalLong.of(report.projectId()));
}
else {
- Optional<JobStatus> status = application.get().deploymentJobs().statusOf(report.jobType());
+ Optional<JobStatus> status = application.get().require(report.applicationId().instance())
+ .deploymentJobs().statusOf(report.jobType());
triggering = status.filter(job -> job.lastTriggered().isPresent()
&& job.lastCompleted()
.map(completion -> ! completion.at().isAfter(job.lastTriggered().get().at()))
@@ -137,16 +143,13 @@ public class DeploymentTrigger {
.orElse("never")))
.lastTriggered().get();
}
- application = application.withJobCompletion(report.projectId(), report.jobType());
- applications().store(application);
- applications().lockOrThrow(report.applicationId(), instance ->
- applications().store(instance.withJobCompletion(report.jobType(),
- triggering.completion(report.buildNumber(), clock.instant()),
- report.jobError())));
+ application = application.with(report.applicationId().instance(),
+ instance -> instance.withJobCompletion(report.jobType(),
+ triggering.completion(report.buildNumber(), clock.instant()),
+ report.jobError()));
+ applications().store(application.withChange(remainingChange(application.get())));
});
- applications().lockApplicationOrThrow(report.applicationId(), application ->
- applications().store(application.withChange(remainingChange(application.get()))));
}
/** Returns a map of jobs that are scheduled to be run, grouped by the job type */
@@ -190,8 +193,8 @@ public class DeploymentTrigger {
public boolean trigger(Job job) {
log.log(LogLevel.DEBUG, String.format("Triggering %s: %s", job, job.triggering));
try {
- applications().lockOrThrow(job.applicationId(), application -> {
- if (application.get().deploymentJobs().deployedInternally())
+ applications().lockApplicationOrThrow(TenantAndApplicationId.from(job.applicationId()), application -> {
+ if (application.get().internal())
jobs.start(job.applicationId(), job.jobType, new Versions(job.triggering.platform(),
job.triggering.application(),
job.triggering.sourcePlatform(),
@@ -199,35 +202,37 @@ public class DeploymentTrigger {
else
buildService.trigger(job);
- applications().store(application.withJobTriggering(job.jobType, job.triggering));
+ applications().store(application.with(job.applicationId().instance(),
+ instance -> instance.withJobTriggering(job.jobType, job.triggering)));
});
return true;
}
catch (RuntimeException e) {
log.log(LogLevel.WARNING, "Exception triggering " + job + ": " + e);
if (e instanceof NoSuchElementException || e instanceof IllegalArgumentException)
- applications().lockApplicationOrThrow(job.applicationId(), application ->
+ applications().lockApplicationOrThrow(TenantAndApplicationId.from(job.applicationId()), application ->
applications().store(application.withProjectId(OptionalLong.empty())));
return false;
}
}
- /** Force triggering of a job for given application. */
+ /** Force triggering of a job for given instance. */
public List<JobType> forceTrigger(ApplicationId applicationId, JobType jobType, String user) {
- Instance instance = applications().require(applicationId);
+ Application application = applications().requireApplication(TenantAndApplicationId.from(applicationId));
+ Instance instance = application.require(applicationId.instance());
if (jobType == component) {
- if (instance.deploymentJobs().deployedInternally())
+ if (application.internal())
throw new IllegalArgumentException(applicationId + " has no component job we can trigger.");
- buildService.trigger(BuildJob.of(applicationId, instance.deploymentJobs().projectId().getAsLong(), jobType.jobName()));
+ buildService.trigger(BuildJob.of(applicationId, application.projectId().getAsLong(), jobType.jobName()));
return singletonList(component);
}
- Versions versions = Versions.from(instance.change(), instance, deploymentFor(instance, jobType),
+ Versions versions = Versions.from(application.change(), application, deploymentFor(instance, jobType),
controller.systemVersion());
String reason = "Job triggered manually by " + user;
return (jobType.isProduction() && ! isTested(instance, versions)
- ? testJobs(instance, versions, reason, clock.instant(), __ -> true).stream()
- : Stream.of(deploymentJob(instance, versions, instance.change(), jobType, reason, clock.instant())))
+ ? testJobs(application.deploymentSpec(), application.change(), instance, versions, reason, clock.instant(), __ -> true).stream()
+ : Stream.of(deploymentJob(instance, versions, application.change(), jobType, reason, clock.instant())))
.peek(this::trigger)
.map(Job::jobType).collect(toList());
}
@@ -237,20 +242,21 @@ public class DeploymentTrigger {
if (until.isAfter(clock.instant().plus(maxPause)))
throw new IllegalArgumentException("Pause only allowed for up to " + maxPause);
- applications().lockOrThrow(id, application ->
- applications().store(application.withJobPause(jobType, OptionalLong.of(until.toEpochMilli()))));
+ applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application ->
+ applications().store(application.with(id.instance(),
+ instance -> instance.withJobPause(jobType, OptionalLong.of(until.toEpochMilli())))));
}
/** Triggers a change of this application, unless it already has a change. */
- public void triggerChange(ApplicationId applicationId, Change change) {
- applications().lockOrThrow(applicationId, application -> {
+ public void triggerChange(TenantAndApplicationId applicationId, Change change) {
+ applications().lockApplicationOrThrow(applicationId, application -> {
if ( ! application.get().change().hasTargets())
forceChange(applicationId, change);
});
}
/** Overrides the given application's platform and application changes with any contained in the given change. */
- public void forceChange(ApplicationId applicationId, Change change) {
+ public void forceChange(TenantAndApplicationId applicationId, Change change) {
applications().lockApplicationOrThrow(applicationId, application -> {
if (change.application().isPresent())
application = application.withOutstandingChange(Change.empty());
@@ -259,7 +265,7 @@ public class DeploymentTrigger {
}
/** Cancels the indicated part of the given application's change. */
- public void cancelChange(ApplicationId applicationId, ChangesToCancel cancellation) {
+ public void cancelChange(TenantAndApplicationId applicationId, ChangesToCancel cancellation) {
applications().lockApplicationOrThrow(applicationId, application -> {
Change change;
switch (cancellation) {
@@ -299,7 +305,14 @@ public class DeploymentTrigger {
/** Returns the set of all jobs which have changes to propagate from the upstream steps. */
private List<Job> computeReadyJobs() {
- return InstanceList.from(applications().asList())
+ ApplicationList applications = ApplicationList.from(applications().asList());
+ applications = applications.withProjectId();
+ applications = applications.withChanges();
+ var jobs = applications.idList().stream()
+ .map(this::computeReadyJobs)
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ return ApplicationList.from(applications().asList())
.withProjectId()
.withChanges()
.idList().stream()
@@ -311,70 +324,74 @@ public class DeploymentTrigger {
/**
* Finds the next step to trigger for the given application, if any, and returns these as a list.
*/
- private List<Job> computeReadyJobs(ApplicationId id) {
+ private List<Job> computeReadyJobs(TenantAndApplicationId id) {
List<Job> jobs = new ArrayList<>();
- applications().get(id).ifPresent(application -> {
- Change change = application.change();
- Optional<Instant> completedAt = max(application.deploymentJobs().statusOf(systemTest)
- .<Instant>flatMap(job -> job.lastSuccess().map(JobRun::at)),
- application.deploymentJobs().statusOf(stagingTest)
- .<Instant>flatMap(job -> job.lastSuccess().map(JobRun::at)));
- String reason = "New change available";
- List<Job> testJobs = null; // null means "uninitialised", while empty means "don't run any jobs".
- DeploymentSteps steps = steps(application.deploymentSpec());
-
- if (change.hasTargets()) {
- for (Step step : steps.production()) {
- List<JobType> stepJobs = steps.toJobs(step);
- List<JobType> remainingJobs = stepJobs.stream().filter(job -> ! isComplete(change, application, job)).collect(toList());
- if ( ! remainingJobs.isEmpty()) { // Change is incomplete; trigger remaining jobs if ready, or their test jobs if untested.
- for (JobType job : remainingJobs) {
- Versions versions = Versions.from(change, application, deploymentFor(application, job),
- controller.systemVersion());
- if (isTested(application, versions)) {
- if (completedAt.isPresent() && canTrigger(job, versions, application, stepJobs)) {
- jobs.add(deploymentJob(application, versions, change, job, reason, completedAt.get()));
+ applications().getApplication(id).ifPresent(application -> {
+ for (Instance instance : application.instances().values()) {
+ Change change = application.change();
+ Optional<Instant> completedAt = max(instance.deploymentJobs().statusOf(systemTest)
+ .<Instant>flatMap(job -> job.lastSuccess().map(JobRun::at)),
+ instance.deploymentJobs().statusOf(stagingTest)
+ .<Instant>flatMap(job -> job.lastSuccess().map(JobRun::at)));
+ String reason = "New change available";
+ List<Job> testJobs = null; // null means "uninitialised", while empty means "don't run any jobs".
+ DeploymentSteps steps = steps(application.deploymentSpec());
+
+ if (change.hasTargets()) {
+ for (Step step : steps.production()) {
+ List<JobType> stepJobs = steps.toJobs(step);
+ List<JobType> remainingJobs = stepJobs.stream().filter(job -> ! isComplete(change, change, instance, job)).collect(toList());
+ if (!remainingJobs.isEmpty()) { // Change is incomplete; trigger remaining jobs if ready, or their test jobs if untested.
+ for (JobType job : remainingJobs) {
+ Versions versions = Versions.from(change, application, deploymentFor(instance, job),
+ controller.systemVersion());
+ if (isTested(instance, versions)) {
+ if (completedAt.isPresent() && canTrigger(job, versions, instance, application.deploymentSpec(), stepJobs)) {
+ jobs.add(deploymentJob(instance, versions, change, job, reason, completedAt.get()));
+ }
+ if ( ! alreadyTriggered(instance, versions) && testJobs == null) {
+ testJobs = emptyList();
+ }
}
- if ( ! alreadyTriggered(application, versions) && testJobs == null) {
- testJobs = emptyList();
+ else if (testJobs == null) {
+ testJobs = testJobs(application.deploymentSpec(),
+ change, instance, versions,
+ String.format("Testing deployment for %s (%s)",
+ job.jobName(), versions.toString()),
+ completedAt.orElseGet(clock::instant));
}
}
- else if (testJobs == null) {
- testJobs = testJobs(application, versions,
- String.format("Testing deployment for %s (%s)",
- job.jobName(), versions.toString()),
- completedAt.orElseGet(clock::instant));
- }
+ completedAt = Optional.empty();
}
- completedAt = Optional.empty();
- }
- else { // All jobs are complete; find the time of completion of this step.
- if (stepJobs.isEmpty()) { // No jobs means this is a delay step.
- Duration delay = ((DeploymentSpec.Delay) step).duration();
- completedAt = completedAt.map(at -> at.plus(delay)).filter(at -> !at.isAfter(clock.instant()));
- reason += " after a delay of " + delay;
- }
- else {
- completedAt = stepJobs.stream().map(job -> application.deploymentJobs().statusOf(job).get().lastCompleted().get().at()).max(naturalOrder());
- reason = "Available change in " + stepJobs.stream().map(JobType::jobName).collect(joining(", "));
+ else { // All jobs are complete; find the time of completion of this step.
+ if (stepJobs.isEmpty()) { // No jobs means this is a delay step.
+ Duration delay = ((DeploymentSpec.Delay) step).duration();
+ completedAt = completedAt.map(at -> at.plus(delay)).filter(at -> !at.isAfter(clock.instant()));
+ reason += " after a delay of " + delay;
+ }
+ else {
+ completedAt = stepJobs.stream().map(job -> instance.deploymentJobs().statusOf(job).get().lastCompleted().get().at()).max(naturalOrder());
+ reason = "Available change in " + stepJobs.stream().map(JobType::jobName).collect(joining(", "));
+ }
}
}
}
+ if (testJobs == null) { // If nothing to test, but outstanding commits, test those.
+ testJobs = testJobs(application.deploymentSpec(), change, instance,
+ Versions.from(application.outstandingChange().onTopOf(change),
+ application,
+ steps.sortedDeployments(instance.productionDeployments().values()).stream().findFirst(),
+ controller.systemVersion()),
+ "Testing last changes outside prod", clock.instant());
+ }
+ jobs.addAll(testJobs);
}
- if (testJobs == null) { // If nothing to test, but outstanding commits, test those.
- testJobs = testJobs(application, Versions.from(application.outstandingChange().onTopOf(application.change()),
- application,
- steps.sortedDeployments(application.productionDeployments().values()).stream().findFirst(),
- controller.systemVersion()),
- "Testing last changes outside prod", clock.instant());
- }
- jobs.addAll(testJobs);
});
return Collections.unmodifiableList(jobs);
}
/** Returns whether given job should be triggered */
- private boolean canTrigger(JobType job, Versions versions, Instance instance, List<JobType> parallelJobs) {
+ private boolean canTrigger(JobType job, Versions versions, Instance instance, DeploymentSpec deploymentSpec, List<JobType> parallelJobs) {
if (jobStateOf(instance, job) != idle) return false;
// Are we already running jobs which are not in the set which can run in parallel with this?
@@ -383,12 +400,12 @@ public class DeploymentTrigger {
// Are there another suspended deployment such that we shouldn't simultaneously change this?
if (job.isProduction() && isSuspendedInAnotherZone(instance, job.zone(controller.system()))) return false;
- return triggerAt(clock.instant(), job, versions, instance);
+ return triggerAt(clock.instant(), job, versions, instance, deploymentSpec);
}
/** Returns whether given job should be triggered */
- private boolean canTrigger(JobType job, Versions versions, Instance instance) {
- return canTrigger(job, versions, instance, null);
+ private boolean canTrigger(JobType job, Versions versions, Instance instance, DeploymentSpec deploymentSpec) {
+ return canTrigger(job, versions, instance, deploymentSpec, null);
}
private boolean isSuspendedInAnotherZone(Instance instance, ZoneId zone) {
@@ -401,7 +418,7 @@ public class DeploymentTrigger {
}
/** Returns whether the given job can trigger at the given instant */
- public boolean triggerAt(Instant instant, JobType job, Versions versions, Instance instance) {
+ public boolean triggerAt(Instant instant, JobType job, Versions versions, Instance instance, DeploymentSpec deploymentSpec) {
Optional<JobStatus> jobStatus = instance.deploymentJobs().statusOf(job);
if (jobStatus.isEmpty()) return true;
if (jobStatus.get().pausedUntil().isPresent() && jobStatus.get().pausedUntil().getAsLong() > clock.instant().toEpochMilli()) return false;
@@ -409,7 +426,7 @@ public class DeploymentTrigger {
if (jobStatus.get().lastCompleted().isEmpty()) return true; // Never completed
if (jobStatus.get().firstFailing().isEmpty()) return true; // Should not happen as firstFailing should be set for an unsuccessful job
if ( ! versions.targetsMatch(jobStatus.get().lastCompleted().get())) return true; // Always trigger as targets have changed
- if (instance.deploymentSpec().upgradePolicy() == DeploymentSpec.UpgradePolicy.canary) return true; // Don't throttle canaries
+ if (deploymentSpec.upgradePolicy() == DeploymentSpec.UpgradePolicy.canary) return true; // Don't throttle canaries
Instant firstFailing = jobStatus.get().firstFailing().get().at();
Instant lastCompleted = jobStatus.get().lastCompleted().get().at();
@@ -447,13 +464,11 @@ public class DeploymentTrigger {
}
private JobState jobStateOf(Instance instance, JobType jobType) {
- if (instance.deploymentJobs().deployedInternally()) {
+ if (controller.applications().requireApplication(TenantAndApplicationId.from(instance.id())).internal()) {
Optional<Run> run = controller.jobController().last(instance.id(), jobType);
return run.isPresent() && ! run.get().hasEnded() ? JobState.running : JobState.idle;
}
- return buildService.stateOf(BuildJob.of(instance.id(),
- instance.deploymentJobs().projectId().getAsLong(),
- jobType.jobName()));
+ return buildService.stateOf(BuildJob.of(instance.id(), 0, jobType.jobName()));
}
// ---------- Completion logic ----------
@@ -470,7 +485,7 @@ public class DeploymentTrigger {
* Additionally, if the application is pinned to a Vespa version, and the given change has a (this) platform,
* the deployment for the job must be on the pinned version.
*/
- public boolean isComplete(Change change, Instance instance, JobType jobType) {
+ public boolean isComplete(Change change, Change fullChange, Instance instance, JobType jobType) {
Optional<Deployment> existingDeployment = deploymentFor(instance, jobType);
if ( change.isPinned()
&& change.platform().isPresent()
@@ -479,10 +494,10 @@ public class DeploymentTrigger {
return instance.deploymentJobs().statusOf(jobType).flatMap(JobStatus::lastSuccess)
.map(job -> change.platform().map(job.platform()::equals).orElse(true)
- && change.application().map(job.application()::equals).orElse(true))
+ && change.application().map(job.application()::equals).orElse(true))
.orElse(false)
|| jobType.isProduction()
- && existingDeployment.map(deployment -> ! isUpgrade(change, deployment) && isDowngrade(instance.change(), deployment))
+ && existingDeployment.map(deployment -> ! isUpgrade(change, deployment) && isDowngrade(fullChange, deployment))
.orElse(false);
}
@@ -522,7 +537,7 @@ public class DeploymentTrigger {
private boolean acceptNewApplicationVersion(Application application) {
if ( ! application.deploymentSpec().canChangeRevisionAt(clock.instant())) return false;
if (application.change().application().isPresent()) return true; // Replacing a previous application change is ok.
- for (Instance instance : controller.applications().asList(TenantAndApplicationId.from(application.id())))
+ for (Instance instance : application.instances().values())
if (instance.deploymentJobs().hasFailures()) return true; // Allow changes to fix upgrade problems.
return application.change().platform().isEmpty();
}
@@ -533,14 +548,14 @@ public class DeploymentTrigger {
? steps.testJobs()
: steps.productionJobs();
- // TODO jonmv: Replace with checking of all instance-jobs, when this is available.
- Instance instance = controller.applications().require(application.id());
Change change = application.change();
- if (jobs.stream().allMatch(job -> isComplete(application.change().withoutApplication(), instance, job)))
- change = change.withoutPlatform();
+ for (Instance instance : application.instances().values()) {
+ if (jobs.stream().allMatch(job -> isComplete(application.change().withoutApplication(), application.change(), instance, job)))
+ change = change.withoutPlatform();
- if (jobs.stream().allMatch(job -> isComplete(application.change().withoutPlatform(), instance, job)))
- change = change.withoutApplication();
+ if (jobs.stream().allMatch(job -> isComplete(application.change().withoutPlatform(), application.change(), instance, job)))
+ change = change.withoutApplication();
+ }
return change;
}
@@ -550,20 +565,23 @@ public class DeploymentTrigger {
/**
* Returns the list of test jobs that should run now, and that need to succeed on the given versions for it to be considered tested.
*/
- private List<Job> testJobs(Instance instance, Versions versions, String reason, Instant availableSince) {
- return testJobs(instance, versions, reason, availableSince, jobType -> canTrigger(jobType, versions, instance));
+ private List<Job> testJobs(DeploymentSpec deploymentSpec, Change change, Instance instance, Versions versions,
+ String reason, Instant availableSince) {
+ return testJobs(deploymentSpec, change, instance, versions, reason, availableSince,
+ jobType -> canTrigger(jobType, versions, instance, deploymentSpec));
}
/**
* Returns the list of test jobs that need to succeed on the given versions for it to be considered tested, filtered by the given condition.
*/
- private List<Job> testJobs(Instance instance, Versions versions, String reason, Instant availableSince, Predicate<JobType> condition) {
+ private List<Job> testJobs(DeploymentSpec deploymentSpec, Change change, Instance instance, Versions versions,
+ String reason, Instant availableSince, Predicate<JobType> condition) {
List<Job> jobs = new ArrayList<>();
- for (JobType jobType : steps(instance.deploymentSpec()).testJobs()) {
+ for (JobType jobType : steps(deploymentSpec).testJobs()) {
Optional<JobRun> completion = successOn(instance, jobType, versions)
.filter(run -> versions.sourcesMatchIfPresent(run) || jobType == systemTest);
if (completion.isEmpty() && condition.test(jobType))
- jobs.add(deploymentJob(instance, versions, instance.change(), jobType, reason, availableSince));
+ jobs.add(deploymentJob(instance, versions, change, jobType, reason, availableSince));
}
return jobs;
}
@@ -593,7 +611,7 @@ public class DeploymentTrigger {
private Job(Instance instance, JobRun triggering, JobType jobType, Instant availableSince,
boolean isRetry, boolean isApplicationUpgrade) {
- super(instance.id(), instance.deploymentJobs().projectId().getAsLong(), jobType.jobName());
+ super(instance.id(), 0L, jobType.jobName());
this.jobType = jobType;
this.triggering = triggering;
this.availableSince = availableSince;
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 ecec123145e..1828a189cad 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
@@ -19,8 +19,9 @@ import com.yahoo.security.KeyUtils;
import com.yahoo.security.SignatureAlgorithm;
import com.yahoo.security.X509CertificateBuilder;
import com.yahoo.security.X509CertificateUtils;
-import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.ActivateResult;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
@@ -37,6 +38,7 @@ import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.yolean.Exceptions;
import javax.security.auth.x500.X500Principal;
@@ -573,9 +575,9 @@ public class InternalStepRunner implements StepRunner {
/** Sends a mail with a notification of a failed run, if one should be sent. */
private void sendNotification(Run run, DualLogger logger) {
- Instance instance = controller.applications().require(run.id().application());
- Notifications notifications = instance.deploymentSpec().notifications();
- boolean newCommit = instance.change().application()
+ Application application = controller.applications().requireApplication(TenantAndApplicationId.from(run.id().application()));
+ Notifications notifications = application.deploymentSpec().notifications();
+ boolean newCommit = application.change().application()
.map(run.versions().targetApplication()::equals)
.orElse(false);
When when = newCommit ? failingCommit : failing;
@@ -611,8 +613,8 @@ public class InternalStepRunner implements StepRunner {
/** Returns the real application with the given id. */
private Instance application(ApplicationId id) {
- controller.applications().lockOrThrow(id, __ -> { }); // Memory fence.
- return controller.applications().require(id);
+ controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), __ -> { }); // Memory fence.
+ return controller.applications().requireInstance(id);
}
/**
@@ -638,7 +640,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) {
ApplicationVersion version = controller.jobController().run(id).get().versions().targetApplication();
- DeploymentSpec spec = controller.applications().require(id.application()).deploymentSpec();
+ DeploymentSpec spec = controller.applications().requireApplication(TenantAndApplicationId.from(id.application())).deploymentSpec();
ZoneId zone = id.type().zone(controller.system());
boolean useTesterCertificate = controller.system().isPublic() && id.type().isTest();
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 cb90d2bf31e..0ecce359a02 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
@@ -10,6 +10,7 @@ import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.LockedApplication;
@@ -99,7 +100,7 @@ public class JobController {
/** Rewrite all job data with the newest format. */
public void updateStorage() {
- for (ApplicationId id : applications())
+ for (ApplicationId id : instances())
for (JobType type : jobs(id)) {
locked(id, type, runs -> { // runs is not modified here, and is written as it was.
curator.readLastRun(id, type).ifPresent(curator::writeLastRun);
@@ -152,7 +153,7 @@ public class JobController {
return run;
ZoneId zone = id.type().zone(controller.system());
- Optional<Deployment> deployment = Optional.ofNullable(controller.applications().require(id.application())
+ Optional<Deployment> deployment = Optional.ofNullable(controller.applications().requireInstance(id.application())
.deployments().get(zone));
if (deployment.isEmpty() || deployment.get().at().isBefore(run.start()))
return run;
@@ -194,10 +195,11 @@ public class JobController {
locked(id, run -> run.with(testerCertificate));
}
- /** Returns a list of all application which have registered. */
- public List<ApplicationId> applications() {
+ /** Returns a list of all instances of applications which have registered. */
+ public List<ApplicationId> instances() {
return copyOf(controller.applications().asList().stream()
- .filter(application -> application.deploymentJobs().deployedInternally())
+ .filter(Application::internal)
+ .flatMap(application -> application.instances().values().stream())
.map(Instance::id)
.iterator());
}
@@ -237,12 +239,12 @@ public class JobController {
/** Returns a list of all active runs. */
public List<Run> active() {
- return copyOf(applications().stream()
- .flatMap(id -> Stream.of(JobType.values())
+ return copyOf(instances().stream()
+ .flatMap(id -> Stream.of(JobType.values())
.map(type -> last(id, type))
.flatMap(Optional::stream)
.filter(run -> ! run.hasEnded()))
- .iterator());
+ .iterator());
}
/** Changes the status of the given step, for the given run, provided it is still active. */
@@ -282,8 +284,8 @@ public class JobController {
public ApplicationVersion submit(ApplicationId id, SourceRevision revision, String authorEmail, long projectId,
ApplicationPackage applicationPackage, byte[] testPackageBytes) {
AtomicReference<ApplicationVersion> version = new AtomicReference<>();
- controller.applications().lockApplicationOrThrow(id, application -> {
- if ( ! application.get().deploymentJobs().deployedInternally())
+ controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> { // TODO jonmv: change callers
+ if ( ! application.get().internal())
application = registered(application);
long run = nextBuild(id);
@@ -311,18 +313,17 @@ public class JobController {
/** Registers the given application, copying necessary application packages, and returns the modified version. */
private LockedApplication registered(LockedApplication application) {
- controller.applications().asList(TenantAndApplicationId.from(application.get().id())).stream()
- .map(Instance::id)
- .forEach(id -> controller.applications().lockOrThrow(id, instance ->
- // TODO jvenstad: Remove when everyone has migrated off SDv3 pipelines. Real soon now!
- // Copy all current packages to the new application store
- instance.get().productionDeployments().values().stream()
- .map(Deployment::applicationVersion)
- .distinct()
- .forEach(appVersion -> {
- byte[] content = controller.applications().artifacts().getApplicationPackage(application.get().id(), appVersion.id());
- controller.applications().applicationStore().put(application.get().id(), appVersion, content);
- })));
+ for (Instance instance : application.get().instances().values()) {
+ // TODO jvenstad: Remove when everyone has migrated off SDv3 pipelines. Real soon now!
+ // Copy all current packages to the new application store
+ instance.productionDeployments().values().stream()
+ .map(Deployment::applicationVersion)
+ .distinct()
+ .forEach(appVersion -> {
+ byte[] content = controller.applications().artifacts().getApplicationPackage(instance.id(), appVersion.id());
+ controller.applications().applicationStore().put(instance.id(), appVersion, content);
+ });
+ }
// Make sure any ongoing upgrade is cancelled, since future jobs will require the tester artifact.
return application.withChange(application.get().change().withoutPlatform().withoutApplication())
.withBuiltInternally(true);
@@ -333,8 +334,8 @@ public class JobController {
if ( ! type.environment().isManuallyDeployed() && versions.targetApplication().isUnknown())
throw new IllegalArgumentException("Target application must be a valid reference.");
- controller.applications().lockIfPresent(id, application -> {
- if ( ! application.get().deploymentJobs().deployedInternally())
+ controller.applications().lockApplicationIfPresent(TenantAndApplicationId.from(id), application -> {
+ if ( ! application.get().internal())
throw new IllegalArgumentException(id + " is not built here!");
locked(id, type, __ -> {
@@ -350,8 +351,8 @@ public class JobController {
/** 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) {
- controller.applications().lockApplicationOrThrow(id, application -> {
- if ( ! application.get().deploymentJobs().deployedInternally())
+ controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
+ if ( ! application.get().internal())
controller.applications().store(registered(application));
});
if ( ! type.environment().isManuallyDeployed())
@@ -387,7 +388,7 @@ public class JobController {
/** Unregisters the given application and makes all associated data eligible for garbage collection. */
public void unregister(ApplicationId id) {
- controller.applications().lockApplicationIfPresent(id, application -> {
+ controller.applications().lockApplicationIfPresent(TenantAndApplicationId.from(id), application -> { // TODO jonmv: change callers.
controller.applications().store(application.withBuiltInternally(false));
jobs(id).forEach(type -> last(id, type).ifPresent(last -> abort(last.id())));
});
@@ -395,7 +396,7 @@ public class JobController {
/** Deletes run data and tester deployments for applications which are unknown, or no longer built internally. */
public void collectGarbage() {
- Set<ApplicationId> applicationsToBuild = new HashSet<>(applications());
+ Set<ApplicationId> applicationsToBuild = new HashSet<>(instances());
curator.applicationsWithJobs().stream()
.filter(id -> ! applicationsToBuild.contains(id))
.forEach(id -> {
@@ -440,7 +441,7 @@ public class JobController {
/** Returns a URI which points at a badge showing current status for all jobs for the given application. */
public URI overviewBadge(ApplicationId id) {
- DeploymentSteps steps = new DeploymentSteps(controller.applications().require(id).deploymentSpec(), controller::system);
+ DeploymentSteps steps = new DeploymentSteps(controller.applications().requireApplication(TenantAndApplicationId.from(id)).deploymentSpec(), controller::system);
return badges.overview(id,
steps.jobs().stream()
.map(type -> last(id, type))
@@ -471,13 +472,13 @@ public class JobController {
/** Returns a set containing the zone of the deployment tested in the given run, and all production zones for the application. */
public Set<ZoneId> testedZoneAndProductionZones(ApplicationId id, JobType type) {
return Stream.concat(Stream.of(type.zone(controller.system())),
- controller.applications().require(id).productionDeployments().keySet().stream())
+ controller.applications().requireInstance(id).productionDeployments().keySet().stream())
.collect(Collectors.toSet());
}
// TODO jvenstad: Find a more appropriate way of doing this, at least when this is the only build service.
private long nextBuild(ApplicationId id) {
- return 1 + controller.applications().require(id).deploymentJobs()
+ return 1 + controller.applications().requireInstance(id).deploymentJobs()
.statusOf(JobType.component)
.flatMap(JobStatus::lastCompleted)
.map(JobStatus.JobRun::id)
@@ -485,8 +486,8 @@ public class JobController {
}
private void prunePackages(ApplicationId id) {
- controller.applications().lockIfPresent(id, application -> {
- application.get().productionDeployments().values().stream()
+ controller.applications().lockApplicationIfPresent(TenantAndApplicationId.from(id), application -> {
+ application.get().require(id.instance()).productionDeployments().values().stream()
.map(Deployment::applicationVersion)
.min(Comparator.comparingLong(applicationVersion -> applicationVersion.buildNumber().getAsLong()))
.ifPresent(oldestDeployed -> {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
index 70424cf9813..385eaae8215 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.component.Version;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
@@ -112,40 +113,43 @@ public class Versions {
}
/** Create versions using change contained in application */
- public static Versions from(Instance instance, Version defaultPlatformVersion) {
- return from(instance.change(), instance, Optional.empty(), defaultPlatformVersion);
+ public static Versions from(Application application, Version defaultPlatformVersion) {
+ return from(application.change(), application, Optional.empty(), defaultPlatformVersion);
}
/** Create versions using given change and application */
- public static Versions from(Change change, Instance instance, Optional<Deployment> deployment,
+ public static Versions from(Change change, Application application, Optional<Deployment> deployment,
Version defaultPlatformVersion) {
- return new Versions(targetPlatform(instance, change, deployment, defaultPlatformVersion),
- targetApplication(instance, change, deployment),
+ return new Versions(targetPlatform(application, change, deployment, defaultPlatformVersion),
+ targetApplication(application, change, deployment),
deployment.map(Deployment::version),
deployment.map(Deployment::applicationVersion));
}
- private static Version targetPlatform(Instance instance, Change change, Optional<Deployment> deployment,
+ private static Version targetPlatform(Application application, Change change, Optional<Deployment> deployment,
Version defaultVersion) {
if (change.isPinned() && change.platform().isPresent())
return change.platform().get();
return max(change.platform(), deployment.map(Deployment::version))
- .orElseGet(() -> instance.oldestDeployedPlatform().orElse(defaultVersion));
+ .orElseGet(() -> application.oldestDeployedPlatform().orElse(defaultVersion));
}
- private static ApplicationVersion targetApplication(Instance instance, Change change,
+ private static ApplicationVersion targetApplication(Application application, Change change,
Optional<Deployment> deployment) {
return max(change.application(), deployment.map(Deployment::applicationVersion))
- .orElseGet(() -> defaultApplicationVersion(instance));
- }
-
- private static ApplicationVersion defaultApplicationVersion(Instance instance) {
- return instance.oldestDeployedApplication()
- .orElseGet(() -> Optional.ofNullable(instance.deploymentJobs().jobStatus().get(JobType.component))
- .flatMap(JobStatus::lastSuccess)
- .map(JobStatus.JobRun::application)
- .orElse(ApplicationVersion.unknown));
+ .orElseGet(() -> defaultApplicationVersion(application));
+ }
+
+ private static ApplicationVersion defaultApplicationVersion(Application application) {
+ return application.oldestDeployedApplication()
+ .or(() -> application.instances().values().stream()
+ .flatMap(instance -> instance.deploymentJobs().statusOf(JobType.component)
+ .flatMap(JobStatus::lastSuccess)
+ .map(JobStatus.JobRun::application)
+ .stream())
+ .findAny())
+ .orElse(ApplicationVersion.unknown);
}
private static <T extends Comparable<T>> Optional<T> max(Optional<T> o1, Optional<T> o2) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
index d829364a421..ca4e59812a0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -10,7 +11,9 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.Applicatio
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.InstanceList;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.UserTenant;
import com.yahoo.yolean.Exceptions;
@@ -47,13 +50,13 @@ public class ApplicationOwnershipConfirmer extends Maintainer {
/** File an ownership issue with the owners of all applications we know about. */
private void confirmApplicationOwnerships() {
- InstanceList.from(controller().applications().asList())
- .withProjectId()
- .hasProductionDeployment()
- .asList()
- .stream()
- .filter(application -> application.createdAt().isBefore(controller().clock().instant().minus(Duration.ofDays(90))))
- .forEach(application -> {
+ ApplicationList.from(controller().applications().asList())
+ .withProjectId()
+ .withProductionDeployment()
+ .asList()
+ .stream()
+ .filter(application -> application.createdAt().isBefore(controller().clock().instant().minus(Duration.ofDays(90))))
+ .forEach(application -> {
try {
Tenant tenant = tenantOf(application.id());
tenant.contact().ifPresent(contact -> { // TODO jvenstad: Makes sense to require, and run this only in main?
@@ -71,25 +74,27 @@ public class ApplicationOwnershipConfirmer extends Maintainer {
}
- private ApplicationSummary summaryOf(ApplicationId application) {
- var app = applications.require(application);
+ private ApplicationSummary summaryOf(TenantAndApplicationId application) {
+ var app = applications.requireApplication(application);
var metrics = new HashMap<ZoneId, ApplicationSummary.Metric>();
- for (var kv : app.deployments().entrySet()) {
- var zone = kv.getKey();
- var deploymentMetrics = kv.getValue().metrics();
- metrics.put(zone, new ApplicationSummary.Metric(deploymentMetrics.documentCount(),
- deploymentMetrics.queriesPerSecond(),
- deploymentMetrics.writesPerSecond()));
- }
- return new ApplicationSummary(app.id(), app.activity().lastQueried(), app.activity().lastWritten(), metrics);
+ for (Instance instance : app.instances().values())
+ for (var kv : instance.deployments().entrySet()) {
+ var zone = kv.getKey();
+ var deploymentMetrics = kv.getValue().metrics();
+ metrics.put(zone, new ApplicationSummary.Metric(deploymentMetrics.documentCount(),
+ deploymentMetrics.queriesPerSecond(),
+ deploymentMetrics.writesPerSecond()));
+ }
+ // TODO jonmv: Default instance should really be replaced with something better.
+ return new ApplicationSummary(app.id().defaultInstance(), app.activity().lastQueried(), app.activity().lastWritten(), metrics);
}
/** Escalate ownership issues which have not been closed before a defined amount of time has passed. */
private void ensureConfirmationResponses() {
- for (Instance instance : controller().applications().asList())
- instance.ownershipIssueId().ifPresent(issueId -> {
+ for (Application application : controller().applications().asList())
+ application.ownershipIssueId().ifPresent(issueId -> {
try {
- Tenant tenant = tenantOf(instance.id());
+ Tenant tenant = tenantOf(application.id());
ownershipIssues.ensureResponse(issueId, tenant.type() == Tenant.Type.athenz ? tenant.contact() : Optional.empty());
}
catch (RuntimeException e) {
@@ -99,28 +104,26 @@ public class ApplicationOwnershipConfirmer extends Maintainer {
}
private void updateConfirmedApplicationOwners() {
- InstanceList.from(controller().applications().asList())
- .withProjectId()
- .hasProductionDeployment()
- .asList()
- .stream()
- .filter(application -> application.ownershipIssueId().isPresent())
- .forEach(application -> {
- IssueId ownershipIssueId = application.ownershipIssueId().get();
- ownershipIssues.getConfirmedOwner(ownershipIssueId).ifPresent(owner -> {
- controller().applications().lockApplicationIfPresent(application.id(), lockedApplication ->
- controller().applications().store(lockedApplication.withOwner(owner)));
- });
- });
+ ApplicationList.from(controller().applications().asList())
+ .withProjectId()
+ .withProductionDeployment()
+ .asList()
+ .stream()
+ .filter(application -> application.ownershipIssueId().isPresent())
+ .forEach(application -> {
+ IssueId ownershipIssueId = application.ownershipIssueId().get();
+ ownershipIssues.getConfirmedOwner(ownershipIssueId).ifPresent(owner -> {
+ controller().applications().lockApplicationIfPresent(application.id(), lockedApplication ->
+ controller().applications().store(lockedApplication.withOwner(owner)));
+ });
+ });
}
- private User determineAssignee(Tenant tenant, Instance instance) {
- return instance.owner().orElse(
- tenant instanceof UserTenant ? userFor(tenant) : null
- );
+ private User determineAssignee(Tenant tenant, Application application) {
+ return application.owner().orElse(tenant instanceof UserTenant ? userFor(tenant) : null);
}
- private Tenant tenantOf(ApplicationId applicationId) {
+ private Tenant tenantOf(TenantAndApplicationId applicationId) {
return controller().tenants().get(applicationId.tenant())
.orElseThrow(() -> new IllegalStateException("No tenant found for application " + applicationId));
}
@@ -129,7 +132,7 @@ public class ApplicationOwnershipConfirmer extends Maintainer {
return User.from(tenant.name().value().replaceFirst(Tenant.userPrefix, ""));
}
- protected void store(IssueId issueId, ApplicationId applicationId) {
+ protected void store(IssueId issueId, TenantAndApplicationId applicationId) {
controller().applications().lockApplicationIfPresent(applicationId, application ->
controller().applications().store(application.withOwnershipIssueId(issueId)));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java
index 37d19986cef..2ab1b4c93ed 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java
@@ -28,10 +28,9 @@ public class BillingMaintainer extends Maintainer {
.filter(tenant -> tenant instanceof CloudTenant)
.map(tenant -> (CloudTenant) tenant)
.forEach(cloudTenant -> controller().applications().asList(cloudTenant.name())
- .stream()
- .forEach(application -> {
- billing.handleBilling(application.id(), cloudTenant.billingInfo().customerId());
- })
+ .forEach(application -> {
+ billing.handleBilling(application.id().defaultInstance(), cloudTenant.billingInfo().customerId());
+ })
);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
index c005cec598c..3b76aba94ba 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
@@ -72,16 +73,20 @@ public class ClusterInfoMaintainer extends Maintainer {
@Override
protected void maintain() {
- for (Instance instance : controller().applications().asList()) {
- for (Deployment deployment : instance.deployments().values()) {
- DeploymentId deploymentId = new DeploymentId(instance.id(), deployment.zone());
- try {
- var nodes = nodeRepository.list(deploymentId.zoneId(), deploymentId.applicationId());
- Map<ClusterSpec.Id, ClusterInfo> clusterInfo = getClusterInfo(nodes);
- controller().applications().lockIfPresent(instance.id(), lockedApplication ->
- controller.applications().store(lockedApplication.withClusterInfo(deployment.zone(), clusterInfo)));
- } catch (Exception e) {
- log.log(Level.WARNING, "Failing getting cluster information for " + deploymentId, e);
+ for (Application application : controller().applications().asList()) {
+ for (Instance instance : application.instances().values()) {
+ for (Deployment deployment : instance.deployments().values()) {
+ DeploymentId deploymentId = new DeploymentId(instance.id(), deployment.zone());
+ try {
+ var nodes = nodeRepository.list(deploymentId.zoneId(), deploymentId.applicationId());
+ Map<ClusterSpec.Id, ClusterInfo> clusterInfo = getClusterInfo(nodes);
+ controller().applications().lockApplicationIfPresent(application.id(), lockedApplication ->
+ controller.applications().store(lockedApplication.with(instance.name(),
+ locked -> locked.withClusterInfo(deployment.zone(), clusterInfo))));
+ }
+ catch (Exception e) {
+ log.log(Level.WARNING, "Failing getting cluster information for " + deploymentId, e);
+ }
}
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
index 44e0c9fd393..7756e6b23a7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.Deployment;
@@ -23,20 +24,20 @@ public class DeploymentExpirer extends Maintainer {
@Override
protected void maintain() {
- for (Instance instance : controller().applications().asList()) {
- for (Deployment deployment : instance.deployments().values()) {
- if (!isExpired(deployment)) continue;
-
- try {
- log.log(Level.INFO, "Expiring deployment of " + instance.id() + " in " + deployment.zone());
- controller().applications().deactivate(instance.id(), deployment.zone());
- } catch (Exception e) {
- log.log(Level.WARNING, "Could not expire " + deployment + " of " + instance +
- ": " + Exceptions.toMessageString(e) + ". Retrying in " +
- maintenanceInterval());
+ for (Application application : controller().applications().asList())
+ for (Instance instance : application.instances().values())
+ for (Deployment deployment : instance.deployments().values()) {
+ if ( ! isExpired(deployment)) continue;
+
+ try {
+ log.log(Level.INFO, "Expiring deployment of " + instance.id() + " in " + deployment.zone());
+ controller().applications().deactivate(instance.id(), deployment.zone());
+ } catch (Exception e) {
+ log.log(Level.WARNING, "Could not expire " + deployment + " of " + instance +
+ ": " + Exceptions.toMessageString(e) + ". Retrying in " +
+ maintenanceInterval());
+ }
}
- }
- }
}
/** Returns whether given deployment has expired according to its TTL */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
index 56df43d707c..7ae00fe51e8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
@@ -4,12 +4,15 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.InstanceList;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.yolean.Exceptions;
@@ -50,10 +53,10 @@ public class DeploymentIssueReporter extends Maintainer {
}
/** Returns the applications to maintain issue status for. */
- private List<Instance> applications() {
- return InstanceList.from(controller().applications().asList())
- .withProjectId()
- .asList();
+ private List<Application> applications() {
+ return ApplicationList.from(controller().applications().asList())
+ .withProjectId()
+ .asList();
}
/**
@@ -61,18 +64,18 @@ public class DeploymentIssueReporter extends Maintainer {
* and store the issue id for the filed issues. Also, clear the issueIds of applications
* where deployment has not failed for this amount of time.
*/
- private void maintainDeploymentIssues(List<Instance> instances) {
- Set<ApplicationId> failingApplications = InstanceList.from(instances)
- .failingApplicationChangeSince(controller().clock().instant().minus(maxFailureAge))
- .asList().stream()
- .map(Instance::id)
- .collect(Collectors.toSet());
-
- for (Instance instance : instances)
- if (failingApplications.contains(instance.id()))
- fileDeploymentIssueFor(instance.id());
+ private void maintainDeploymentIssues(List<Application> applications) {
+ Set<TenantAndApplicationId> failingApplications = ApplicationList.from(applications)
+ .failingApplicationChangeSince(controller().clock().instant().minus(maxFailureAge))
+ .asList().stream()
+ .map(Application::id)
+ .collect(Collectors.toSet());
+
+ for (Application application : applications)
+ if (failingApplications.contains(application.id()))
+ fileDeploymentIssueFor(application.id());
else
- store(instance.id(), null);
+ store(application.id(), null);
}
/**
@@ -80,7 +83,7 @@ public class DeploymentIssueReporter extends Maintainer {
* applications that have been failing the upgrade to the system version for
* longer than the set grace period, or update this list if the issue already exists.
*/
- private void maintainPlatformIssue(List<Instance> instances) {
+ private void maintainPlatformIssue(List<Application> applications) {
if (controller().system() == SystemName.cd)
return;
@@ -89,21 +92,25 @@ public class DeploymentIssueReporter extends Maintainer {
if ((controller().versionStatus().version(systemVersion).confidence() != broken))
return;
- if (InstanceList.from(instances)
- .failingUpgradeToVersionSince(systemVersion, controller().clock().instant().minus(upgradeGracePeriod))
- .isEmpty())
+ if (ApplicationList.from(applications)
+ .failingUpgradeToVersionSince(systemVersion, controller().clock().instant().minus(upgradeGracePeriod))
+ .isEmpty())
return;
- List<ApplicationId> failingApplications = InstanceList.from(instances)
- .failingUpgradeToVersionSince(systemVersion, controller().clock().instant())
- .idList();
+ List<ApplicationId> failingApplications = ApplicationList.from(applications)
+ .failingUpgradeToVersionSince(systemVersion, controller().clock().instant())
+ .idList()
+ .stream()
+ .map(TenantAndApplicationId::defaultInstance)
+ .collect(Collectors.toUnmodifiableList());
+ // TODO jonmv: Send only tenant and application, here and elsewhere in this.
deploymentIssues.fileUnlessOpen(failingApplications, systemVersion);
}
- private Tenant ownerOf(ApplicationId applicationId) {
+ private Tenant ownerOf(TenantAndApplicationId applicationId) {
return controller().tenants().get(applicationId.tenant())
- .orElseThrow(() -> new IllegalStateException("No tenant found for application " + applicationId));
+ .orElseThrow(() -> new IllegalStateException("No tenant found for application " + applicationId));
}
private User userFor(Tenant tenant) {
@@ -111,13 +118,13 @@ public class DeploymentIssueReporter extends Maintainer {
}
/** File an issue for applicationId, if it doesn't already have an open issue associated with it. */
- private void fileDeploymentIssueFor(ApplicationId applicationId) {
+ private void fileDeploymentIssueFor(TenantAndApplicationId applicationId) {
try {
Tenant tenant = ownerOf(applicationId);
tenant.contact().ifPresent(contact -> {
User assignee = tenant.type() == Tenant.Type.user ? userFor(tenant) : null;
- Optional<IssueId> ourIssueId = controller().applications().require(applicationId).deploymentJobs().issueId();
- IssueId issueId = deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, assignee, contact);
+ Optional<IssueId> ourIssueId = controller().applications().requireApplication(applicationId).deploymentIssueId();
+ IssueId issueId = deploymentIssues.fileUnlessOpen(ourIssueId, applicationId.defaultInstance(), assignee, contact);
store(applicationId, issueId);
});
}
@@ -127,8 +134,8 @@ public class DeploymentIssueReporter extends Maintainer {
}
/** Escalate issues for which there has been no activity for a certain amount of time. */
- private void escalateInactiveDeploymentIssues(Collection<Instance> instances) {
- instances.forEach(application -> application.deploymentJobs().issueId().ifPresent(issueId -> {
+ private void escalateInactiveDeploymentIssues(Collection<Application> applications) {
+ applications.forEach(application -> application.deploymentIssueId().ifPresent(issueId -> {
try {
Tenant tenant = ownerOf(application.id());
deploymentIssues.escalateIfInactive(issueId,
@@ -141,7 +148,7 @@ public class DeploymentIssueReporter extends Maintainer {
}));
}
- private void store(ApplicationId id, IssueId issueId) {
+ private void store(TenantAndApplicationId id, IssueId issueId) {
controller().applications().lockApplicationIfPresent(id, application ->
controller().applications().store(application.withDeploymentIssueId(issueId)));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
index d77856a1661..162e46e19d2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.SystemName;
import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -41,40 +42,40 @@ public class DeploymentMetricsMaintainer extends Maintainer {
@Override
protected void maintain() {
AtomicInteger failures = new AtomicInteger(0);
+ AtomicInteger attempts = new AtomicInteger(0);
AtomicReference<Exception> lastException = new AtomicReference<>(null);
- List<Instance> instanceList = applications.asList();
// Run parallel stream inside a custom ForkJoinPool so that we can control the number of threads used
ForkJoinPool pool = new ForkJoinPool(applicationsToUpdateInParallel);
pool.submit(() ->
- instanceList.parallelStream().forEach(application -> {
- applications.lockIfPresent(application.id(), locked ->
- applications.store(locked.with(controller().metrics().getApplicationMetrics(application.id()))));
+ applications.asList().parallelStream().forEach(application -> {
+ for (Instance instance : application.instances().values())
+ for (Deployment deployment : instance.deployments().values()) {
+ attempts.incrementAndGet();
+ try {
+ if (deployment.version().getMajor() < 7) continue;
+ var collectedMetrics = controller().metrics().getDeploymentMetrics(instance.id(), deployment.zone());
+ var now = controller().clock().instant();
+ applications.lockApplicationIfPresent(application.id(), locked -> {
+ Deployment existingDeployment = locked.get().require(instance.name()).deployments().get(deployment.zone());
+ if (existingDeployment == null) return; // Deployment removed since we started collecting metrics
+ DeploymentMetrics newMetrics = existingDeployment.metrics()
+ .withQueriesPerSecond(collectedMetrics.queriesPerSecond())
+ .withWritesPerSecond(collectedMetrics.writesPerSecond())
+ .withDocumentCount(collectedMetrics.documentCount())
+ .withQueryLatencyMillis(collectedMetrics.queryLatencyMillis())
+ .withWriteLatencyMillis(collectedMetrics.writeLatencyMillis())
+ .at(now);
+ applications.store(locked.with(instance.name(),
+ lockedInstance -> lockedInstance.with(existingDeployment.zone(), newMetrics)
+ .recordActivityAt(now, existingDeployment.zone())));
- for (Deployment deployment : application.deployments().values()) {
- try {
- if (deployment.version().getMajor() < 7) continue;
- var collectedMetrics = controller().metrics().getDeploymentMetrics(application.id(), deployment.zone());
- var now = controller().clock().instant();
- applications.lockIfPresent(application.id(), locked -> {
- Deployment existingDeployment = locked.get().deployments().get(deployment.zone());
- if (existingDeployment == null) return; // Deployment removed since we started collecting metrics
- DeploymentMetrics newMetrics = existingDeployment.metrics()
- .withQueriesPerSecond(collectedMetrics.queriesPerSecond())
- .withWritesPerSecond(collectedMetrics.writesPerSecond())
- .withDocumentCount(collectedMetrics.documentCount())
- .withQueryLatencyMillis(collectedMetrics.queryLatencyMillis())
- .withWriteLatencyMillis(collectedMetrics.writeLatencyMillis())
- .at(now);
- applications.store(locked.with(existingDeployment.zone(), newMetrics)
- .recordActivityAt(now, existingDeployment.zone()));
-
- });
- } catch (Exception e) {
- failures.incrementAndGet();
- lastException.set(e);
+ });
+ } catch (Exception e) {
+ failures.incrementAndGet();
+ lastException.set(e);
+ }
}
- }
})
);
pool.shutdown();
@@ -84,7 +85,7 @@ public class DeploymentMetricsMaintainer extends Maintainer {
log.log(LogLevel.WARNING,
String.format("Failed to gather metrics for %d/%d applications. Retrying in %s. Last error: %s",
failures.get(),
- instanceList.size(),
+ attempts.get(),
maintenanceInterval(),
Exceptions.toMessageString(lastException.get())));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
index ba3b2118e9f..073a9e43b57 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
@@ -4,9 +4,11 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.google.common.collect.ImmutableMap;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.jdisc.Metric;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.InstanceList;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
@@ -61,24 +63,26 @@ public class MetricsReporter extends Maintainer {
}
private void reportDeploymentMetrics() {
- InstanceList applications = InstanceList.from(controller().applications().asList())
- .hasProductionDeployment();
+ List<Instance> instances = ApplicationList.from(controller().applications().asList())
+ .withProductionDeployment().asList().stream()
+ .flatMap(application -> application.instances().values().stream())
+ .collect(Collectors.toUnmodifiableList());
- metric.set(DEPLOYMENT_FAIL_METRIC, deploymentFailRatio(applications) * 100, metric.createContext(Collections.emptyMap()));
+ metric.set(DEPLOYMENT_FAIL_METRIC, deploymentFailRatio(instances) * 100, metric.createContext(Collections.emptyMap()));
- averageDeploymentDurations(applications, clock.instant()).forEach((application, duration) -> {
+ averageDeploymentDurations(instances, clock.instant()).forEach((application, duration) -> {
metric.set(DEPLOYMENT_AVERAGE_DURATION, duration.getSeconds(), metric.createContext(dimensions(application)));
});
- deploymentsFailingUpgrade(applications).forEach((application, failingJobs) -> {
+ deploymentsFailingUpgrade(instances).forEach((application, failingJobs) -> {
metric.set(DEPLOYMENT_FAILING_UPGRADES, failingJobs, metric.createContext(dimensions(application)));
});
- deploymentWarnings(applications).forEach((application, warnings) -> {
+ deploymentWarnings(instances).forEach((application, warnings) -> {
metric.set(DEPLOYMENT_WARNINGS, warnings, metric.createContext(dimensions(application)));
});
- for (Instance instance : applications.asList())
+ for (Instance instance : instances)
instance.deploymentJobs().statusOf(JobType.component)
.flatMap(JobStatus::lastSuccess)
.flatMap(run -> run.application().buildTime())
@@ -92,24 +96,21 @@ public class MetricsReporter extends Maintainer {
metric.createContext(Map.of()));
}
- private static double deploymentFailRatio(InstanceList instanceList) {
- List<Instance> instances = instanceList.asList();
- if (instances.isEmpty()) return 0;
-
- return (double) instances.stream().filter(a -> a.deploymentJobs().hasFailures()).count() /
- (double) instances.size();
+ private static double deploymentFailRatio(List<Instance> instances) {
+ return instances.stream()
+ .mapToInt(instance -> instance.deploymentJobs().hasFailures() ? 1 : 0)
+ .average().orElse(0);
}
- private static Map<ApplicationId, Duration> averageDeploymentDurations(InstanceList applications, Instant now) {
- return applications.asList().stream()
- .collect(Collectors.toMap(Instance::id,
- application -> averageDeploymentDuration(application, now)));
+ private static Map<ApplicationId, Duration> averageDeploymentDurations(List<Instance> instances, Instant now) {
+ return instances.stream()
+ .collect(Collectors.toMap(Instance::id,
+ instance -> averageDeploymentDuration(instance, now)));
}
- private static Map<ApplicationId, Integer> deploymentsFailingUpgrade(InstanceList applications) {
- return applications.asList()
- .stream()
- .collect(Collectors.toMap(Instance::id, MetricsReporter::deploymentsFailingUpgrade));
+ private static Map<ApplicationId, Integer> deploymentsFailingUpgrade(List<Instance> instances) {
+ return instances.stream()
+ .collect(Collectors.toMap(Instance::id, MetricsReporter::deploymentsFailingUpgrade));
}
private static int deploymentsFailingUpgrade(Instance instance) {
@@ -134,9 +135,9 @@ public class MetricsReporter extends Maintainer {
.orElse(Duration.ZERO);
}
- private static Map<ApplicationId, Integer> deploymentWarnings(InstanceList applications) {
- return applications.asList().stream()
- .collect(Collectors.toMap(Instance::id, a -> maxWarningCountOf(a.deployments().values())));
+ private static Map<ApplicationId, Integer> deploymentWarnings(List<Instance> instances) {
+ return instances.stream()
+ .collect(Collectors.toMap(Instance::id, a -> maxWarningCountOf(a.deployments().values())));
}
private static int maxWarningCountOf(Collection<Deployment> deployments) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
index 047905c0841..95e1c53f10c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -19,11 +20,11 @@ public class OutstandingChangeDeployer extends Maintainer {
@Override
protected void maintain() {
- for (Instance instance : controller().applications().asList()) {
- if (instance.outstandingChange().hasTargets()
- && instance.deploymentSpec().canChangeRevisionAt(controller().clock().instant())) {
- controller().applications().deploymentTrigger().triggerChange(instance.id(),
- instance.outstandingChange());
+ for (Application application : controller().applications().asList()) {
+ if (application.outstandingChange().hasTargets()
+ && application.deploymentSpec().canChangeRevisionAt(controller().clock().instant())) {
+ controller().applications().deploymentTrigger().triggerChange(application.id(),
+ application.outstandingChange());
}
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java
index 6c7d4cab908..9503167360e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java
@@ -7,7 +7,9 @@ import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingService;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.InstanceList;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import com.yahoo.vespa.hosted.controller.rotation.RotationState;
import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
@@ -42,16 +44,22 @@ public class RotationStatusUpdater extends Maintainer {
@Override
protected void maintain() {
var failures = new AtomicInteger(0);
+ var attempts = new AtomicInteger(0);
var lastException = new AtomicReference<Exception>(null);
- var applicationList = InstanceList.from(applications.asList()).hasRotation();
+ var instancesWithRotations = ApplicationList.from(applications.asList()).hasRotation().asList().stream()
+ .flatMap(application -> application.instances().values().stream())
+ .filter(instance -> ! instance.rotations().isEmpty());
// Run parallel stream inside a custom ForkJoinPool so that we can control the number of threads used
var pool = new ForkJoinPool(applicationsToUpdateInParallel);
pool.submit(() -> {
- applicationList.asList().parallelStream().forEach(application -> {
+ instancesWithRotations.parallel().forEach(instance -> {
+ attempts.incrementAndGet();
try {
- applications.lockIfPresent(application.id(), (app) -> applications.store(app.with(getStatus(app.get()))));
+ RotationStatus status = getStatus(instance);
+ applications.lockApplicationIfPresent(TenantAndApplicationId.from(instance.id()), app ->
+ applications.store(app.with(instance.name(), locked -> locked.with(status))));
} catch (Exception e) {
failures.incrementAndGet();
lastException.set(e);
@@ -64,7 +72,7 @@ public class RotationStatusUpdater extends Maintainer {
if (lastException.get() != null) {
log.log(LogLevel.WARNING, String.format("Failed to get global routing status of %d/%d applications. Retrying in %s. Last error: ",
failures.get(),
- applicationList.size(),
+ attempts.get(),
maintenanceInterval()),
lastException.get());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index c4ca94e169d..d32a10dfbbf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -4,8 +4,10 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy;
import com.yahoo.vespa.curator.Lock;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.InstanceList;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
@@ -70,7 +72,7 @@ public class Upgrader extends Maintainer {
cancelUpgradesOf(applications().with(UpgradePolicy.conservative).upgrading().failing().notUpgradingTo(conservativeTargets), reason);
// Schedule the right upgrades
- InstanceList applications = applications();
+ ApplicationList applications = applications();
canaryTarget.ifPresent(target -> upgrade(applications.with(UpgradePolicy.canary), target));
defaultTargets.forEach(target -> upgrade(applications.with(UpgradePolicy.defaultPolicy), target));
conservativeTargets.forEach(target -> upgrade(applications.with(UpgradePolicy.conservative), target));
@@ -90,12 +92,12 @@ public class Upgrader extends Maintainer {
}
/** Returns a list of all applications, except those which are pinned — these should not be manipulated by the Upgrader */
- private InstanceList applications() {
- return InstanceList.from(controller().applications().asList()).unpinned();
+ private ApplicationList applications() {
+ return ApplicationList.from(controller().applications().asList()).unpinned();
}
- private void upgrade(InstanceList applications, Version version) {
- applications = applications.hasProductionDeployment();
+ private void upgrade(ApplicationList applications, Version version) {
+ applications = applications.withProductionDeployment();
applications = applications.onLowerVersionThan(version);
applications = applications.allowMajorVersion(version.getMajor(), targetMajorVersion().orElse(version.getMajor()));
applications = applications.notDeploying(); // wait with applications deploying an application change or already upgrading
@@ -103,15 +105,15 @@ public class Upgrader extends Maintainer {
applications = applications.canUpgradeAt(controller().clock().instant()); // wait with applications that are currently blocking upgrades
applications = applications.byIncreasingDeployedVersion(); // start with lowest versions
applications = applications.first(numberOfApplicationsToUpgrade()); // throttle upgrades
- for (Instance instance : applications.asList())
- controller().applications().deploymentTrigger().triggerChange(instance.id(), Change.of(version));
+ for (Application application : applications.asList())
+ controller().applications().deploymentTrigger().triggerChange(application.id(), Change.of(version));
}
- private void cancelUpgradesOf(InstanceList applications, String reason) {
+ private void cancelUpgradesOf(ApplicationList applications, String reason) {
if (applications.isEmpty()) return;
log.info("Cancelling upgrading of " + applications.asList().size() + " applications: " + reason);
- for (Instance instance : applications.asList())
- controller().applications().deploymentTrigger().cancelChange(instance.id(), PLATFORM);
+ for (Application application : applications.asList())
+ controller().applications().deploymentTrigger().cancelChange(application.id(), PLATFORM);
}
/** Returns the number of applications to upgrade in this run */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index 6d321231d00..f6ed4d29bed 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -6,6 +6,7 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
@@ -13,6 +14,7 @@ import com.yahoo.slime.Inspector;
import com.yahoo.slime.ObjectTraverser;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
@@ -29,6 +31,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import com.yahoo.vespa.hosted.controller.rotation.RotationState;
@@ -67,23 +70,30 @@ public class ApplicationSerializer {
private static final String createdAtField = "createdAt";
private static final String deploymentSpecField = "deploymentSpecField";
private static final String validationOverridesField = "validationOverrides";
- private static final String deploymentsField = "deployments";
- private static final String deploymentJobsField = "deploymentJobs";
+ private static final String instancesField = "instances";
private static final String deployingField = "deployingField";
+ private static final String projectIdField = "projectId";
+ private static final String builtInternallyField = "builtInternally";
private static final String pinnedField = "pinned";
private static final String outstandingChangeField = "outstandingChangeField";
+ private static final String deploymentIssueField = "deploymentIssueId";
private static final String ownershipIssueIdField = "ownershipIssueId";
private static final String ownerField = "confirmedOwner";
private static final String majorVersionField = "majorVersion";
private static final String writeQualityField = "writeQuality";
private static final String queryQualityField = "queryQuality";
private static final String pemDeployKeyField = "pemDeployKey";
- private static final String assignedRotationsField = "assignedRotations";
- private static final String assignedRotationEndpointField = "endpointId";
private static final String assignedRotationClusterField = "clusterId";
private static final String assignedRotationRotationField = "rotationId";
private static final String applicationCertificateField = "applicationCertificate";
+ // Instance fields
+ private static final String instanceNameField = "instanceName";
+ private static final String deploymentsField = "deployments";
+ private static final String deploymentJobsField = "deploymentJobs";
+ private static final String assignedRotationsField = "assignedRotations";
+ private static final String assignedRotationEndpointField = "endpointId";
+
// Deployment fields
private static final String zoneField = "zone";
private static final String environmentField = "environment";
@@ -104,10 +114,7 @@ public class ApplicationSerializer {
private static final String lastWritesPerSecondField = "lastWritesPerSecond";
// DeploymentJobs fields
- private static final String projectIdField = "projectId";
private static final String jobStatusField = "jobStatus";
- private static final String issueIdField = "jiraIssueId";
- private static final String builtInternallyField = "builtInternally";
// JobStatus field
private static final String jobTypeField = "jobType";
@@ -162,28 +169,39 @@ public class ApplicationSerializer {
// ------------------ Serialization
- public Slime toSlime(Application instance) {
+ public Slime toSlime(Application application) {
Slime slime = new Slime();
Cursor root = slime.setObject();
- root.setString(idField, instance.id().serializedForm());
- root.setLong(createdAtField, instance.createdAt().toEpochMilli());
- root.setString(deploymentSpecField, instance.deploymentSpec().xmlForm());
- root.setString(validationOverridesField, instance.validationOverrides().xmlForm());
- deploymentsToSlime(instance.deployments().values(), root.setArray(deploymentsField));
- toSlime(instance.deploymentJobs(), root.setObject(deploymentJobsField));
- toSlime(instance.change(), root, deployingField);
- toSlime(instance.outstandingChange(), root, outstandingChangeField);
- instance.ownershipIssueId().ifPresent(issueId -> root.setString(ownershipIssueIdField, issueId.value()));
- instance.owner().ifPresent(owner -> root.setString(ownerField, owner.username()));
- instance.majorVersion().ifPresent(majorVersion -> root.setLong(majorVersionField, majorVersion));
- root.setDouble(queryQualityField, instance.metrics().queryServiceQuality());
- root.setDouble(writeQualityField, instance.metrics().writeServiceQuality());
- instance.pemDeployKey().ifPresent(pemDeployKey -> root.setString(pemDeployKeyField, pemDeployKey));
- assignedRotationsToSlime(instance.rotations(), root, assignedRotationsField);
- toSlime(instance.rotationStatus(), root.setArray(rotationStatusField));
+ root.setString(idField, application.id().serialized());
+ root.setLong(createdAtField, application.createdAt().toEpochMilli());
+ root.setString(deploymentSpecField, application.deploymentSpec().xmlForm());
+ root.setString(validationOverridesField, application.validationOverrides().xmlForm());
+ application.projectId().ifPresent(projectId -> root.setLong(projectIdField, projectId));
+ application.deploymentIssueId().ifPresent(jiraIssueId -> root.setString(deploymentIssueField, jiraIssueId.value()));
+ application.ownershipIssueId().ifPresent(issueId -> root.setString(ownershipIssueIdField, issueId.value()));
+ root.setBool(builtInternallyField, application.internal());
+ toSlime(application.change(), root, deployingField);
+ toSlime(application.outstandingChange(), root, outstandingChangeField);
+ application.owner().ifPresent(owner -> root.setString(ownerField, owner.username()));
+ application.majorVersion().ifPresent(majorVersion -> root.setLong(majorVersionField, majorVersion));
+ root.setDouble(queryQualityField, application.metrics().queryServiceQuality());
+ root.setDouble(writeQualityField, application.metrics().writeServiceQuality());
+ application.pemDeployKey().ifPresent(pemDeployKey -> root.setString(pemDeployKeyField, pemDeployKey));
+ instancesToSlime(application, root.setArray(instancesField));
return slime;
}
+ private void instancesToSlime(Application application, Cursor array) {
+ for (Instance instance : application.instances().values()) {
+ Cursor instanceObject = array.addObject();
+ instanceObject.setString(instanceNameField, instance.name().value());
+ deploymentsToSlime(instance.deployments().values(), instanceObject.setArray(deploymentsField));
+ toSlime(instance.deploymentJobs(), instanceObject.setObject(deploymentJobsField));
+ assignedRotationsToSlime(instance.rotations(), instanceObject, assignedRotationsField);
+ toSlime(instance.rotationStatus(), instanceObject.setArray(rotationStatusField));
+ }
+ }
+
private void deploymentsToSlime(Collection<Deployment> deployments, Cursor array) {
for (Deployment deployment : deployments)
deploymentToSlime(deployment, array.addObject());
@@ -273,10 +291,7 @@ public class ApplicationSerializer {
}
private void toSlime(DeploymentJobs deploymentJobs, Cursor cursor) {
- deploymentJobs.projectId().ifPresent(projectId -> cursor.setLong(projectIdField, projectId));
jobStatusToSlime(deploymentJobs.jobStatus().values(), cursor.setArray(jobStatusField));
- deploymentJobs.issueId().ifPresent(jiraIssueId -> cursor.setString(issueIdField, jiraIssueId.value()));
- cursor.setBool(builtInternallyField, deploymentJobs.deployedInternally());
}
private void jobStatusToSlime(Collection<JobStatus> jobStatuses, Cursor jobStatusArray) {
@@ -347,26 +362,43 @@ public class ApplicationSerializer {
public Application fromSlime(Slime slime) {
Inspector root = slime.get();
- ApplicationId id = ApplicationId.fromSerializedForm(root.field(idField).asString());
+ TenantAndApplicationId id = TenantAndApplicationId.fromSerialized(root.field(idField).asString());
Instant createdAt = Instant.ofEpochMilli(root.field(createdAtField).asLong());
DeploymentSpec deploymentSpec = DeploymentSpec.fromXml(root.field(deploymentSpecField).asString(), false);
ValidationOverrides validationOverrides = ValidationOverrides.fromXml(root.field(validationOverridesField).asString());
- List<Deployment> deployments = deploymentsFromSlime(root.field(deploymentsField));
- DeploymentJobs deploymentJobs = deploymentJobsFromSlime(root.field(deploymentJobsField));
Change deploying = changeFromSlime(root.field(deployingField));
Change outstandingChange = changeFromSlime(root.field(outstandingChangeField));
+ Optional<IssueId> deploymentIssueId = Serializers.optionalString(root.field(deploymentIssueField)).map(IssueId::from);
Optional<IssueId> ownershipIssueId = Serializers.optionalString(root.field(ownershipIssueIdField)).map(IssueId::from);
Optional<User> owner = Serializers.optionalString(root.field(ownerField)).map(User::from);
OptionalInt majorVersion = Serializers.optionalInteger(root.field(majorVersionField));
ApplicationMetrics metrics = new ApplicationMetrics(root.field(queryQualityField).asDouble(),
root.field(writeQualityField).asDouble());
Optional<String> pemDeployKey = Serializers.optionalString(root.field(pemDeployKeyField));
- List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(deploymentSpec, root);
- RotationStatus rotationStatus = rotationStatusFromSlime(root);
-
- return new Application(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs,
- deploying, outstandingChange, ownershipIssueId, owner, majorVersion, metrics,
- pemDeployKey, assignedRotations, rotationStatus);
+ List<Instance> instances = instancesFromSlime(id, deploymentSpec, root.field(instancesField));
+ OptionalLong projectId = Serializers.optionalLong(root.field(projectIdField));
+ boolean builtInternally = root.field(builtInternallyField).asBool();
+
+ return new Application(id, createdAt, deploymentSpec, validationOverrides, deploying, outstandingChange,
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics,
+ pemDeployKey, projectId, builtInternally, instances);
+ }
+
+ private List<Instance> instancesFromSlime(TenantAndApplicationId id, DeploymentSpec deploymentSpec, Inspector field) {
+ List<Instance> instances = new ArrayList<>();
+ field.traverse((ArrayTraverser) (name, object) -> {
+ InstanceName instanceName = InstanceName.from(object.field(instanceNameField).asString());
+ List<Deployment> deployments = deploymentsFromSlime(object.field(deploymentsField));
+ DeploymentJobs deploymentJobs = deploymentJobsFromSlime(object.field(deploymentJobsField));
+ List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(deploymentSpec, object);
+ RotationStatus rotationStatus = rotationStatusFromSlime(object);
+ instances.add(new Instance(id.instance(instanceName),
+ deployments,
+ deploymentJobs,
+ assignedRotations,
+ rotationStatus));
+ });
+ return instances;
}
private List<Deployment> deploymentsFromSlime(Inspector array) {
@@ -497,12 +529,9 @@ public class ApplicationSerializer {
}
private DeploymentJobs deploymentJobsFromSlime(Inspector object) {
- OptionalLong projectId = Serializers.optionalLong(object.field(projectIdField));
List<JobStatus> jobStatusList = jobStatusListFromSlime(object.field(jobStatusField));
- Optional<IssueId> issueId = Serializers.optionalString(object.field(issueIdField)).map(IssueId::from);
- boolean builtInternally = object.field(builtInternallyField).asBool();
- return new DeploymentJobs(projectId, jobStatusList, issueId, builtInternally);
+ return new DeploymentJobs(OptionalLong.empty(), jobStatusList, Optional.empty(), false); // WARNING: Unused variables.
}
private Change changeFromSlime(Inspector object) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
index e80372113e8..2680160b1cb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
@@ -6,6 +6,7 @@ import com.google.inject.Inject;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.path.Path;
@@ -341,13 +342,16 @@ public class CuratorDb {
// -------------- Applications ---------------------------------------------
public void writeApplication(Application application) {
- curator.set(applicationPath(TenantAndApplicationId.from(application.id())), asJson(applicationSerializer.toSlime(application)));
- curator.set(oldApplicationPath(application.id()), asJson(applicationSerializer.toSlime(application)));
- curator.set(instancePath(application.id()), asJson(applicationSerializer.toSlime(application)));
+ curator.set(applicationPath(application.id()), asJson(applicationSerializer.toSlime(application)));
+ for (InstanceName name : application.instances().keySet()) {
+ curator.set(oldApplicationPath(application.id().instance(name)),
+ asJson(instanceSerializer.toSlime(application.legacy(name))));
+ }
}
- public Optional<Application> readApplication(ApplicationId application) {
- return readSlime(instancePath(application)).map(applicationSerializer::fromSlime);
+ public Optional<Application> readApplication(TenantAndApplicationId application) {
+ List<Instance> instances = readInstances(id -> TenantAndApplicationId.from(id).equals(application));
+ return Application.aggregate(instances);
}
public List<Application> readApplications() {
@@ -358,92 +362,72 @@ public class CuratorDb {
return readApplications(application -> application.tenant().equals(name));
}
- private Stream<ApplicationId> readApplicationIds() {
- // TODO jonmv: Filter on number of parts in id when reading from applicationRoot again.
- return curator.getChildren(instanceRoot).stream().map(ApplicationId::fromSerializedForm);
- }
-
- private List<Application> readApplications(Predicate<ApplicationId> applicationFilter) {
- return readApplicationIds().filter(applicationFilter)
- .sorted()
- .map(this::readApplication)
- .flatMap(Optional::stream)
- .collect(Collectors.toUnmodifiableList());
- }
-
- public void removeApplication(ApplicationId id) {
- // WARNING: This is part of a multi-step data move operation, so don't touch!!!
- curator.delete(applicationPath(TenantAndApplicationId.from(id)));
- curator.delete(oldApplicationPath(id));
- curator.delete(instancePath(id));
+ private Stream<TenantAndApplicationId> readTenantAndApplicationIds() {
+ return readInstanceIds().map(TenantAndApplicationId::from).distinct();
}
- // -------------- Instances ---------------------------------------------
-
- public void writeInstance(Instance instance) {
- curator.set(applicationPath(TenantAndApplicationId.from(instance.id())), asJson(instanceSerializer.toSlime(instance)));
- curator.set(oldApplicationPath(instance.id()), asJson(instanceSerializer.toSlime(instance)));
- curator.set(instancePath(instance.id()), asJson(instanceSerializer.toSlime(instance)));
+ private List<Application> readApplications(Predicate<TenantAndApplicationId> applicationFilter) {
+ return readTenantAndApplicationIds().filter(applicationFilter)
+ .sorted()
+ .map(this::readApplication)
+ .flatMap(Optional::stream)
+ .collect(Collectors.toUnmodifiableList());
}
- public Optional<Instance> readInstance(ApplicationId application) {
- return readSlime(instancePath(application)).map(instanceSerializer::fromSlime);
- }
-
- public List<Instance> readInstances() {
- return readInstances(ignored -> true);
- }
-
- public List<Instance> readInstances(TenantName name) {
- return readInstances(instance -> instance.tenant().equals(name));
- }
-
- public List<Instance> readInstances(TenantAndApplicationId id) {
- return readInstances(instance -> instance.tenant().equals(id.tenant())
- && instance.application().equals(id.application()));
+ private Optional<Instance> readInstance(ApplicationId application) {
+ return readSlime(oldApplicationPath(application)).map(instanceSerializer::fromSlime);
}
private Stream<ApplicationId> readInstanceIds() {
- return curator.getChildren(instanceRoot).stream().map(ApplicationId::fromSerializedForm);
+ return curator.getChildren(applicationRoot).stream()
+ .filter(id -> id.split(":").length == 3)
+ .map(ApplicationId::fromSerializedForm);
}
- private List<Instance> readInstances(Predicate<ApplicationId> instanceFilter) {
- return readInstanceIds().filter(instanceFilter)
+ private List<Instance> readInstances(Predicate<ApplicationId> applicationFilter) {
+ return readInstanceIds().filter(applicationFilter)
.sorted()
.map(this::readInstance)
.flatMap(Optional::stream)
.collect(Collectors.toUnmodifiableList());
}
- public void removeInstance(ApplicationId id) {
+ public void removeApplication(ApplicationId id) {
// WARNING: This is part of a multi-step data move operation, so don't touch!!!
- curator.delete(applicationPath(TenantAndApplicationId.from(id)));
curator.delete(oldApplicationPath(id));
- curator.delete(instancePath(id));
+ if (readApplication(TenantAndApplicationId.from(id)).isEmpty())
+ curator.delete(applicationPath(TenantAndApplicationId.from(id)));
+ }
+
+ public void clearInstanceRoot() {
+ curator.delete(instanceRoot);
}
/**
* Migration plan:
*
* Add filter for reading only Instance from old application path RELEASED
- * Write instance to Instance and old application path RELEASED
+ * Write Instance to instance and old application path RELEASED
+ *
+ * Lock on application level for instance mutations MERGED
*
- * Write Instance to instance and application and old application paths DONE TO REMOVE
- * Read Instance from instance path DONE TO REMOVE
+ * Write Instance to instance and application and old application paths DONE TO CHANGE DONE
+ * Read Instance from instance path DONE TO REMOVE DONE
* Duplicate Application from Instance, with helper classes DONE
- * Write Application to instance and application and old application paths DONE TO CHANGE
- * Read Application from instance path DONE TO REMOVE
- * Use Application where applicable DO
- * Lock instances and application on same level: tenant + application DONE TO CHANGE
- * When reading an application, read all instances, and aggregate them
- * Write application with instances to application path
- * Lock application on both tenant + application, and instance levels DONE
+ * Write Application to instance and application and old application paths DONE TO CHANGE DONE
+ * Read Application from instance path DONE TO REMOVE DONE
+ * Use Application where applicable DONE !!!
+ * Lock instances and application on same level: tenant + application DONE TO CHANGE DONE
+ * When reading an application, read all instances, and aggregate them DONE
+ * Write application with instances to application path DONE
+ * Write all instances of an application to old application path DONE
+ * Remove everything under instance root DONE
*
* Read Application with instances from application path (with filter)
* Stop locking applications on instance level
*
* Stop writing Instance to old application path
- * Remove unused parts of Instance and Application
+ * Remove unused parts of Instance (Used only for legacy serialization)
*/
// -------------- Job Runs ------------------------------------------------
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 bfbd1beaa24..4b7414a42a6 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
@@ -9,6 +9,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
@@ -350,10 +351,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
TenantName tenant = TenantName.from(tenantName);
Slime slime = new Slime();
Cursor array = slime.setArray();
- for (Instance instance : controller.applications().asList(tenant)) {
- if (applicationName.isPresent() && ! instance.id().application().toString().equals(applicationName.get()))
- continue;
- toSlime(instance, array.addObject(), request);
+ for (Application application : controller.applications().asList(tenant)) {
+ if (applicationName.map(application.id().application().value()::equals).orElse(true))
+ for (InstanceName instance : application.instances().keySet())
+ toSlime(application.id().instance(instance), array.addObject(), request);
}
return new SlimeJsonResponse(slime);
}
@@ -395,7 +396,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private Instance getInstance(String tenantName, String applicationName, String instanceName) {
ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, instanceName);
- return controller.applications().get(applicationId)
+ return controller.applications().getInstance(applicationId)
.orElseThrow(() -> new NotExistsException(applicationId + " not found"));
}
@@ -498,8 +499,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.map(run -> run.application().source())
.ifPresent(source -> sourceRevisionToSlime(source, object.setObject("source")));
- instance.deploymentJobs().projectId()
- .ifPresent(id -> object.setLong("projectId", id));
+ application.projectId().ifPresent(id -> object.setLong("projectId", id));
// Currently deploying change
if ( ! application.change().isEmpty()) {
@@ -516,7 +516,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.steps(application.deploymentSpec())
.sortedJobs(instance.deploymentJobs().jobStatus().values());
- object.setBool("deployedInternally", application.deploymentJobs().deployedInternally());
+ object.setBool("deployedInternally", application.internal());
Cursor deploymentsArray = object.setArray("deploymentJobs");
for (JobStatus job : jobStatus) {
Cursor jobObject = deploymentsArray.addObject();
@@ -608,7 +608,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
}
- instance.pemDeployKey().ifPresent(key -> object.setString("pemDeployKey", key));
+ application.pemDeployKey().ifPresent(key -> object.setString("pemDeployKey", key));
// Metrics
Cursor metricsObject = object.setObject("metrics");
@@ -617,19 +617,19 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// Activity
Cursor activity = object.setObject("activity");
- instance.activity().lastQueried().ifPresent(instant -> activity.setLong("lastQueried", instant.toEpochMilli()));
- instance.activity().lastWritten().ifPresent(instant -> activity.setLong("lastWritten", instant.toEpochMilli()));
- instance.activity().lastQueriesPerSecond().ifPresent(value -> activity.setDouble("lastQueriesPerSecond", value));
- instance.activity().lastWritesPerSecond().ifPresent(value -> activity.setDouble("lastWritesPerSecond", value));
+ application.activity().lastQueried().ifPresent(instant -> activity.setLong("lastQueried", instant.toEpochMilli()));
+ application.activity().lastWritten().ifPresent(instant -> activity.setLong("lastWritten", instant.toEpochMilli()));
+ application.activity().lastQueriesPerSecond().ifPresent(value -> activity.setDouble("lastQueriesPerSecond", value));
+ application.activity().lastWritesPerSecond().ifPresent(value -> activity.setDouble("lastWritesPerSecond", value));
application.ownershipIssueId().ifPresent(issueId -> object.setString("ownershipIssueId", issueId.value()));
application.owner().ifPresent(owner -> object.setString("owner", owner.username()));
- application.deploymentJobs().issueId().ifPresent(issueId -> object.setString("deploymentIssueId", issueId.value()));
+ application.deploymentIssueId().ifPresent(issueId -> object.setString("deploymentIssueId", issueId.value()));
}
private HttpResponse deployment(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
- Instance instance = controller.applications().get(id)
+ Instance instance = controller.applications().getInstance(id)
.orElseThrow(() -> new NotExistsException(id + " not found"));
DeploymentId deploymentId = new DeploymentId(instance.id(),
@@ -683,7 +683,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
controller.zoneRegistry().getDeploymentTimeToLive(deploymentId.zoneId())
.ifPresent(deploymentTimeToLive -> response.setLong("expiryTimeEpochMs", deployment.at().plus(deploymentTimeToLive).toEpochMilli()));
- controller.applications().require(deploymentId.applicationId()).deploymentJobs().projectId()
+ controller.applications().requireApplication(TenantAndApplicationId.from(deploymentId.applicationId())).projectId()
.ifPresent(i -> response.setString("screwdriverId", String.valueOf(i)));
sourceRevisionToSlime(deployment.applicationVersion().source(), response);
@@ -749,7 +749,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
* If no known version is applicable, the newest version at least as old as the oldest platform is selected,
* among all versions released for this system. If no such versions exists, throws an IllegalStateException.
*/
- private Version compileVersion(ApplicationId id) {
+ private Version compileVersion(TenantAndApplicationId id) {
Version oldestPlatform = controller.applications().oldestInstalledPlatform(id);
return controller.versionStatus().versions().stream()
.filter(version -> version.confidence().equalOrHigherThan(VespaVersion.Confidence.low))
@@ -768,7 +768,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse setGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region, boolean inService, HttpRequest request) {
- Instance instance = controller.applications().require(ApplicationId.from(tenantName, applicationName, instanceName));
+ Instance instance = controller.applications().requireInstance(ApplicationId.from(tenantName, applicationName, instanceName));
ZoneId zone = ZoneId.from(environment, region);
Deployment deployment = instance.deployments().get(zone);
if (deployment == null) {
@@ -813,7 +813,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse rotationStatus(String tenantName, String applicationName, String instanceName, String environment, String region, Optional<String> endpointId) {
ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, instanceName);
- Instance instance = controller.applications().require(applicationId);
+ Instance instance = controller.applications().requireInstance(applicationId);
ZoneId zone = ZoneId.from(environment, region);
RotationId rotation = findRotationId(instance, endpointId);
Deployment deployment = instance.deployments().get(zone);
@@ -891,7 +891,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse deploying(String tenant, String application, String instance, HttpRequest request) {
- Instance app = controller.applications().require(ApplicationId.from(tenant, application, instance));
+ Application app = controller.applications().requireApplication(TenantAndApplicationId.from(tenant, application));
Slime slime = new Slime();
Cursor root = slime.setObject();
if ( ! app.change().isEmpty()) {
@@ -979,7 +979,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Application application = controller.applications().createApplication(id, credentials);
Slime slime = new Slime();
- toSlime(application, slime.setObject(), request);
+ toSlime(id, slime.setObject(), request);
return new SlimeJsonResponse(slime);
}
catch (ZmsClientException e) { // TODO: Push conversion down
@@ -994,9 +994,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse deployPlatform(String tenantName, String applicationName, String instanceName, boolean pin, HttpRequest request) {
request = controller.auditLogger().log(request);
String versionString = readToString(request.getData());
- ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
+ TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName);
StringBuilder response = new StringBuilder();
- controller.applications().lockOrThrow(id, application -> {
+ controller.applications().lockApplicationOrThrow(id, application -> {
Version version = Version.fromString(versionString);
if (version.equals(Version.emptyVersion))
version = controller.systemVersion();
@@ -1021,10 +1021,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
/** Trigger deployment to the last known application package for the given application. */
private HttpResponse deployApplication(String tenantName, String applicationName, String instanceName, HttpRequest request) {
controller.auditLogger().log(request);
- ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
+ TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName);
StringBuilder response = new StringBuilder();
- controller.applications().lockOrThrow(id, application -> {
- Change change = Change.of(application.get().deploymentJobs().statusOf(JobType.component).get().lastSuccess().get().application());
+ controller.applications().lockApplicationOrThrow(id, application -> {
+ Change change = Change.of(application.get().require(InstanceName.from(instanceName)).deploymentJobs().statusOf(JobType.component).get().lastSuccess().get().application());
controller.applications().deploymentTrigger().forceChange(id, change);
response.append("Triggered " + change + " for " + id);
});
@@ -1033,9 +1033,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
/** Cancel ongoing change for given application, e.g., everything with {"cancel":"all"} */
private HttpResponse cancelDeploy(String tenantName, String applicationName, String instanceName, String choice) {
- ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
+ TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName);
StringBuilder response = new StringBuilder();
- controller.applications().lockOrThrow(id, application -> {
+ controller.applications().lockApplicationOrThrow(id, application -> {
Change change = application.get().change();
if (change.isEmpty()) {
response.append("No deployment in progress for " + application + " at this time");
@@ -1045,7 +1045,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
ChangesToCancel cancel = ChangesToCancel.valueOf(choice.toUpperCase());
controller.applications().deploymentTrigger().cancelChange(id, cancel);
response.append("Changed deployment from '" + change + "' to '" +
- controller.applications().require(id).change() + "' for " + application);
+ controller.applications().requireApplication(id).change() + "' for " + application);
});
return new MessageResponse(response.toString());
@@ -1130,6 +1130,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Optional<ApplicationPackage> applicationPackage = Optional.ofNullable(dataParts.get("applicationZip"))
.map(ApplicationPackage::new);
+ Optional<Application> application = controller.applications().getApplication(TenantAndApplicationId.from(applicationId));
Inspector sourceRevision = deployOptions.field("sourceRevision");
Inspector buildNumber = deployOptions.field("buildNumber");
@@ -1143,7 +1144,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
applicationVersion = Optional.of(ApplicationVersion.from(toSourceRevision(sourceRevision),
buildNumber.asLong()));
- applicationPackage = Optional.of(controller.applications().getApplicationPackage(controller.applications().require(applicationId), applicationVersion.get()));
+ applicationPackage = Optional.of(controller.applications().getApplicationPackage(applicationId,
+ application.get().internal(),
+ applicationVersion.get()));
}
boolean deployDirectly = deployOptions.field("deployDirectly").asBool();
@@ -1152,7 +1155,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (deployDirectly && applicationPackage.isEmpty() && applicationVersion.isEmpty() && vespaVersion.isEmpty()) {
// Redeploy the existing deployment with the same versions.
- Optional<Deployment> deployment = controller.applications().get(applicationId)
+ Optional<Deployment> deployment = controller.applications().getInstance(applicationId)
.map(Instance::deployments)
.flatMap(deployments -> Optional.ofNullable(deployments.get(zone)));
@@ -1165,7 +1168,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
applicationVersion = Optional.of(version);
vespaVersion = Optional.of(deployment.get().version());
- applicationPackage = Optional.of(controller.applications().getApplicationPackage(controller.applications().require(applicationId), applicationVersion.get()));
+ applicationPackage = Optional.of(controller.applications().getApplicationPackage(applicationId,
+ application.get().internal(),
+ applicationVersion.get()));
}
// TODO: get rid of the json object
@@ -1221,7 +1226,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse deactivate(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
- Instance instance = controller.applications().require(ApplicationId.from(tenantName, applicationName, instanceName));
+ Instance instance = controller.applications().requireInstance(ApplicationId.from(tenantName, applicationName, instanceName));
// Attempt to deactivate application even if the deployment is not known by the controller
DeploymentId deploymentId = new DeploymentId(instance.id(), ZoneId.from(environment, region));
@@ -1234,7 +1239,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
try {
DeploymentJobs.JobReport report = toJobReport(tenant, application, toSlime(request.getData()).get());
if ( report.jobType() == JobType.component
- && controller.applications().require(report.applicationId()).deploymentJobs().deployedInternally())
+ && controller.applications().requireApplication(TenantAndApplicationId.from(report.applicationId())).internal())
throw new IllegalArgumentException(report.applicationId() + " is set up to be deployed from internally, and no " +
"longer accepts submissions from Screwdriver v3 jobs. If you need to revert " +
"to the old pipeline, please file a ticket at yo/vespa-support and request this.");
@@ -1313,19 +1318,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
default: throw new IllegalArgumentException("Unexpected tenant type '" + tenant.type() + "'.");
}
Cursor applicationArray = object.setArray("applications");
- for (Instance instance : controller.applications().asList(tenant.name())) {
- if ( ! instance.id().instance().isTester()) {
+ for (Application application : controller.applications().asList(tenant.name()))
+ for (Instance instance : application.instances().values())
if (recurseOverApplications(request))
- toSlime(applicationArray.addObject(),
- instance,
- getApplication(instance.id().tenant().value(),
- instance.id().application().value(),
- instance.id().instance().value()),
- request);
+ toSlime(applicationArray.addObject(), instance, application, request);
else
- toSlime(instance, applicationArray.addObject(), request);
- }
- }
+ toSlime(instance.id(), applicationArray.addObject(), request);
}
// A tenant has different content when in a list ... antipattern, but not solvable before application/v5
@@ -1404,25 +1402,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return Joiner.on("/").join(elements);
}
- private void toSlime(Application application, Cursor object, HttpRequest request) {
- object.setString("tenant", application.id().tenant().value());
- object.setString("application", application.id().application().value());
- object.setString("instance", application.id().instance().value());
- object.setString("url", withPath("/application/v4" +
- "/tenant/" + application.id().tenant().value() +
- "/application/" + application.id().application().value() +
- "/instance/" + application.id().instance().value(),
- request.getUri()).toString());
- }
-
- private void toSlime(Instance instance, Cursor object, HttpRequest request) {
- object.setString("tenant", instance.id().tenant().value());
- object.setString("application", instance.id().application().value());
- object.setString("instance", instance.id().instance().value());
+ private void toSlime(ApplicationId id, Cursor object, HttpRequest request) {
+ object.setString("tenant", id.tenant().value());
+ object.setString("application", id.application().value());
+ object.setString("instance", id.instance().value());
object.setString("url", withPath("/application/v4" +
- "/tenant/" + instance.id().tenant().value() +
- "/application/" + instance.id().application().value() +
- "/instance/" + instance.id().instance().value(),
+ "/tenant/" + id.tenant().value() +
+ "/application/" + id.application().value() +
+ "/instance/" + id.instance().value(),
request.getUri()).toString());
}
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 697e95e75b3..90a4ecdef9a 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
@@ -10,6 +10,7 @@ import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.NotExistsException;
@@ -22,6 +23,7 @@ import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.deployment.Run;
@@ -69,17 +71,18 @@ class JobControllerApiHandlerHelper {
* @return Response with all job types that have recorded runs for the application _and_ the status for the last run of that type
*/
static HttpResponse jobTypeResponse(Controller controller, ApplicationId id, URI baseUriForJobs) {
- Instance instance = controller.applications().require(id);
- Change change = instance.change();
- DeploymentSteps steps = new DeploymentSteps(instance.deploymentSpec(), controller::system);
+ Application application = controller.applications().requireApplication(TenantAndApplicationId.from(id));
+ Instance instance = application.require(id.instance());
+ Change change = application.change();
+ DeploymentSteps steps = new DeploymentSteps(application.deploymentSpec(), controller::system);
// The logic for pending runs imitates DeploymentTrigger logic; not good, but the trigger wiring must be re-written to reuse :S
Map<JobType, Versions> pendingProduction =
steps.productionJobs().stream()
- .filter(type -> ! controller.applications().deploymentTrigger().isComplete(change, instance, type))
+ .filter(type -> ! controller.applications().deploymentTrigger().isComplete(change, change, instance, type))
.collect(Collectors.toMap(type -> type,
type -> Versions.from(change,
- instance,
+ application,
Optional.ofNullable(instance.deployments().get(type.zone(controller.system()))),
controller.systemVersion()),
(v1, v2) -> { throw new IllegalStateException("Entries '" + v1 + "' and '" + v2 + "' have the same key!"); },
@@ -97,8 +100,8 @@ class JobControllerApiHandlerHelper {
Cursor lastVersionsObject = responseObject.setObject("lastVersions");
if (instance.deploymentJobs().statusOf(component).flatMap(JobStatus::lastSuccess).isPresent()) {
- lastPlatformToSlime(lastVersionsObject.setObject("platform"), controller, instance, change, steps);
- lastApplicationToSlime(lastVersionsObject.setObject("application"), instance, change, steps, controller);
+ lastPlatformToSlime(lastVersionsObject.setObject("platform"), controller, application, instance, change, steps);
+ lastApplicationToSlime(lastVersionsObject.setObject("application"), application, instance, change, steps, controller);
}
Cursor deployingObject = responseObject.setObject("deploying");
@@ -128,6 +131,7 @@ class JobControllerApiHandlerHelper {
steps.jobs().forEach(type -> {
jobTypeToSlime(jobsObject.setObject(shortNameOf(type, controller.system())),
controller,
+ application,
instance,
type,
steps,
@@ -152,11 +156,11 @@ class JobControllerApiHandlerHelper {
return new SlimeJsonResponse(slime);
}
- private static void lastPlatformToSlime(Cursor lastPlatformObject, Controller controller, Instance instance, Change change, DeploymentSteps steps) {
+ private static void lastPlatformToSlime(Cursor lastPlatformObject, Controller controller, Application application, Instance instance, Change change, DeploymentSteps steps) {
VespaVersion lastVespa = controller.versionStatus().version(controller.systemVersion());
VespaVersion.Confidence targetConfidence = Map.of(defaultPolicy, normal,
conservative, high)
- .getOrDefault(instance.deploymentSpec().upgradePolicy(), broken);
+ .getOrDefault(application.deploymentSpec().upgradePolicy(), broken);
for (VespaVersion version : controller.versionStatus().versions())
if ( ! version.versionNumber().isAfter(controller.systemVersion())
&& version.confidence().equalOrHigherThan(targetConfidence))
@@ -165,39 +169,39 @@ class JobControllerApiHandlerHelper {
Version lastPlatform = lastVespa.versionNumber();
lastPlatformObject.setString("platform", lastPlatform.toString());
lastPlatformObject.setLong("at", lastVespa.committedAt().toEpochMilli());
- long completed = steps.productionJobs().stream().filter(type -> controller.applications().deploymentTrigger().isComplete(Change.of(lastPlatform), instance, type)).count();
+ long completed = steps.productionJobs().stream().filter(type -> controller.applications().deploymentTrigger().isComplete(Change.of(lastPlatform), change, instance, type)).count();
if (Optional.of(lastPlatform).equals(change.platform()))
lastPlatformObject.setString("deploying", completed + " of " + steps.productionJobs().size() + " complete");
else if (completed == steps.productionJobs().size())
lastPlatformObject.setString("completed", completed + " of " + steps.productionJobs().size() + " complete");
- else if ( ! instance.deploymentSpec().canUpgradeAt(controller.clock().instant())) {
- lastPlatformObject.setString("blocked", instance.deploymentSpec().changeBlocker().stream()
- .filter(blocker -> blocker.blocksVersions())
- .filter(blocker -> blocker.window().includes(controller.clock().instant()))
- .findAny().map(blocker -> blocker.window().toString()).get());
+ else if ( ! application.deploymentSpec().canUpgradeAt(controller.clock().instant())) {
+ lastPlatformObject.setString("blocked", application.deploymentSpec().changeBlocker().stream()
+ .filter(blocker -> blocker.blocksVersions())
+ .filter(blocker -> blocker.window().includes(controller.clock().instant()))
+ .findAny().map(blocker -> blocker.window().toString()).get());
}
else
lastPlatformObject.setString("pending",
- instance.change().isEmpty()
+ application.change().isEmpty()
? "Waiting for upgrade slot"
: "Waiting for " + instance.change() + " to complete");
}
- private static void lastApplicationToSlime(Cursor lastApplicationObject, Instance instance, Change change, DeploymentSteps steps, Controller controller) {
+ private static void lastApplicationToSlime(Cursor lastApplicationObject, Application application, Instance instance, Change change, DeploymentSteps steps, Controller controller) {
long completed;
ApplicationVersion lastApplication = instance.deploymentJobs().statusOf(component).flatMap(JobStatus::lastSuccess).get().application();
applicationVersionToSlime(lastApplicationObject.setObject("application"), lastApplication);
lastApplicationObject.setLong("at", instance.deploymentJobs().statusOf(component).flatMap(JobStatus::lastSuccess).get().at().toEpochMilli());
- completed = steps.productionJobs().stream().filter(type -> controller.applications().deploymentTrigger().isComplete(Change.of(lastApplication), instance, type)).count();
+ completed = steps.productionJobs().stream().filter(type -> controller.applications().deploymentTrigger().isComplete(Change.of(lastApplication), change, instance, type)).count();
if (Optional.of(lastApplication).equals(change.application()))
lastApplicationObject.setString("deploying", completed + " of " + steps.productionJobs().size() + " complete");
else if (completed == steps.productionJobs().size())
lastApplicationObject.setString("completed", completed + " of " + steps.productionJobs().size() + " complete");
- else if ( ! instance.deploymentSpec().canChangeRevisionAt(controller.clock().instant())) {
- lastApplicationObject.setString("blocked", instance.deploymentSpec().changeBlocker().stream()
- .filter(blocker -> blocker.blocksRevisions())
- .filter(blocker -> blocker.window().includes(controller.clock().instant()))
- .findAny().map(blocker -> blocker.window().toString()).get());
+ else if ( ! application.deploymentSpec().canChangeRevisionAt(controller.clock().instant())) {
+ lastApplicationObject.setString("blocked", application.deploymentSpec().changeBlocker().stream()
+ .filter(blocker -> blocker.blocksRevisions())
+ .filter(blocker -> blocker.window().includes(controller.clock().instant()))
+ .findAny().map(blocker -> blocker.window().toString()).get());
}
else
lastApplicationObject.setString("pending", "Waiting for current deployment to complete");
@@ -220,7 +224,7 @@ class JobControllerApiHandlerHelper {
deploymentObject.setString("status", pendingProduction.containsKey(type) ? "pending" : "completed");
}
- private static void jobTypeToSlime(Cursor jobObject, Controller controller, Instance instance, JobType type, DeploymentSteps steps,
+ private static void jobTypeToSlime(Cursor jobObject, Controller controller, Application application, Instance instance, JobType type, DeploymentSteps steps,
Map<JobType, Versions> pendingProduction, Map<JobType, Run> running, URI baseUriForJob) {
instance.deploymentJobs().statusOf(type).ifPresent(status -> status.pausedUntil().ifPresent(until ->
jobObject.setLong("pausedUntil", until)));
@@ -244,7 +248,7 @@ class JobControllerApiHandlerHelper {
Cursor runObject = runArray.addObject();
runObject.setString("status", "pending");
versionsToSlime(runObject, versions);
- if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, versions, instance))
+ if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, versions, instance, application.deploymentSpec()))
runObject.setObject("tasks").setString("cooldown", "failed");
else
runObject.setObject("tasks").setString("capacity", "running");
@@ -262,7 +266,7 @@ class JobControllerApiHandlerHelper {
Cursor pendingObject = runObject.setObject("tasks");
if (instance.deploymentJobs().statusOf(type).map(status -> status.pausedUntil().isPresent()).orElse(false))
pendingObject.setString("paused", "pending");
- else if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, versions, instance))
+ else if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, versions, instance, application.deploymentSpec()))
pendingObject.setString("cooldown", "failed");
else {
int pending = 0;
@@ -281,8 +285,8 @@ class JobControllerApiHandlerHelper {
break;
for (JobType stepType : steps.toJobs(step)) {
if (pendingProduction.containsKey(stepType)) {
- Versions jobVersions = Versions.from(instance.change(),
- instance,
+ Versions jobVersions = Versions.from(application.change(),
+ application,
Optional.ofNullable(instance.deployments().get(stepType.zone(controller.system()))),
controller.systemVersion());
pendingObject.setString(shortNameOf(stepType, controller.system()), statusOf(controller, instance.id(), stepType, jobVersions));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
index b2bc6d72044..86310ca2f6b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
@@ -18,6 +18,7 @@ import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.restapi.Uri;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.restapi.application.EmptyResponse;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.yolean.Exceptions;
@@ -97,7 +98,7 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
Cursor failingArray = versionObject.setArray("failingApplications");
for (ApplicationId id : version.statistics().failing()) {
- controller.applications().get(id).ifPresent(application -> {
+ controller.applications().getInstance(id).ifPresent(application -> {
firstFailingOn(version.versionNumber(), application).ifPresent(firstFailing -> {
Cursor applicationObject = failingArray.addObject();
toSlime(applicationObject, application, request);
@@ -108,7 +109,7 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
Cursor productionArray = versionObject.setArray("productionApplications");
for (ApplicationId id : version.statistics().production()) {
- controller.applications().get(id).ifPresent(application -> {
+ controller.applications().getInstance(id).ifPresent(application -> {
int successes = productionSuccessesFor(version.versionNumber(), application);
if (successes == 0) return; // Just upgraded to a newer version.
Cursor applicationObject = productionArray.addObject();
@@ -120,7 +121,7 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
Cursor runningArray = versionObject.setArray("deployingApplications");
for (ApplicationId id : version.statistics().deploying()) {
- controller.applications().get(id).ifPresent(application -> {
+ controller.applications().getInstance(id).ifPresent(application -> {
lastDeployingTo(version.versionNumber(), application).ifPresent(lastDeploying -> {
Cursor applicationObject = runningArray.addObject();
toSlime(applicationObject, application, request);
@@ -140,7 +141,8 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
instance.id().tenant().value() +
"/application/" +
instance.id().application().value()).toString());
- object.setString("upgradePolicy", toString(instance.deploymentSpec().upgradePolicy()));
+ object.setString("upgradePolicy", toString(controller.applications().requireApplication(TenantAndApplicationId.from(instance.id()))
+ .deploymentSpec().upgradePolicy()));
}
private static String toString(DeploymentSpec.UpgradePolicy upgradePolicy) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
index 9d7fa68807e..e86a5d16452 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
@@ -10,10 +10,12 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase;
import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.yolean.Exceptions;
import java.security.Principal;
@@ -45,8 +47,8 @@ public class SignatureFilter extends JsonSecurityRequestFilterBase {
&& request.getHeader("X-Authorization") != null)
try {
ApplicationId id = ApplicationId.fromSerializedForm(request.getHeader("X-Key-Id"));
- boolean verified = controller.applications().get(id)
- .flatMap(Instance::pemDeployKey)
+ boolean verified = controller.applications().getApplication(TenantAndApplicationId.from(id))
+ .flatMap(Application::pemDeployKey)
.map(key -> new RequestVerifier(key, controller.clock()))
.map(verifier -> verifier.verify(Method.valueOf(request.getMethod()),
request.getUri(),
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
index 988df9c6a66..a16ca5cb201 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
@@ -1,10 +1,14 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.rotation;
+import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.Endpoint;
+import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
@@ -73,23 +77,24 @@ public class RotationRepository {
* If a rotation is already assigned to the application, that rotation will be returned.
* If no rotation is assigned, return an available rotation. The caller is responsible for assigning the rotation.
*
- * @param instance The application requesting a rotation
+ * @param deploymentSpec The deployment spec for the application
+ * @param instance The instance requesting a rotation
* @param lock Lock which must be acquired by the caller
*/
- public Rotation getOrAssignRotation(Instance instance, RotationLock lock) {
- if (! instance.rotations().isEmpty()) {
+ public Rotation getOrAssignRotation(DeploymentSpec deploymentSpec, Instance instance, RotationLock lock) {
+ if ( ! instance.rotations().isEmpty()) {
return allRotations.get(instance.rotations().get(0).rotationId());
}
- if (instance.deploymentSpec().globalServiceId().isEmpty()) {
+ if (deploymentSpec.globalServiceId().isEmpty()) {
throw new IllegalArgumentException("global-service-id is not set in deployment spec");
}
- long productionZones = instance.deploymentSpec().zones().stream()
- .filter(zone -> zone.deploysTo(Environment.prod))
- .count();
+ long productionZones = deploymentSpec.zones().stream()
+ .filter(zone -> zone.deploysTo(Environment.prod))
+ .count();
if (productionZones < 2) {
throw new IllegalArgumentException("global-service-id is set but less than 2 prod zones are defined");
}
- return findAvailableRotation(instance, lock);
+ return findAvailableRotation(instance.id(), lock);
}
/**
@@ -99,27 +104,28 @@ public class RotationRepository {
* If rotations are not assigned, a new assignment will be created taking new rotations from the repository.
* This method supports both global-service-id as well as the new endpoints tag.
*
- * @param instance The application requesting rotations.
+ * @param deploymentSpec The deployment spec of the application
+ * @param instance The application requesting rotations
* @param lock Lock which by acquired by the caller
- * @return List of rotation assignments - either new or existing.
+ * @return List of rotation assignments - either new or existing
*/
- public List<AssignedRotation> getOrAssignRotations(Instance instance, RotationLock lock) {
- if (instance.deploymentSpec().globalServiceId().isPresent() && ! instance.deploymentSpec().endpoints().isEmpty()) {
+ public List<AssignedRotation> getOrAssignRotations(DeploymentSpec deploymentSpec, Instance instance, RotationLock lock) {
+ if (deploymentSpec.globalServiceId().isPresent() && ! deploymentSpec.endpoints().isEmpty()) {
throw new IllegalArgumentException("Cannot provision rotations with both global-service-id and 'endpoints'");
}
// Support the older case of setting global-service-id
- if (instance.deploymentSpec().globalServiceId().isPresent()) {
- final var regions = instance.deploymentSpec().zones().stream()
- .filter(zone -> zone.environment().isProduction())
- .flatMap(zone -> zone.region().stream())
- .collect(Collectors.toSet());
+ if (deploymentSpec.globalServiceId().isPresent()) {
+ final var regions = deploymentSpec.zones().stream()
+ .filter(zone -> zone.environment().isProduction())
+ .flatMap(zone -> zone.region().stream())
+ .collect(Collectors.toSet());
- final var rotation = getOrAssignRotation(instance, lock);
+ final var rotation = getOrAssignRotation(deploymentSpec, instance, lock);
return List.of(
new AssignedRotation(
- new ClusterSpec.Id(instance.deploymentSpec().globalServiceId().get()),
+ new ClusterSpec.Id(deploymentSpec.globalServiceId().get()),
EndpointId.default_(),
rotation.id(),
regions
@@ -127,20 +133,20 @@ public class RotationRepository {
);
}
- final Map<EndpointId, AssignedRotation> existingAssignments = existingEndpointAssignments(instance);
- final Map<EndpointId, AssignedRotation> updatedAssignments = assignRotationsToEndpoints(instance, existingAssignments, lock);
+ final Map<EndpointId, AssignedRotation> existingAssignments = existingEndpointAssignments(deploymentSpec, instance);
+ final Map<EndpointId, AssignedRotation> updatedAssignments = assignRotationsToEndpoints(deploymentSpec, existingAssignments, lock);
existingAssignments.putAll(updatedAssignments);
return List.copyOf(existingAssignments.values());
}
- private Map<EndpointId, AssignedRotation> assignRotationsToEndpoints(Instance instance, Map<EndpointId, AssignedRotation> existingAssignments, RotationLock lock) {
+ private Map<EndpointId, AssignedRotation> assignRotationsToEndpoints(DeploymentSpec deploymentSpec, Map<EndpointId, AssignedRotation> existingAssignments, RotationLock lock) {
final var availableRotations = new ArrayList<>(availableRotations(lock).values());
- final var neededRotations = instance.deploymentSpec().endpoints().stream()
- .filter(Predicate.not(endpoint -> existingAssignments.containsKey(EndpointId.of(endpoint.endpointId()))))
- .collect(Collectors.toSet());
+ final var neededRotations = deploymentSpec.endpoints().stream()
+ .filter(Predicate.not(endpoint -> existingAssignments.containsKey(EndpointId.of(endpoint.endpointId()))))
+ .collect(Collectors.toSet());
if (neededRotations.size() > availableRotations.size()) {
throw new IllegalStateException("Hosted Vespa ran out of rotations, unable to assign rotation: need " + neededRotations.size() + ", have " + availableRotations.size());
@@ -165,17 +171,17 @@ public class RotationRepository {
);
}
- private Map<EndpointId, AssignedRotation> existingEndpointAssignments(Instance instance) {
+ private Map<EndpointId, AssignedRotation> existingEndpointAssignments(DeploymentSpec deploymentSpec, Instance instance) {
//
// Get the regions that has been configured for an endpoint. Empty set if the endpoint
// is no longer mentioned in the configuration file.
//
final Function<EndpointId, Set<RegionName>> configuredRegionsForEndpoint = endpointId -> {
- return instance.deploymentSpec().endpoints().stream()
- .filter(endpoint -> endpointId.id().equals(endpoint.endpointId()))
- .map(Endpoint::regions)
- .findFirst()
- .orElse(Set.of());
+ return deploymentSpec.endpoints().stream()
+ .filter(endpoint -> endpointId.id().equals(endpoint.endpointId()))
+ .map(Endpoint::regions)
+ .findFirst()
+ .orElse(Set.of());
};
//
@@ -210,8 +216,8 @@ public class RotationRepository {
*/
public Map<RotationId, Rotation> availableRotations(@SuppressWarnings("unused") RotationLock lock) {
List<RotationId> assignedRotations = applications.asList().stream()
- .filter(application -> ! application.rotations().isEmpty())
- .flatMap(application -> application.rotations().stream())
+ .flatMap(application -> application.instances().values().stream())
+ .flatMap(instance -> instance.rotations().stream())
.map(AssignedRotation::rotationId)
.collect(Collectors.toList());
Map<RotationId, Rotation> unassignedRotations = new LinkedHashMap<>(this.allRotations);
@@ -219,15 +225,15 @@ public class RotationRepository {
return Collections.unmodifiableMap(unassignedRotations);
}
- private Rotation findAvailableRotation(Instance instance, RotationLock lock) {
+ private Rotation findAvailableRotation(ApplicationId id, RotationLock lock) {
Map<RotationId, Rotation> availableRotations = availableRotations(lock);
if (availableRotations.isEmpty()) {
- throw new IllegalStateException("Unable to assign global rotation to " + instance.id()
+ throw new IllegalStateException("Unable to assign global rotation to " + id
+ " - no rotations available");
}
// Return first available rotation
RotationId rotation = availableRotations.keySet().iterator().next();
- log.info(String.format("Offering %s to application %s", rotation, instance.id()));
+ log.info(String.format("Offering %s to application %s", rotation, id));
return allRotations.get(rotation);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java
index 6ed32feae28..77ccce873fe 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java
@@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.util.List;
@@ -33,10 +33,10 @@ public interface AccessControl {
* @param tenantSpec specification for the tenant to update
* @param credentials the credentials for the entity requesting the update
* @param existing list of existing tenants, to check for conflicts
- * @param instances list of applications this tenant already owns
+ * @param applications list of applications this tenant already owns
* @return the updated tenant, for keeping
*/
- Tenant updateTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing, List<Instance> instances);
+ Tenant updateTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing, List<Application> applications);
/**
* Deletes access control for the given tenant.
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
index b53388d7aa6..33529c342a3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.security;
import com.google.inject.Inject;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.integration.organization.BillingInfo;
import com.yahoo.vespa.hosted.controller.api.integration.user.Roles;
@@ -44,7 +45,7 @@ public class CloudAccessControl implements AccessControl {
}
@Override
- public Tenant updateTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing, List<Instance> instances) {
+ public Tenant updateTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing, List<Application> applications) {
throw new UnsupportedOperationException("Update is not supported here, as it would entail changing the tenant name.");
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
index c6c66eab1b4..9dc6b86e4be 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
@@ -8,10 +8,11 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
-import com.yahoo.vespa.hosted.controller.application.InstanceList;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.JobList;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
@@ -189,51 +190,50 @@ public class VersionStatus {
}
private static Collection<DeploymentStatistics> computeDeploymentStatistics(Set<Version> infrastructureVersions,
- List<Instance> instances) {
+ List<Application> instances) {
Map<Version, DeploymentStatistics> versionMap = new HashMap<>();
for (Version infrastructureVersion : infrastructureVersions) {
versionMap.put(infrastructureVersion, DeploymentStatistics.empty(infrastructureVersion));
}
- InstanceList instanceList = InstanceList.from(instances)
- .hasProductionDeployment();
- for (Instance instance : instanceList.asList()) {
- // Note that each version deployed on this application in production exists
- // (ignore non-production versions)
- for (Deployment deployment : instance.productionDeployments().values()) {
- versionMap.computeIfAbsent(deployment.version(), DeploymentStatistics::empty);
- }
+ for (Application application : ApplicationList.from(instances).withProductionDeployment().asList())
+ for (Instance instance : application.instances().values()) {
+ // Note that each version deployed on this application in production exists
+ // (ignore non-production versions)
+ for (Deployment deployment : instance.productionDeployments().values()) {
+ versionMap.computeIfAbsent(deployment.version(), DeploymentStatistics::empty);
+ }
- // List versions which have failing jobs, versions which are in production, and versions for which there are running deployment jobs
-
- // Failing versions
- JobList.from(instance)
- .failing()
- .not().failingApplicationChange()
- .not().failingBecause(outOfCapacity)
- .mapToList(job -> job.lastCompleted().get().platform())
- .forEach(version -> versionMap
- .put(version, versionMap.getOrDefault(version, DeploymentStatistics.empty(version))
- .withFailing(instance.id())));
-
- // Succeeding versions
- JobList.from(instance)
- .lastSuccess().present()
- .production()
- .mapToList(job -> job.lastSuccess().get().platform())
- .forEach(version -> versionMap
- .put(version, versionMap.getOrDefault(version, DeploymentStatistics.empty(version))
- .withProduction(instance.id())));
-
- // Deploying versions
- JobList.from(instance)
- .upgrading()
- .mapToList(job -> job.lastTriggered().get().platform())
- .forEach(version -> versionMap
- .put(version, versionMap.getOrDefault(version, DeploymentStatistics.empty(version))
- .withDeploying(instance.id())));
- }
+ // List versions which have failing jobs, versions which are in production, and versions for which there are running deployment jobs
+
+ // Failing versions
+ JobList.from(instance)
+ .failing()
+ .not().failingApplicationChange()
+ .not().failingBecause(outOfCapacity)
+ .mapToList(job -> job.lastCompleted().get().platform())
+ .forEach(version -> versionMap
+ .put(version, versionMap.getOrDefault(version, DeploymentStatistics.empty(version))
+ .withFailing(instance.id())));
+
+ // Succeeding versions
+ JobList.from(instance)
+ .lastSuccess().present()
+ .production()
+ .mapToList(job -> job.lastSuccess().get().platform())
+ .forEach(version -> versionMap
+ .put(version, versionMap.getOrDefault(version, DeploymentStatistics.empty(version))
+ .withProduction(instance.id())));
+
+ // Deploying versions
+ JobList.from(instance)
+ .upgrading()
+ .mapToList(job -> job.lastTriggered().get().platform())
+ .forEach(version -> versionMap
+ .put(version, versionMap.getOrDefault(version, DeploymentStatistics.empty(version))
+ .withDeploying(instance.id())));
+ }
return versionMap.values();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
index aaf216f805b..e6a16a67af9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
@@ -4,7 +4,9 @@ package com.yahoo.vespa.hosted.controller.versions;
import com.google.common.collect.ImmutableSet;
import com.yahoo.component.Version;
import com.yahoo.config.provision.HostName;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.InstanceList;
import java.time.Instant;
@@ -49,12 +51,12 @@ public class VespaVersion implements Comparable<VespaVersion> {
public static Confidence confidenceFrom(DeploymentStatistics statistics, Controller controller) {
// 'production on this': All deployment jobs upgrading to this version have completed without failure
- InstanceList productionOnThis = InstanceList.from(statistics.production(), controller.applications())
- .notUpgradingTo(statistics.version())
- .notFailing();
- InstanceList failingOnThis = InstanceList.from(statistics.failing(), controller.applications());
- InstanceList all = InstanceList.from(controller.applications().asList())
- .hasDeployment();
+ ApplicationList productionOnThis = ApplicationList.from(statistics.production(), controller.applications())
+ .notUpgradingTo(statistics.version())
+ .notFailing();
+ ApplicationList failingOnThis = ApplicationList.from(statistics.failing(), controller.applications());
+ ApplicationList all = ApplicationList.from(controller.applications().asList())
+ .withProductionDeployment();
// 'broken' if any Canary fails
if ( ! failingOnThis.with(UpgradePolicy.canary).isEmpty())
@@ -162,10 +164,10 @@ public class VespaVersion implements Comparable<VespaVersion> {
}
private static boolean nonCanaryApplicationsBroken(Version version,
- InstanceList failingOnThis,
- InstanceList productionOnThis) {
- InstanceList failingNonCanaries = failingOnThis.without(UpgradePolicy.canary).startedFailingOn(version);
- InstanceList productionNonCanaries = productionOnThis.without(UpgradePolicy.canary);
+ ApplicationList failingOnThis,
+ ApplicationList productionOnThis) {
+ ApplicationList failingNonCanaries = failingOnThis.without(UpgradePolicy.canary).startedFailingOn(version);
+ ApplicationList productionNonCanaries = productionOnThis.without(UpgradePolicy.canary);
if (productionNonCanaries.size() + failingNonCanaries.size() == 0) return false;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index c5baa040ffc..5d82225c9d5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -7,7 +7,8 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.AthenzDomain;
+import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
@@ -15,6 +16,7 @@ import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.slime.JsonFormat;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
@@ -22,33 +24,44 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
+import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.BuildJob;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
-import com.yahoo.vespa.hosted.controller.persistence.ApplicationSerializer;
+import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.persistence.InstanceSerializer;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import com.yahoo.vespa.hosted.controller.persistence.OldMockCuratorDb;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
+import com.yahoo.vespa.hosted.controller.rotation.RotationState;
+import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
import org.junit.Test;
+import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -59,6 +72,7 @@ import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobTy
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.productionUsWest1;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.stagingTest;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.temporal.ChronoUnit.MILLIS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -88,41 +102,41 @@ public class ControllerTest {
// staging job - succeeding
Version version1 = tester.defaultPlatformVersion();
Application app1 = tester.createApplication("app1", "tenant1", 1, 11L);
- Instance instance = tester.instance(app1.id());
+ Instance instance = tester.defaultInstance(app1.id());
tester.jobCompletion(component).application(app1).uploadArtifact(applicationPackage).submit();
assertEquals("Application version is known from completion of initial job",
ApplicationVersion.from(BuildJob.defaultSourceRevision, BuildJob.defaultBuildNumber),
- tester.controller().applications().require(app1.id()).change().application().get());
+ tester.application(app1.id()).change().application().get());
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, systemTest);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, stagingTest);
- assertEquals(4, applications.require(app1.id()).deploymentJobs().jobStatus().size());
+ assertEquals(4, tester.defaultInstance(app1.id()).deploymentJobs().jobStatus().size());
- ApplicationVersion applicationVersion = tester.controller().applications().require(app1.id()).change().application().get();
+ ApplicationVersion applicationVersion = tester.application(app1.id()).change().application().get();
assertFalse("Application version has been set during deployment", applicationVersion.isUnknown());
assertStatus(JobStatus.initial(stagingTest)
.withTriggering(version1, applicationVersion, Optional.empty(),"", tester.clock().instant().truncatedTo(MILLIS))
- .withCompletion(42, Optional.empty(), tester.clock().instant().truncatedTo(MILLIS)), app1.id(), tester.controller());
+ .withCompletion(42, Optional.empty(), tester.clock().instant().truncatedTo(MILLIS)), app1.id().defaultInstance(), tester.controller());
// Causes first deployment job to be triggered
assertStatus(JobStatus.initial(productionUsWest1)
- .withTriggering(version1, applicationVersion, Optional.empty(), "", tester.clock().instant().truncatedTo(MILLIS)), app1.id(), tester.controller());
+ .withTriggering(version1, applicationVersion, Optional.empty(), "", tester.clock().instant().truncatedTo(MILLIS)), app1.id().defaultInstance(), tester.controller());
tester.clock().advance(Duration.ofSeconds(1));
// production job (failing) after deployment
tester.deploy(productionUsWest1, instance.id(), applicationPackage);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), false, productionUsWest1);
- assertEquals(4, applications.require(app1.id()).deploymentJobs().jobStatus().size());
+ assertEquals(4, tester.defaultInstance(app1.id()).deploymentJobs().jobStatus().size());
JobStatus expectedJobStatus = JobStatus.initial(productionUsWest1)
.withTriggering(version1, applicationVersion, Optional.empty(), "", tester.clock().instant().truncatedTo(MILLIS)) // Triggered first without application version info
.withCompletion(42, Optional.of(JobError.unknown), tester.clock().instant().truncatedTo(MILLIS))
.withTriggering(version1,
applicationVersion,
- Optional.of(tester.instance(app1.id()).deployments().get(productionUsWest1.zone(main))),
+ Optional.of(tester.defaultInstance(app1.id()).deployments().get(productionUsWest1.zone(main))),
"",
tester.clock().instant().truncatedTo(MILLIS)); // Re-triggering (due to failure) has application version info
- assertStatus(expectedJobStatus, app1.id(), tester.controller());
+ assertStatus(expectedJobStatus, app1.id().defaultInstance(), tester.controller());
// Simulate restart
tester.restartController();
@@ -130,40 +144,38 @@ public class ControllerTest {
applications = tester.controller().applications();
assertNotNull(tester.controller().tenants().get(TenantName.from("tenant1")));
- assertNotNull(applications.get(ApplicationId.from(TenantName.from("tenant1"),
- ApplicationName.from("application1"),
- InstanceName.from("default"))));
- assertEquals(4, applications.require(app1.id()).deploymentJobs().jobStatus().size());
+ assertNotNull(tester.defaultInstance(app1.id()));
+ assertEquals(4, tester.defaultInstance(app1.id()).deploymentJobs().jobStatus().size());
tester.clock().advance(Duration.ofHours(1));
// system and staging test job - succeeding
tester.jobCompletion(component).application(app1).nextBuildNumber().uploadArtifact(applicationPackage).submit();
- applicationVersion = tester.instance("app1").change().application().get();
+ applicationVersion = tester.defaultInstance("app1").change().application().get();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, systemTest);
assertStatus(JobStatus.initial(systemTest)
- .withTriggering(version1, applicationVersion, Optional.of(tester.instance(app1.id()).deployments().get(productionUsWest1.zone(main))), "", tester.clock().instant().truncatedTo(MILLIS))
+ .withTriggering(version1, applicationVersion, Optional.of(tester.defaultInstance(app1.id()).deployments().get(productionUsWest1.zone(main))), "", tester.clock().instant().truncatedTo(MILLIS))
.withCompletion(42, Optional.empty(), tester.clock().instant().truncatedTo(MILLIS)),
- app1.id(), tester.controller());
+ app1.id().defaultInstance(), tester.controller());
tester.clock().advance(Duration.ofHours(1)); // Stop retrying
tester.jobCompletion(productionUsWest1).application(app1).unsuccessful().submit();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, stagingTest);
// production job succeeding now
expectedJobStatus = expectedJobStatus
- .withTriggering(version1, applicationVersion, Optional.of(tester.instance(app1.id()).deployments().get(productionUsWest1.zone(main))), "", tester.clock().instant().truncatedTo(MILLIS))
+ .withTriggering(version1, applicationVersion, Optional.of(tester.defaultInstance(app1.id()).deployments().get(productionUsWest1.zone(main))), "", tester.clock().instant().truncatedTo(MILLIS))
.withCompletion(42, Optional.empty(), tester.clock().instant().truncatedTo(MILLIS));
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsWest1);
- assertStatus(expectedJobStatus, app1.id(), tester.controller());
+ assertStatus(expectedJobStatus, app1.id().defaultInstance(), tester.controller());
// causes triggering of next production job
assertStatus(JobStatus.initial(productionUsEast3)
.withTriggering(version1, applicationVersion, Optional.empty(), "", tester.clock().instant().truncatedTo(MILLIS)),
- app1.id(), tester.controller());
+ app1.id().defaultInstance(), tester.controller());
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsEast3);
- assertEquals(5, applications.get(app1.id()).get().deploymentJobs().jobStatus().size());
+ assertEquals(5, tester.defaultInstance(app1.id()).deploymentJobs().jobStatus().size());
// Production zone for which there is no JobType is not allowed.
applicationPackage = new ApplicationPackageBuilder()
@@ -171,7 +183,7 @@ public class ControllerTest {
.region("deep-space-9")
.build();
try {
- tester.controller().jobController().submit(app1.id(), BuildJob.defaultSourceRevision, "a@b",
+ tester.controller().jobController().submit(app1.id().defaultInstance(), BuildJob.defaultSourceRevision, "a@b",
2, applicationPackage, new byte[0]);
fail("Expected exception due to illegal deployment spec.");
}
@@ -196,8 +208,8 @@ public class ControllerTest {
e.getMessage());
}
assertNotNull("Zone was not removed",
- applications.require(app1.id()).deployments().get(productionUsWest1.zone(main)));
- JobStatus jobStatus = applications.require(app1.id()).deploymentJobs().jobStatus().get(productionUsWest1);
+ tester.defaultInstance(app1.id()).deployments().get(productionUsWest1.zone(main)));
+ JobStatus jobStatus = tester.defaultInstance(app1.id()).deploymentJobs().jobStatus().get(productionUsWest1);
assertNotNull("Deployment job was not removed", jobStatus);
assertEquals(42, jobStatus.lastCompleted().get().id());
assertEquals("New change available", jobStatus.lastCompleted().get().reason());
@@ -212,8 +224,8 @@ public class ControllerTest {
tester.jobCompletion(component).application(app1).nextBuildNumber(2).uploadArtifact(applicationPackage).submit();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, systemTest);
assertNull("Zone was removed",
- applications.require(app1.id()).deployments().get(productionUsWest1.zone(main)));
- assertNull("Deployment job was removed", applications.require(app1.id()).deploymentJobs().jobStatus().get(productionUsWest1));
+ tester.defaultInstance(app1.id()).deployments().get(productionUsWest1.zone(main)));
+ assertNull("Deployment job was removed", tester.defaultInstance(app1.id()).deploymentJobs().jobStatus().get(productionUsWest1));
}
@Test
@@ -229,11 +241,11 @@ public class ControllerTest {
ApplicationVersion applicationVersion = ApplicationVersion.from(source, 101);
runDeployment(tester, app.id(), applicationVersion, applicationPackage, source,101);
assertEquals("Artifact is downloaded twice in staging and once for other zones", 5,
- tester.controllerTester().serviceRegistry().artifactRepositoryMock().hits(app.id(), applicationVersion.id()));
+ tester.controllerTester().serviceRegistry().artifactRepositoryMock().hits(app.id().defaultInstance(), applicationVersion.id()));
// Application is upgraded. This makes deployment orchestration pick the last successful application version in
// zones which do not have permanent deployments, e.g. test and staging
- runUpgrade(tester, app.id(), applicationVersion);
+ runUpgrade(tester, app.id().defaultInstance(), applicationVersion);
}
@Test
@@ -296,13 +308,13 @@ public class ControllerTest {
.build();
tester.deployCompletely(application, applicationPackage);
- Collection<Deployment> deployments = tester.instance(application.id()).deployments().values();
+ Collection<Deployment> deployments = tester.defaultInstance(application.id()).deployments().values();
assertFalse(deployments.isEmpty());
for (Deployment deployment : deployments) {
assertEquals("Rotation names are passed to config server in " + deployment.zone(),
Set.of("rotation-id-01",
"app1--tenant1.global.vespa.oath.cloud"),
- tester.configServer().rotationNames().get(new DeploymentId(application.id(), deployment.zone())));
+ tester.configServer().rotationNames().get(new DeploymentId(application.id().defaultInstance(), deployment.zone())));
}
tester.flushDnsRequests();
@@ -326,7 +338,7 @@ public class ControllerTest {
.build();
tester.deployCompletely(application, applicationPackage);
- Collection<Deployment> deployments = tester.instance(application.id()).deployments().values();
+ Collection<Deployment> deployments = tester.defaultInstance(application.id()).deployments().values();
assertFalse(deployments.isEmpty());
for (Deployment deployment : deployments) {
assertEquals("Rotation names are passed to config server in " + deployment.zone(),
@@ -334,7 +346,7 @@ public class ControllerTest {
"app1--tenant1.global.vespa.oath.cloud",
"app1.tenant1.global.vespa.yahooapis.com",
"app1--tenant1.global.vespa.yahooapis.com"),
- tester.configServer().rotationNames().get(new DeploymentId(application.id(), deployment.zone())));
+ tester.configServer().rotationNames().get(new DeploymentId(application.id().defaultInstance(), deployment.zone())));
}
tester.flushDnsRequests();
assertEquals(3, tester.controllerTester().nameService().records().size());
@@ -370,7 +382,7 @@ public class ControllerTest {
.build();
tester.deployCompletely(application, applicationPackage);
- Collection<Deployment> deployments = tester.instance(application.id()).deployments().values();
+ Collection<Deployment> deployments = tester.defaultInstance(application.id()).deployments().values();
assertFalse(deployments.isEmpty());
var notWest = Set.of(
@@ -383,7 +395,7 @@ public class ControllerTest {
for (Deployment deployment : deployments) {
assertEquals("Rotation names are passed to config server in " + deployment.zone(),
ZoneId.from("prod.us-west-1").equals(deployment.zone()) ? west : notWest,
- tester.configServer().rotationNames().get(new DeploymentId(application.id(), deployment.zone())));
+ tester.configServer().rotationNames().get(new DeploymentId(application.id().defaultInstance(), deployment.zone())));
}
tester.flushDnsRequests();
@@ -425,12 +437,12 @@ public class ControllerTest {
assertEquals(
Set.of("rotation-id-01", "app1--tenant1.global.vespa.oath.cloud"),
- tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-west-1")))
+ tester.configServer().rotationNames().get(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-west-1")))
);
assertEquals(
Set.of("rotation-id-01", "app1--tenant1.global.vespa.oath.cloud"),
- tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-central-1")))
+ tester.configServer().rotationNames().get(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-central-1")))
);
@@ -445,15 +457,15 @@ public class ControllerTest {
assertEquals(
Set.of("rotation-id-01", "app1--tenant1.global.vespa.oath.cloud"),
- tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-west-1")))
+ tester.configServer().rotationNames().get(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-west-1")))
);
assertEquals(
Set.of(),
- tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-central-1")))
+ tester.configServer().rotationNames().get(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-central-1")))
);
- assertEquals(Set.of(RegionName.from("us-west-1")), tester.instance(application.id()).rotations().get(0).regions());
+ assertEquals(Set.of(RegionName.from("us-west-1")), tester.defaultInstance(application.id()).rotations().get(0).regions());
}
@Test
@@ -480,12 +492,12 @@ public class ControllerTest {
assertEquals(
List.of(AssignedRotation.fromStrings("qrs", "default", "rotation-id-01", Set.of())),
- tester.instance(application.id()).rotations()
+ tester.defaultInstance(application.id()).rotations()
);
assertEquals(
Set.of(),
- tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-west-1")))
+ tester.configServer().rotationNames().get(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-west-1")))
);
}
@@ -515,9 +527,9 @@ public class ControllerTest {
.allow(ValidationId.deploymentRemoval)
.build();
tester.jobCompletion(component).application(app1).nextBuildNumber().uploadArtifact(applicationPackage).submit();
- tester.deployAndNotify(tester.instance(app1.id()).id(), Optional.of(applicationPackage), true, systemTest);
- tester.applications().deactivate(app1.id(), ZoneId.from(Environment.test, RegionName.from("us-east-1")));
- tester.applications().deactivate(app1.id(), ZoneId.from(Environment.staging, RegionName.from("us-east-3")));
+ tester.deployAndNotify(tester.defaultInstance(app1.id()).id(), Optional.of(applicationPackage), true, systemTest);
+ tester.applications().deactivate(app1.id().defaultInstance(), ZoneId.from(Environment.test, RegionName.from("us-east-1")));
+ tester.applications().deactivate(app1.id().defaultInstance(), ZoneId.from(Environment.staging, RegionName.from("us-east-3")));
tester.applications().deleteApplication(app1.id().tenant(), app1.id().application(), tester.controllerTester().credentialsFor(app1.id()));
try (RotationLock lock = tester.applications().rotationRepository().lock()) {
assertTrue("Rotation is unassigned",
@@ -566,7 +578,7 @@ public class ControllerTest {
.region("us-central-1")
.build();
tester.deployCompletely(app1, applicationPackage);
- assertEquals("rotation-id-02", tester.instance(app1.id()).rotations().get(0).rotationId().asString());
+ assertEquals("rotation-id-02", tester.defaultInstance(app1.id()).rotations().get(0).rotationId().asString());
// DNS records are created for the newly assigned rotation
assertEquals(2, tester.controllerTester().nameService().records().size());
@@ -602,18 +614,18 @@ public class ControllerTest {
// Same options as used in our integration tests
DeployOptions options = new DeployOptions(true, Optional.empty(), false,
false);
- tester.controller().applications().deploy(app.id(), zone, Optional.of(applicationPackage), options);
+ tester.controller().applications().deploy(app.id().defaultInstance(), zone, Optional.of(applicationPackage), options);
assertTrue("Application deployed and activated",
- tester.controllerTester().configServer().application(app.id(), zone).get().activated());
+ tester.controllerTester().configServer().application(app.id().defaultInstance(), zone).get().activated());
assertTrue("No job status added",
- tester.applications().require(app.id()).deploymentJobs().jobStatus().isEmpty());
+ tester.applications().requireInstance(app.id().defaultInstance()).deploymentJobs().jobStatus().isEmpty());
Version seven = Version.fromString("7.2");
tester.upgradeSystem(seven);
- tester.controller().applications().deploy(app.id(), zone, Optional.of(applicationPackage), options);
- assertEquals(six, tester.instance(app.id()).deployments().get(zone).version());
+ tester.controller().applications().deploy(app.id().defaultInstance(), zone, Optional.of(applicationPackage), options);
+ assertEquals(six, tester.defaultInstance(app.id()).deployments().get(zone).version());
}
@Test
@@ -629,12 +641,12 @@ public class ControllerTest {
ZoneId zone = ZoneId.from("dev", "us-east-1");
// Deploy
- tester.controller().applications().deploy(app.id(), zone, Optional.of(applicationPackage), DeployOptions.none());
+ tester.controller().applications().deploy(app.id().defaultInstance(), zone, Optional.of(applicationPackage), DeployOptions.none());
assertTrue("Application deployed and activated",
- tester.controllerTester().configServer().application(app.id(), zone).get().activated());
+ tester.controllerTester().configServer().application(app.id().defaultInstance(), zone).get().activated());
assertTrue("No job status added",
- tester.applications().require(app.id()).deploymentJobs().jobStatus().isEmpty());
- assertEquals("DeploymentSpec is not persisted", DeploymentSpec.empty, tester.applications().require(app.id()).deploymentSpec());
+ tester.defaultInstance(app.id()).deploymentJobs().jobStatus().isEmpty());
+ assertEquals("DeploymentSpec is not persisted", DeploymentSpec.empty, tester.application(app.id()).deploymentSpec());
}
@Test
@@ -650,8 +662,8 @@ public class ControllerTest {
ApplicationVersion applicationVersion = ApplicationVersion.from(source, 101);
runDeployment(tester, app.id(), applicationVersion, applicationPackage, source,101);
- DeploymentId deployment1 = new DeploymentId(app.id(), ZoneId.from(Environment.prod, RegionName.from("us-west-1")));
- DeploymentId deployment2 = new DeploymentId(app.id(), ZoneId.from(Environment.prod, RegionName.from("us-east-3")));
+ DeploymentId deployment1 = new DeploymentId(app.id().defaultInstance(), ZoneId.from(Environment.prod, RegionName.from("us-west-1")));
+ DeploymentId deployment2 = new DeploymentId(app.id().defaultInstance(), ZoneId.from(Environment.prod, RegionName.from("us-east-3")));
assertFalse(tester.configServer().isSuspended(deployment1));
assertFalse(tester.configServer().isSuspended(deployment2));
tester.configServer().setSuspended(deployment1, true);
@@ -671,33 +683,33 @@ public class ControllerTest {
.build();
ZoneId zone = ZoneId.from("prod", "us-west-1");
- tester.controller().applications().deploy(app.id(), zone, Optional.of(applicationPackage), DeployOptions.none());
- tester.controller().applications().deactivate(app.id(), ZoneId.from(Environment.prod, RegionName.from("us-west-1")));
- tester.controller().applications().deactivate(app.id(), ZoneId.from(Environment.prod, RegionName.from("us-west-1")));
+ tester.controller().applications().deploy(app.id().defaultInstance(), zone, Optional.of(applicationPackage), DeployOptions.none());
+ tester.controller().applications().deactivate(app.id().defaultInstance(), ZoneId.from(Environment.prod, RegionName.from("us-west-1")));
+ tester.controller().applications().deactivate(app.id().defaultInstance(), ZoneId.from(Environment.prod, RegionName.from("us-west-1")));
}
@Test
public void testDeployApplicationPackageWithApplicationDir() {
- Application instance = tester.createApplication("app1", "tenant1", 1, 1L);
+ Application application = tester.createApplication("app1", "tenant1", 1, 1L);
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-west-1")
.build(true);
- tester.deployCompletely(instance, applicationPackage);
+ tester.deployCompletely(application, applicationPackage);
}
@Test
public void testDeployApplicationWithWarnings() {
- Application instance = tester.createApplication("app1", "tenant1", 1, 1L);
+ Application application = tester.createApplication("app1", "tenant1", 1, 1L);
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-west-1")
.build();
ZoneId zone = ZoneId.from("prod", "us-west-1");
int warnings = 3;
- tester.configServer().generateWarnings(new DeploymentId(instance.id(), zone), warnings);
- tester.deployCompletely(instance, applicationPackage);
- assertEquals(warnings, tester.applications().require(instance.id()).deployments().get(zone)
+ tester.configServer().generateWarnings(new DeploymentId(application.id().defaultInstance(), zone), warnings);
+ tester.deployCompletely(application, applicationPackage);
+ assertEquals(warnings, tester.applications().requireInstance(application.id().defaultInstance()).deployments().get(zone)
.metrics().warnings().get(DeploymentMetrics.Warning.all).intValue());
}
@@ -713,7 +725,7 @@ public class ControllerTest {
.build();
// Deploy app1 in production
tester.deployCompletely(app1, applicationPackage);
- Instance instance1 = tester.instance(app1.id());
+ Instance instance1 = tester.defaultInstance(app1.id());
var cert = certificate.apply(instance1);
assertTrue("Provisions certificate in " + Environment.prod, cert.isPresent());
assertEquals(List.of(
@@ -728,7 +740,7 @@ public class ControllerTest {
"*.app1.tenant1.us-central-1.vespa.oath.cloud",
"app1.tenant1.eu-west-1.vespa.oath.cloud",
"*.app1.tenant1.eu-west-1.vespa.oath.cloud"
- ), tester.controllerTester().serviceRegistry().applicationCertificateMock().dnsNamesOf(app1.id()));
+ ), tester.controllerTester().serviceRegistry().applicationCertificateMock().dnsNamesOf(app1.id().defaultInstance()));
// Next deployment reuses certificate
tester.deployCompletely(app1, applicationPackage, BuildJob.defaultBuildNumber + 1);
@@ -736,13 +748,13 @@ public class ControllerTest {
// Create app2
var app2 = tester.createApplication("app2", "tenant2", 3, 4L);
- Instance instance2 = tester.instance(app2.id());
+ Instance instance2 = tester.defaultInstance(app2.id());
ZoneId zone = ZoneId.from("dev", "us-east-1");
// Deploy app2 in dev
- tester.controller().applications().deploy(app2.id(), zone, Optional.of(applicationPackage), DeployOptions.none());
+ tester.controller().applications().deploy(app2.id().defaultInstance(), zone, Optional.of(applicationPackage), DeployOptions.none());
assertTrue("Application deployed and activated",
- tester.controllerTester().configServer().application(app2.id(), zone).get().activated());
+ tester.controllerTester().configServer().application(app2.id().defaultInstance(), zone).get().activated());
assertFalse("Does not provision certificate in " + Environment.dev, certificate.apply(instance2).isPresent());
}
@@ -782,54 +794,107 @@ public class ControllerTest {
@Test
- public void testInstanceDataMigration() {
+ public void testInstanceDataMigration() throws IOException {
+ /*
+ Set up initial state, using old DB:
+ Create two instances of an application; the default will be the base.
+
+ Read, modify and write the application using the new DB.
+ Verify results using both old and new DBs.
+ */
+
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder().allow(ValidationId.contentClusterRemoval)
+ .athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service"))
+ .endpoint("endpoint", "container", "us-east-1")
+ .region("us-east-1")
+ .build();
+ ApplicationId defaultId = ApplicationId.from("t1", "a1", "default");
+ Instance old1 = new Instance(defaultId,
+ Instant.ofEpochMilli(123),
+ applicationPackage.deploymentSpec(),
+ applicationPackage.validationOverrides(),
+ List.of(new Deployment(ZoneId.from("prod", "us-east-1"),
+ ApplicationVersion.from(new SourceRevision("repo", "branch", "commit"), 3),
+ Version.fromString("7.8.9"),
+ Instant.ofEpochMilli(321))),
+ new DeploymentJobs(OptionalLong.of(72),
+ List.of(new JobStatus(JobType.productionAwsUsEast1a,
+ Optional.empty(),
+ Optional.of(new JobStatus.JobRun(32,
+ Version.fromString("7.8.9"),
+ ApplicationVersion.unknown,
+ Optional.empty(),
+ Optional.empty(),
+ "make the job great again",
+ Instant.ofEpochMilli(200))),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ OptionalLong.empty())),
+ Optional.of(IssueId.from("issue")),
+ true),
+ Change.of(Version.fromString("9")),
+ Change.empty(),
+ Optional.of(IssueId.from("tissue")),
+ Optional.of(User.from("user")),
+ OptionalInt.of(3),
+ new ApplicationMetrics(2, 3),
+ Optional.of("key"),
+ List.of(AssignedRotation.fromStrings("container", "endpoint", "rot13", List.of("us-east-1"))),
+ RotationStatus.from(Map.of(new RotationId("rot13"), Map.of(ZoneId.from("prod", "us-east-1"), RotationState.in))));
+
+ Instance old2 = new Instance(ApplicationId.from("t1", "a1", "i1"),
+ Instant.ofEpochMilli(400));
+
+
+ InstanceSerializer instanceSerializer = new InstanceSerializer();
+ String old1Serialized = new String(JsonFormat.toJsonBytes(instanceSerializer.toSlime(old1)), UTF_8);
+
MockCuratorDb newDb = new MockCuratorDb();
OldMockCuratorDb oldDb = new OldMockCuratorDb(newDb.curator());
- Instance instance1 = new Instance(ApplicationId.from("tenant1", "application1", "instance1"), Instant.ofEpochMilli(1));
- Instance instance2 = new Instance(ApplicationId.from("tenant2", "application2", "instance2"), Instant.ofEpochMilli(2));
-
- oldDb.writeInstance(instance1);
- newDb.writeInstance(instance2);
-
- assertEquals(instance1, oldDb.readInstance(instance1.id()).orElseThrow());
- assertEquals(instance1, newDb.readInstance(instance1.id()).orElseThrow());
-
- assertEquals(instance2, oldDb.readInstance(instance2.id()).orElseThrow());
- assertEquals(instance2, newDb.readInstance(instance2.id()).orElseThrow());
-
- assertEquals(List.of(instance1, instance2), oldDb.readInstances());
- assertEquals(List.of(instance1, instance2), newDb.readInstances());
-
- instance1 = new Instance(instance1.id(), Instant.ofEpochMilli(3));
- oldDb.writeInstance(instance1);
- assertEquals(instance1, oldDb.readInstance(instance1.id()).orElseThrow());
- assertEquals(instance1, newDb.readInstance(instance1.id()).orElseThrow());
-
- instance2 = new Instance(instance2.id(), Instant.ofEpochMilli(4));
- newDb.writeInstance(instance2);
- assertEquals(instance2, oldDb.readInstance(instance2.id()).orElseThrow());
- assertEquals(instance2, newDb.readInstance(instance2.id()).orElseThrow());
-
- Application application = newDb.readApplication(instance1.id()).orElseThrow();
- assertEquals(new ApplicationSerializer().toSlime(application).toString(),
- new InstanceSerializer().toSlime(instance1).toString());
-
- oldDb.removeInstance(instance1.id());
- newDb.removeInstance(instance2.id());
- assertEquals(List.of(), oldDb.readInstances());
- assertEquals(List.of(), newDb.readInstances());
+ oldDb.writeInstance(old1);
+ oldDb.writeInstance(old2);
+
+ Application application = newDb.readApplication(TenantAndApplicationId.from("t1", "a1")).orElseThrow();
+ Instance new1 = application.legacy(InstanceName.defaultName());
+ String new1Serialized = new String(JsonFormat.toJsonBytes(instanceSerializer.toSlime(new1)), UTF_8);
+ assertEquals(old1Serialized, new1Serialized);
+
+ LockedApplication locked = new LockedApplication(application, newDb.lock(application.id()));
+ newDb.writeApplication(locked.with(new ApplicationMetrics(8, 9)).get());
+ Instance mod1 = oldDb.readInstance(old1.id()).orElseThrow();
+ Instance mod2 = oldDb.readInstance(old2.id()).orElseThrow();
+
+ old1 = old1.with(new ApplicationMetrics(8, 9));
+ old1Serialized = new String(JsonFormat.toJsonBytes(instanceSerializer.toSlime(old1)), UTF_8);
+ String mod1Serialized = new String(JsonFormat.toJsonBytes(instanceSerializer.toSlime(mod1)), UTF_8);
+ assertEquals(old1Serialized, mod1Serialized);
+
+ assertEquals(old1.createdAt(), mod2.createdAt());
+ assertEquals(old1.change(), mod2.change());
+ assertEquals(old1.outstandingChange(), mod2.outstandingChange());
+ assertEquals(old1.deploymentSpec(), mod2.deploymentSpec());
+ assertEquals(old2.deployments(), mod2.deployments());
+ assertEquals(old2.deploymentJobs().jobStatus(), mod2.deploymentJobs().jobStatus());
+
+ newDb.removeApplication(old1.id());
+ assertEquals(1, newDb.readApplication(application.id()).orElseThrow().instances().size());
+ newDb.removeApplication(old2.id());
+ assertTrue(newDb.readApplication(application.id()).isEmpty());
+ assertTrue(oldDb.readInstance(old1.id()).isEmpty());
+ assertTrue(oldDb.readInstance(old2.id()).isEmpty());
}
private void runUpgrade(DeploymentTester tester, ApplicationId application, ApplicationVersion version) {
Version next = Version.fromString("6.2");
tester.upgradeSystem(next);
- runDeployment(tester, tester.applications().require(application), version, Optional.of(next), Optional.empty());
+ runDeployment(tester, tester.applications().requireInstance(application), version, Optional.of(next), Optional.empty());
}
- private void runDeployment(DeploymentTester tester, ApplicationId id, ApplicationVersion version,
+ private void runDeployment(DeploymentTester tester, TenantAndApplicationId id, ApplicationVersion version,
ApplicationPackage applicationPackage, SourceRevision sourceRevision, long buildNumber) {
- Instance instance = tester.applications().require(id);
+ Instance instance = tester.defaultInstance(id);
tester.jobCompletion(component)
.application(tester.application(id))
.buildNumber(buildNumber)
@@ -839,13 +904,13 @@ public class ControllerTest {
ApplicationVersion change = ApplicationVersion.from(sourceRevision, buildNumber);
assertEquals(change.id(), tester.controller().applications()
- .require(id)
+ .requireApplication(id)
.change().application().get().id());
runDeployment(tester, instance, version, Optional.empty(), Optional.of(applicationPackage));
}
private void assertStatus(JobStatus expectedStatus, ApplicationId id, Controller controller) {
- Instance app = controller.applications().get(id).get();
+ Instance app = controller.applications().getInstance(id).get();
JobStatus existingStatus = app.deploymentJobs().jobStatus().get(expectedStatus.type());
assertNotNull("Status of type " + expectedStatus.type() + " is present", existingStatus);
assertEquals(expectedStatus, existingStatus);
@@ -880,7 +945,7 @@ public class ControllerTest {
assertStatus(expected, app.id(), tester.controller());
// Verify deployed version
- app = tester.controller().applications().require(app.id());
+ app = tester.controller().applications().requireInstance(app.id());
for (Deployment deployment : app.productionDeployments().values()) {
assertEquals(version, deployment.applicationVersion());
upgrade.ifPresent(v -> assertEquals(v, deployment.version()));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 10757902f75..82d9690f7d7 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -14,7 +14,6 @@ import com.yahoo.vespa.athenz.api.AthenzPrincipal;
import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.curator.Lock;
-import com.yahoo.vespa.curator.mock.MockCurator;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
@@ -46,6 +45,7 @@ import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.Consumer;
@@ -54,6 +54,7 @@ import java.util.logging.Handler;
import java.util.logging.Logger;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
/**
* Convenience methods for controller tests.
@@ -182,13 +183,12 @@ public final class ControllerTester {
}
/** Create application from slime */
- public Instance createApplication(Slime slime) {
- InstanceSerializer serializer = new InstanceSerializer();
- Instance instance = serializer.fromSlime(slime);
- try (Lock lock = controller().applications().lock(TenantAndApplicationId.from(instance.id()))) {
- controller().applications().store(new LockedInstance(instance, lock));
+ public void createApplication(Slime slime) {
+ Instance instance = new InstanceSerializer().fromSlime(slime);
+ Application application = Application.aggregate(List.of(instance)).get();
+ try (Lock lock = controller().applications().lock(application.id())) {
+ controller().applications().store(new LockedApplication(application, lock));
}
- return instance;
}
public ZoneId toZone(Environment environment) {
@@ -208,7 +208,7 @@ public final class ControllerTester {
return domain;
}
- public Optional<AthenzDomain> domainOf(ApplicationId id) {
+ public Optional<AthenzDomain> domainOf(TenantAndApplicationId id) {
Tenant tenant = controller().tenants().require(id.tenant());
return tenant.type() == Tenant.Type.athenz ? Optional.of(((AthenzTenant) tenant).domain()) : Optional.empty();
}
@@ -236,7 +236,7 @@ public final class ControllerTester {
return createTenant(tenantName, domainName, propertyId, Optional.empty());
}
- public Optional<Credentials> credentialsFor(ApplicationId id) {
+ public Optional<Credentials> credentialsFor(TenantAndApplicationId id) {
return domainOf(id).map(domain -> new AthenzCredentials(new AthenzPrincipal(new AthenzUser("user")),
domain,
new OktaAccessToken("okta-token")));
@@ -244,10 +244,12 @@ public final class ControllerTester {
public Application createApplication(TenantName tenant, String applicationName, String instanceName, long projectId) {
ApplicationId applicationId = ApplicationId.from(tenant.value(), applicationName, instanceName);
- controller().applications().createApplication(applicationId, credentialsFor(applicationId));
- controller().applications().lockApplicationOrThrow(applicationId, application ->
+ controller().applications().createApplication(applicationId, credentialsFor(TenantAndApplicationId.from(applicationId)));
+ controller().applications().lockApplicationOrThrow(TenantAndApplicationId.from(applicationId), application ->
controller().applications().store(application.withProjectId(OptionalLong.of(projectId))));
- return controller().applications().requireApplication(applicationId);
+ Application application = controller().applications().requireApplication(TenantAndApplicationId.from(applicationId));
+ assertTrue(application.projectId().isPresent());
+ return application;
}
public void deploy(ApplicationId id, ZoneId zone) {
@@ -274,12 +276,7 @@ public final class ControllerTester {
}
public Supplier<Instance> application(ApplicationId application) {
- return () -> controller().applications().require(application);
- }
-
- /** Used by ApplicationSerializerTest to avoid breaking encapsulation. Should not be used by anything else */
- public static LockedInstance writable(Instance instance) {
- return new LockedInstance(instance, new Lock("/test", new MockCurator()));
+ return () -> controller().applications().requireInstance(application);
}
private static Controller createController(CuratorDb curator, RotationsConfig rotationsConfig,
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java
index afe4a1dba1f..ce733d60a77 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java
@@ -52,11 +52,10 @@ public class BuildJob {
}
public BuildJob application(Application application) {
- this.applicationId = application.id();
- if (application.deploymentJobs().projectId().isPresent()) {
- this.projectId = application.deploymentJobs().projectId().getAsLong();
- }
- return this;
+ if (application.projectId().isPresent())
+ this.projectId = application.projectId().getAsLong();
+
+ return application(application.id().defaultInstance());
}
public BuildJob application(ApplicationId applicationId) {
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 c1d8c32f516..5dc6fb183a2 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
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.test.ManualClock;
@@ -19,6 +20,7 @@ import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.maintenance.NameServiceDispatcher;
@@ -113,16 +115,20 @@ public class DeploymentTester {
public ConfigServerMock configServer() { return tester.serviceRegistry().configServerMock(); }
- public Application application(ApplicationId id) {
+ public Application application(TenantAndApplicationId id) {
return controller().applications().requireApplication(id);
}
- public Instance instance(String name) {
+ public Instance defaultInstance(String name) {
return instance(ApplicationId.from("tenant1", name, "default"));
}
+ public Instance defaultInstance(TenantAndApplicationId application) {
+ return controller().applications().requireApplication(application).require(InstanceName.defaultName());
+ }
+
public Instance instance(ApplicationId application) {
- return controller().applications().require(application);
+ return controller().applications().requireInstance(application);
}
/** Re-compute and write version status */
@@ -236,22 +242,22 @@ public class DeploymentTester {
}
private void completeDeployment(Application application, ApplicationPackage applicationPackage, Optional<JobType> failOnJob) {
- assertTrue(application.id() + " has pending changes to deploy", applications().require(application.id()).change().hasTargets());
+ assertTrue(application.id() + " has pending changes to deploy", applications().requireApplication(application.id()).change().hasTargets());
DeploymentSteps steps = controller().applications().deploymentTrigger().steps(applicationPackage.deploymentSpec());
List<JobType> jobs = steps.jobs();
// TODO jonmv: Change to list instances here.
for (JobType job : jobs) {
boolean failJob = failOnJob.map(j -> j.equals(job)).orElse(false);
- deployAndNotify(application.id(), applicationPackage, ! failJob, job);
+ deployAndNotify(application.id().defaultInstance(), applicationPackage, ! failJob, job);
if (failJob) {
break;
}
}
if (failOnJob.isPresent()) {
- assertTrue(applications().require(application.id()).change().hasTargets());
- assertTrue(applications().require(application.id()).deploymentJobs().hasFailures());
+ assertTrue(applications().requireApplication(application.id()).change().hasTargets());
+ assertTrue(defaultInstance(application.id()).deploymentJobs().hasFailures());
} else {
- assertFalse(applications().require(application.id()).change().hasTargets());
+ assertFalse(applications().requireApplication(application.id()).change().hasTargets());
}
if (updateDnsAutomatically) {
flushDnsRequests();
@@ -263,8 +269,8 @@ public class DeploymentTester {
}
public void completeUpgrade(Application application, Version version, ApplicationPackage applicationPackage) {
- assertTrue(application + " has a change", applications().require(application.id()).change().hasTargets());
- assertEquals(Change.of(version), applications().require(application.id()).change());
+ assertTrue(application + " has a change", applications().requireApplication(application.id()).change().hasTargets());
+ assertEquals(Change.of(version), applications().requireApplication(application.id()).change());
completeDeployment(application, applicationPackage, Optional.empty());
}
@@ -277,8 +283,8 @@ public class DeploymentTester {
}
private void completeUpgradeWithError(Application application, Version version, ApplicationPackage applicationPackage, Optional<JobType> failOnJob) {
- assertTrue(applications().require(application.id()).change().hasTargets());
- assertEquals(Change.of(version), applications().require(application.id()).change());
+ assertTrue(applications().requireApplication(application.id()).change().hasTargets());
+ assertEquals(Change.of(version), applications().requireApplication(application.id()).change());
completeDeployment(application, applicationPackage, failOnJob);
}
@@ -327,7 +333,7 @@ public class DeploymentTester {
}
public Optional<JobStatus.JobRun> firstFailing(Instance instance, JobType job) {
- return tester.controller().applications().require(instance.id())
+ return tester.controller().applications().requireInstance(instance.id())
.deploymentJobs().jobStatus().get(job).firstFailing();
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
index 6951271fbaf..5441ed6aec0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.component.Version;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.slime.Slime;
@@ -19,9 +20,11 @@ import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockBuildService;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.maintenance.ReadyJobsTrigger;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
+import org.junit.Ignore;
import org.junit.Test;
import java.nio.file.Files;
@@ -69,7 +72,7 @@ public class DeploymentTriggerTest {
@Test
public void testTriggerFailing() {
Application app = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.upgradePolicy("default")
.environment(Environment.prod)
@@ -90,14 +93,14 @@ public class DeploymentTriggerTest {
tester.upgradeSystem(version);
// staging-test times out and is retried
- tester.buildService().remove(buildJob(app.id(), stagingTest));
+ tester.buildService().remove(buildJob(app.id().defaultInstance(), stagingTest));
tester.readyJobTrigger().maintain();
assertEquals("Retried dead job", 2, tester.buildService().jobs().size());
- tester.assertRunning(stagingTest, app.id());
+ tester.assertRunning(stagingTest, app.id().defaultInstance());
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, stagingTest);
// system-test is now the only running job -- production jobs haven't started yet, since it is unfinished.
- tester.assertRunning(systemTest, app.id());
+ tester.assertRunning(systemTest, app.id().defaultInstance());
assertEquals(1, tester.buildService().jobs().size());
// system-test fails and is retried
@@ -105,15 +108,17 @@ public class DeploymentTriggerTest {
assertEquals("Job is retried on failure", 1, tester.buildService().jobs().size());
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.systemTest);
- tester.assertRunning(productionUsWest1, app.id());
+ tester.assertRunning(productionUsWest1, app.id().defaultInstance());
}
@Test
+ @Ignore
+ // TODO jonmv: Re-enable, but changed, when instances are orchestrated.
public void testIndependentInstances() {
Application app1 = tester.createApplication("instance1", "app", "tenant", 1, 1L);
Application app2 = tester.createApplication("instance2", "app", "tenant", 2, 1L);
- Instance instance1 = tester.instance(app1.id());
- Instance instance2 = tester.instance(app2.id());
+ Instance instance1 = tester.instance(app1.id().instance(InstanceName.from("instance1")));
+ Instance instance2 = tester.instance(app2.id().instance(InstanceName.from("instance2")));
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.upgradePolicy("default")
.environment(Environment.prod)
@@ -124,12 +129,12 @@ public class DeploymentTriggerTest {
tester.upgradeSystem(version);
// Deploy completely once
- tester.jobCompletion(component).application(app1).uploadArtifact(applicationPackage).submit();
+ tester.jobCompletion(component).application(app1).application(instance1.id()).uploadArtifact(applicationPackage).submit();
tester.deployAndNotify(instance1.id(), Optional.of(applicationPackage), true, JobType.systemTest);
tester.deployAndNotify(instance1.id(), Optional.of(applicationPackage), true, JobType.stagingTest);
tester.deployAndNotify(instance1.id(), Optional.of(applicationPackage), true, JobType.productionUsWest1);
- tester.jobCompletion(component).application(app2).uploadArtifact(applicationPackage).submit();
+ tester.jobCompletion(component).application(app2).application(instance2.id()).uploadArtifact(applicationPackage).submit();
tester.deployAndNotify(instance2.id(), Optional.of(applicationPackage), true, JobType.systemTest);
tester.deployAndNotify(instance2.id(), Optional.of(applicationPackage), true, JobType.stagingTest);
tester.deployAndNotify(instance2.id(), Optional.of(applicationPackage), true, JobType.productionUsWest1);
@@ -143,8 +148,8 @@ public class DeploymentTriggerTest {
tester.deployAndNotify(instance1.id(), Optional.of(applicationPackage), true, JobType.stagingTest);
tester.deployAndNotify(instance1.id(), Optional.of(applicationPackage), true, JobType.productionUsWest1);
- Version instance1Version = tester.instance(app1.id()).deployments().get(JobType.productionUsWest1.zone(main)).version();
- Version instance2Version = tester.instance(app2.id()).deployments().get(JobType.productionUsWest1.zone(main)).version();
+ Version instance1Version = tester.defaultInstance(app1.id()).deployments().get(JobType.productionUsWest1.zone(main)).version();
+ Version instance2Version = tester.defaultInstance(app2.id()).deployments().get(JobType.productionUsWest1.zone(main)).version();
assertEquals(newVersion, instance1Version);
assertEquals(version, instance2Version);
@@ -156,7 +161,7 @@ public class DeploymentTriggerTest {
DeploymentTester tester = iTester.tester();
Instance instance = iTester.instance();
- Application application = tester.application(instance.id());
+ Application application = tester.application(TenantAndApplicationId.from(instance.id()));
ApplicationPackage applicationPackage = InternalDeploymentTester.applicationPackage;
tester.jobCompletion(component).application(application).uploadArtifact(applicationPackage).submit();
@@ -168,7 +173,7 @@ public class DeploymentTriggerTest {
tester.jobCompletion(component).application(application).nextBuildNumber().uploadArtifact(applicationPackage).submit();
tester.assertRunning(productionUsCentral1, instance.id());
- tester.applications().deploymentTrigger().cancelChange(instance.id(), ALL);
+ tester.applications().deploymentTrigger().cancelChange(application.id(), ALL);
tester.deployAndNotify(instance.id(), Optional.empty(), false, systemTest);
tester.deployAndNotify(instance.id(), Optional.empty(), false, stagingTest);
tester.deployAndNotify(instance.id(), Optional.empty(), false, productionUsCentral1);
@@ -218,7 +223,7 @@ public class DeploymentTriggerTest {
TenantName tenant = tester.controllerTester().createTenant("tenant1", "domain1", 1L);
MockBuildService mockBuildService = tester.buildService();
Application application = tester.controllerTester().createApplication(tenant, "app1", "default", 1L);
- Instance instance = tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-east-3")
@@ -242,7 +247,7 @@ public class DeploymentTriggerTest {
public void deploymentsSpecWithDelays() {
MockBuildService mockBuildService = tester.buildService();
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
@@ -269,13 +274,13 @@ public class DeploymentTriggerTest {
// 30 seconds later, the first jobs may trigger.
assertEquals(1, mockBuildService.jobs().size());
- tester.assertRunning(productionUsWest1, application.id());
+ tester.assertRunning(productionUsWest1, instance.id());
// 3 minutes pass, delayed trigger does nothing as us-west-1 is still in progress
tester.clock().advance(Duration.ofMinutes(3));
tester.deploymentTrigger().triggerReadyJobs();
assertEquals(1, mockBuildService.jobs().size());
- tester.assertRunning(productionUsWest1, application.id());
+ tester.assertRunning(productionUsWest1, instance.id());
// us-west-1 completes
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsWest1);
@@ -304,7 +309,7 @@ public class DeploymentTriggerTest {
@Test
public void deploymentSpecWithParallelDeployments() {
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
@@ -326,8 +331,8 @@ public class DeploymentTriggerTest {
// Deploys in two regions in parallel
assertEquals(2, tester.buildService().jobs().size());
- tester.assertRunning(productionUsEast3, application.id());
- tester.assertRunning(productionUsWest1, application.id());
+ tester.assertRunning(productionUsEast3, instance.id());
+ tester.assertRunning(productionUsWest1, instance.id());
tester.deploy(JobType.productionUsWest1, instance.id(), applicationPackage, false);
tester.jobCompletion(JobType.productionUsWest1).application(application).submit();
@@ -346,7 +351,7 @@ public class DeploymentTriggerTest {
public void testNoOtherChangesDuringSuspension() {
// Application is deployed in 3 regions:
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-central-1")
@@ -363,7 +368,7 @@ public class DeploymentTriggerTest {
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.productionUsEast3);
// The first production zone is suspended:
- tester.configServer().setSuspended(new DeploymentId(application.id(), JobType.productionUsCentral1.zone(tester.controller().system())), true);
+ tester.configServer().setSuspended(new DeploymentId(instance.id(), JobType.productionUsCentral1.zone(tester.controller().system())), true);
// A new change needs to be pushed out, but should not go beyond the suspended zone:
tester.jobCompletion(component)
@@ -376,11 +381,11 @@ public class DeploymentTriggerTest {
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.stagingTest);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.productionUsCentral1);
tester.triggerUntilQuiescence();
- tester.assertNotRunning(JobType.productionUsEast3, application.id());
- tester.assertNotRunning(JobType.productionUsWest1, application.id());
+ tester.assertNotRunning(JobType.productionUsEast3, instance.id());
+ tester.assertNotRunning(JobType.productionUsWest1, instance.id());
// The zone is unsuspended so jobs start:
- tester.configServer().setSuspended(new DeploymentId(application.id(), JobType.productionUsCentral1.zone(tester.controller().system())), false);
+ tester.configServer().setSuspended(new DeploymentId(instance.id(), JobType.productionUsCentral1.zone(tester.controller().system())), false);
tester.triggerUntilQuiescence();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.productionUsWest1);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.productionUsEast3);
@@ -394,7 +399,7 @@ public class DeploymentTriggerTest {
.build();
Application app = tester.createApplication("app1", "tenant1", 1, 11L);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
tester.jobCompletion(component).application(app).uploadArtifact(applicationPackage).submit();
// Test environments pass
@@ -405,13 +410,13 @@ public class DeploymentTriggerTest {
tester.deploy(JobType.productionUsWest1, instance.id(), applicationPackage);
tester.jobCompletion(JobType.productionUsWest1).application(app).submit();
assertTrue("Change is present as not all jobs are complete",
- tester.applications().require(app.id()).change().hasTargets());
+ tester.applications().requireApplication(app.id()).change().hasTargets());
// All jobs complete
tester.deploy(JobType.productionUsEast3, instance.id(), applicationPackage);
tester.jobCompletion(JobType.productionUsEast3).application(app).submit();
assertFalse("Change has been deployed",
- tester.applications().require(app.id()).change().hasTargets());
+ tester.applications().requireApplication(app.id()).change().hasTargets());
}
@Test
@@ -419,7 +424,7 @@ public class DeploymentTriggerTest {
TenantName tenant = tester.controllerTester().createTenant("tenant1", "domain1", 1L);
MockBuildService mockBuildService = tester.buildService();
Application application = tester.controllerTester().createApplication(tenant, "app1", "default", 1L);
- Instance instance = tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
ApplicationPackage previousApplicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-east-3")
@@ -468,7 +473,7 @@ public class DeploymentTriggerTest {
.region("us-east-3");
Application app = tester.createAndDeploy("app1", 1, applicationPackageBuilder.build());
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
tester.clock().advance(Duration.ofHours(1)); // --------------- Enter block window: 18:30
@@ -489,12 +494,12 @@ public class DeploymentTriggerTest {
.sourceRevision(new SourceRevision("repository1", "master", "cafed00d"))
.uploadArtifact(changedApplication)
.submit();
- assertTrue(tester.applications().require(app.id()).outstandingChange().hasTargets());
+ assertTrue(tester.applications().requireApplication(app.id()).outstandingChange().hasTargets());
tester.deployAndNotify(instance.id(), Optional.of(changedApplication), true, systemTest);
tester.deployAndNotify(instance.id(), Optional.of(changedApplication), true, stagingTest);
tester.outstandingChangeDeployer().run();
- assertTrue(tester.applications().require(app.id()).outstandingChange().hasTargets());
+ assertTrue(tester.applications().requireApplication(app.id()).outstandingChange().hasTargets());
readyJobsTrigger.run();
assertEquals(emptyList(), tester.buildService().jobs());
@@ -502,10 +507,10 @@ public class DeploymentTriggerTest {
tester.clock().advance(Duration.ofHours(2)); // ---------------- Exit block window: 20:30
tester.outstandingChangeDeployer().run();
- assertFalse(tester.applications().require(app.id()).outstandingChange().hasTargets());
+ assertFalse(tester.applications().requireApplication(app.id()).outstandingChange().hasTargets());
tester.deploymentTrigger().triggerReadyJobs(); // Schedules staging test for the blocked production job(s)
- assertEquals(singletonList(buildJob(app.id(), productionUsWest1)), tester.buildService().jobs());
+ assertEquals(singletonList(buildJob(instance.id(), productionUsWest1)), tester.buildService().jobs());
}
@Test
@@ -518,7 +523,7 @@ public class DeploymentTriggerTest {
.region("us-east-3")
.build();
Application application = tester.createAndDeploy("app1", 1, applicationPackage);
- Instance instance = tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
// Application on (6.1, 1.0.42)
Version v1 = Version.fromString("6.1");
@@ -535,9 +540,9 @@ public class DeploymentTriggerTest {
tester.clock().advance(Duration.ofHours(1));
tester.outstandingChangeDeployer().run();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsWest1);
- assertEquals(BuildJob.defaultBuildNumber, tester.instance(application.id()).deploymentJobs().jobStatus()
+ assertEquals(BuildJob.defaultBuildNumber, tester.defaultInstance(application.id()).deploymentJobs().jobStatus()
.get(productionUsWest1).lastSuccess().get().application().buildNumber().getAsLong());
- assertEquals((BuildJob.defaultBuildNumber + 1), tester.instance(application.id()).outstandingChange().application().get().buildNumber().getAsLong());
+ assertEquals((BuildJob.defaultBuildNumber + 1), tester.defaultInstance(application.id()).outstandingChange().application().get().buildNumber().getAsLong());
tester.readyJobTrigger().maintain();
// Platform upgrade keeps rolling, since it has already deployed in a production zone, and tests for the new revision have also started.
@@ -546,55 +551,55 @@ public class DeploymentTriggerTest {
assertEquals(2, tester.buildService().jobs().size());
// Upgrade is done, and oustanding change rolls out when block window ends.
- assertEquals(Change.empty(), tester.instance(application.id()).change());
- assertFalse(tester.instance(application.id()).change().hasTargets());
- assertTrue(tester.instance(application.id()).outstandingChange().hasTargets());
+ assertEquals(Change.empty(), tester.defaultInstance(application.id()).change());
+ assertFalse(tester.defaultInstance(application.id()).change().hasTargets());
+ assertTrue(tester.defaultInstance(application.id()).outstandingChange().hasTargets());
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, stagingTest);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, systemTest);
tester.clock().advance(Duration.ofHours(1));
tester.outstandingChangeDeployer().run();
- assertTrue(tester.instance(application.id()).change().hasTargets());
- assertFalse(tester.instance(application.id()).outstandingChange().hasTargets());
+ assertTrue(tester.defaultInstance(application.id()).change().hasTargets());
+ assertFalse(tester.defaultInstance(application.id()).outstandingChange().hasTargets());
tester.readyJobTrigger().run();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsWest1);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsEast3);
- assertFalse(tester.instance(application.id()).change().hasTargets());
- assertFalse(tester.instance(application.id()).outstandingChange().hasTargets());
+ assertFalse(tester.defaultInstance(application.id()).change().hasTargets());
+ assertFalse(tester.defaultInstance(application.id()).outstandingChange().hasTargets());
}
@Test
public void testJobPause() {
Application app = tester.createAndDeploy("app", 3, "default");
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
tester.upgradeSystem(new Version("9.8.7"));
- tester.applications().deploymentTrigger().pauseJob(app.id(), productionUsWest1, tester.clock().instant().plus(Duration.ofSeconds(1)));
- tester.applications().deploymentTrigger().pauseJob(app.id(), productionUsEast3, tester.clock().instant().plus(Duration.ofSeconds(3)));
+ tester.applications().deploymentTrigger().pauseJob(instance.id(), productionUsWest1, tester.clock().instant().plus(Duration.ofSeconds(1)));
+ tester.applications().deploymentTrigger().pauseJob(instance.id(), productionUsEast3, tester.clock().instant().plus(Duration.ofSeconds(3)));
// us-west-1 does not trigger when paused.
tester.deployAndNotify(instance.id(), Optional.empty(), true, systemTest);
tester.deployAndNotify(instance.id(), Optional.empty(), true, stagingTest);
- tester.assertNotRunning(productionUsWest1, app.id());
+ tester.assertNotRunning(productionUsWest1, instance.id());
// us-west-1 triggers when no longer paused, but does not retry when paused again.
tester.clock().advance(Duration.ofMillis(1500));
tester.readyJobTrigger().run();
- tester.assertRunning(productionUsWest1, app.id());
- tester.applications().deploymentTrigger().pauseJob(app.id(), productionUsWest1, tester.clock().instant().plus(Duration.ofSeconds(1)));
+ tester.assertRunning(productionUsWest1, instance.id());
+ tester.applications().deploymentTrigger().pauseJob(instance.id(), productionUsWest1, tester.clock().instant().plus(Duration.ofSeconds(1)));
tester.deployAndNotify(instance.id(), Optional.empty(), false, productionUsWest1);
- tester.assertNotRunning(productionUsWest1, app.id());
+ tester.assertNotRunning(productionUsWest1, instance.id());
tester.clock().advance(Duration.ofMillis(1000));
tester.readyJobTrigger().run();
tester.deployAndNotify(instance.id(), Optional.empty(), true, productionUsWest1);
// us-east-3 does not automatically trigger when paused, but does when forced.
- tester.assertNotRunning(productionUsEast3, app.id());
- tester.deploymentTrigger().forceTrigger(app.id(), productionUsEast3, "mrTrigger");
- tester.assertRunning(productionUsEast3, app.id());
- assertFalse(tester.instance(app.id()).deploymentJobs().jobStatus().get(productionUsEast3).pausedUntil().isPresent());
+ tester.assertNotRunning(productionUsEast3, instance.id());
+ tester.deploymentTrigger().forceTrigger(instance.id(), productionUsEast3, "mrTrigger");
+ tester.assertRunning(productionUsEast3, instance.id());
+ assertFalse(tester.instance(instance.id()).deploymentJobs().jobStatus().get(productionUsEast3).pausedUntil().isPresent());
}
@Test
@@ -609,15 +614,15 @@ public class DeploymentTriggerTest {
});
assertEquals(0, tester.buildService().jobs().size());
readyJobsTrigger.run();
- tester.assertRunning(systemTest, app.id());
- tester.assertRunning(stagingTest, app.id());
+ tester.assertRunning(systemTest, app.id().defaultInstance());
+ tester.assertRunning(stagingTest, app.id().defaultInstance());
}
@Test
public void applicationVersionIsNotDowngraded() {
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(application.id());
- Supplier<Instance> app = () -> tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
+ Supplier<Instance> app = () -> tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-central-1")
@@ -667,9 +672,9 @@ public class DeploymentTriggerTest {
public void stepIsCompletePreciselyWhenItShouldBe() {
Application application1 = tester.createApplication("app1", "tenant1", 1, 1L);
Application application2 = tester.createApplication("app2", "tenant2", 2, 2L);
- Instance instance1 = tester.instance(application1.id());
- Instance instance2 = tester.instance(application2.id());
- Supplier<Instance> app1 = () -> tester.instance(application1.id());
+ Instance instance1 = tester.defaultInstance(application1.id());
+ Instance instance2 = tester.defaultInstance(application2.id());
+ Supplier<Instance> app1 = () -> tester.defaultInstance(application1.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-central-1")
@@ -723,7 +728,7 @@ public class DeploymentTriggerTest {
tester.deployAndNotify(instance1.id(), Optional.of(applicationPackage), true, systemTest);
tester.deployAndNotify(instance1.id(), Optional.of(applicationPackage), true, stagingTest);
- tester.assertRunning(productionUsCentral1, application1.id());
+ tester.assertRunning(productionUsCentral1, instance1.id());
assertEquals(version2, app1.get().deployments().get(productionUsCentral1.zone(main)).version());
assertEquals(42, app1.get().deployments().get(productionUsCentral1.zone(main)).applicationVersion().buildNumber().getAsLong());
assertNotEquals(triggered, app1.get().deploymentJobs().jobStatus().get(productionUsCentral1).lastTriggered().get().at());
@@ -738,7 +743,7 @@ public class DeploymentTriggerTest {
tester.clock().advance(Duration.ofHours(2).plus(Duration.ofSeconds(1))); // Enough time for retry
tester.readyJobTrigger().maintain();
// Failing job is not retried as change has been deployed
- tester.assertNotRunning(productionUsCentral1, application1.id());
+ tester.assertNotRunning(productionUsCentral1, instance1.id());
// Last job has a different deployment target, so tests need to run again.
tester.deployAndNotify(instance1.id(), Optional.empty(), true, systemTest);
@@ -751,8 +756,8 @@ public class DeploymentTriggerTest {
@Test
public void eachDeployTargetIsTested() {
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(application.id());
- Supplier<Instance> app = () -> tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
+ Supplier<Instance> app = () -> tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.parallel("eu-west-1", "us-east-3")
@@ -808,8 +813,8 @@ public class DeploymentTriggerTest {
@Test
public void eachDifferentUpgradeCombinationIsTested() {
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(application.id());
- Supplier<Instance> app = () -> tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
+ Supplier<Instance> app = () -> tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-central-1")
@@ -849,7 +854,7 @@ public class DeploymentTriggerTest {
@Test
public void retriesFailingJobs() {
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-central-1")
@@ -867,34 +872,34 @@ public class DeploymentTriggerTest {
tester.clock().advance(Duration.ofSeconds(59));
tester.jobCompletion(systemTest).application(application).unsuccessful().submit();
tester.readyJobTrigger().maintain();
- tester.assertRunning(systemTest, application.id());
+ tester.assertRunning(systemTest, instance.id());
// Stops immediate retry after failing for 1 minute
tester.clock().advance(Duration.ofSeconds(1));
tester.jobCompletion(systemTest).application(application).unsuccessful().submit();
tester.readyJobTrigger().maintain();
- tester.assertNotRunning(systemTest, application.id());
+ tester.assertNotRunning(systemTest, instance.id());
// Retries after 10 minutes since previous completion as we failed within the last hour
tester.clock().advance(Duration.ofMinutes(10).plus(Duration.ofSeconds(1)));
tester.readyJobTrigger().maintain();
- tester.assertRunning(systemTest, application.id());
+ tester.assertRunning(systemTest, instance.id());
// Retries less frequently after 1 hour of failure
tester.clock().advance(Duration.ofMinutes(50));
tester.jobCompletion(systemTest).application(application).unsuccessful().submit();
tester.readyJobTrigger().maintain();
- tester.assertNotRunning(systemTest, application.id());
+ tester.assertNotRunning(systemTest, instance.id());
// Retries after two hours pass since last completion
tester.clock().advance(Duration.ofHours(2).plus(Duration.ofSeconds(1)));
tester.readyJobTrigger().maintain();
- tester.assertRunning(systemTest, application.id());
+ tester.assertRunning(systemTest, instance.id());
// Still fails and is not retried
tester.jobCompletion(systemTest).application(application).unsuccessful().submit();
tester.readyJobTrigger().maintain();
- tester.assertNotRunning(systemTest, application.id());
+ tester.assertNotRunning(systemTest, instance.id());
// Another application change is deployed and fixes system-test. Change is triggered immediately as target changes
tester.jobCompletion(component).application(application).nextBuildNumber(2).uploadArtifact(applicationPackage).submit();
@@ -915,7 +920,7 @@ public class DeploymentTriggerTest {
tester.upgradeSystem(version);
Application app = tester.createApplication("app1", "tenant1", 1, 11L);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
tester.jobCompletion(component).application(app).uploadArtifact(applicationPackage).submit();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.systemTest);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.stagingTest);
@@ -936,7 +941,7 @@ public class DeploymentTriggerTest {
tester.clock().advance(Duration.ofSeconds(1)); // Advance time so that we can detect jobs in progress
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), false, JobType.productionUsEast3);
assertEquals("Production job is retried", 1, tester.buildService().jobs().size());
- assertEquals("Application has pending upgrade to " + version, version, tester.instance(app.id()).change().platform().get());
+ assertEquals("Application has pending upgrade to " + version, version, tester.defaultInstance(app.id()).change().platform().get());
// Another version is released, which cancels any pending upgrades to lower versions
version = Version.fromString("6.4");
@@ -944,7 +949,7 @@ public class DeploymentTriggerTest {
tester.upgrader().maintain();
tester.jobCompletion(JobType.productionUsEast3).application(app).unsuccessful().submit();
assertEquals("Application starts upgrading to new version", 2, tester.buildService().jobs().size());
- assertEquals("Application has pending upgrade to " + version, version, tester.instance(app.id()).change().platform().get());
+ assertEquals("Application has pending upgrade to " + version, version, tester.defaultInstance(app.id()).change().platform().get());
// Failure re-deployer did not retry failing job for prod.us-east-3, since it no longer had an available change
assertFalse("Job is not retried", tester.buildService().jobs().stream()
@@ -956,12 +961,12 @@ public class DeploymentTriggerTest {
// Production job fails again, and is retried
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), false, JobType.productionUsEast3);
- assertEquals("Job is retried", Collections.singletonList(buildJob(app.id(), productionUsEast3)), tester.buildService().jobs());
+ assertEquals("Job is retried", Collections.singletonList(buildJob(instance.id(), productionUsEast3)), tester.buildService().jobs());
// Production job finally succeeds
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.productionUsEast3);
assertTrue("All jobs consumed", tester.buildService().jobs().isEmpty());
- assertFalse("No failures", tester.instance(app.id()).deploymentJobs().hasFailures());
+ assertFalse("No failures", tester.defaultInstance(app.id()).deploymentJobs().hasFailures());
}
@Test
@@ -975,7 +980,7 @@ public class DeploymentTriggerTest {
tester.upgradeSystem(version);
Application app = tester.createApplication("app1", "tenant1", 1, 11L);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
tester.jobCompletion(component).application(app).uploadArtifact(applicationPackage).submit();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.systemTest);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, JobType.stagingTest);
@@ -987,7 +992,7 @@ public class DeploymentTriggerTest {
assertEquals(version, tester.controller().versionStatus().systemVersion().get().versionNumber());
tester.upgrader().maintain();
tester.readyJobTrigger().maintain();
- assertEquals("Application has pending upgrade to " + version, version, tester.instance(app.id()).change().platform().get());
+ assertEquals("Application has pending upgrade to " + version, version, tester.defaultInstance(app.id()).change().platform().get());
// system-test fails and is left with a retry
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), false, JobType.systemTest);
@@ -997,20 +1002,20 @@ public class DeploymentTriggerTest {
tester.upgradeSystem(version);
assertEquals(version, tester.controller().versionStatus().systemVersion().get().versionNumber());
- tester.buildService().remove(buildJob(app.id(), systemTest));
+ tester.buildService().remove(buildJob(instance.id(), systemTest));
tester.upgrader().maintain();
tester.readyJobTrigger().maintain();
- assertEquals("Application has pending upgrade to " + version, version, tester.instance(app.id()).change().platform().get());
+ assertEquals("Application has pending upgrade to " + version, version, tester.defaultInstance(app.id()).change().platform().get());
// Cancellation of outdated version and triggering on a new version is done by the upgrader.
- assertEquals(version, tester.instance(app.id()).deploymentJobs().jobStatus().get(systemTest).lastTriggered().get().platform());
+ assertEquals(version, tester.defaultInstance(app.id()).deploymentJobs().jobStatus().get(systemTest).lastTriggered().get().platform());
}
@Test
public void testUpdatesFailingJobStatus() {
// Setup application
Application app = tester.createApplication("app1", "foo", 1, 1L);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-west-1")
@@ -1081,7 +1086,7 @@ public class DeploymentTriggerTest {
Version version1 = tester.controller().versionStatus().systemVersion().get().versionNumber();
Application app1 = tester.createApplication("application1", "tenant1", 1, 1L);
- Instance instance1 = tester.instance(app1.id());
+ Instance instance1 = tester.defaultInstance(app1.id());
// First deployment: An application change
tester.jobCompletion(component).application(app1).uploadArtifact(applicationPackage).submit();
@@ -1097,7 +1102,7 @@ public class DeploymentTriggerTest {
// Unexpected deployment
tester.deploy(productionUsWest1, instance1.id(), applicationPackage);
// instances are immutable, so any change to one, including deployment changes, would give rise to a new instance.
- assertEquals("Unexpected deployment is ignored", instance1, tester.instance(app1.id()));
+ assertEquals("Unexpected deployment is ignored", instance1, tester.defaultInstance(app1.id()));
// Application change after a new system version, and a region added
Version version2 = new Version(version1.getMajor(), version1.getMinor() + 1);
@@ -1151,9 +1156,9 @@ public class DeploymentTriggerTest {
Application app1 = tester.createApplication("app1", "tenant1", project1, 1L);
Application app2 = tester.createApplication("app2", "tenant2", project2, 1L);
Application app3 = tester.createApplication("app3", "tenant3", project3, 1L);
- Instance instance1 = tester.instance(app1.id());
- Instance instance2 = tester.instance(app2.id());
- Instance instance3 = tester.instance(app3.id());
+ Instance instance1 = tester.defaultInstance(app1.id());
+ Instance instance2 = tester.defaultInstance(app2.id());
+ Instance instance3 = tester.defaultInstance(app3.id());
MockBuildService mockBuildService = tester.buildService();
// all applications: system-test completes successfully with some time in between, to determine trigger order.
@@ -1178,23 +1183,23 @@ public class DeploymentTriggerTest {
assertJobsInOrder(jobs, tester.buildService().jobs());
tester.triggerUntilQuiescence();
- jobs.add(buildJob(app2.id(), stagingTest));
- jobs.add(buildJob(app1.id(), stagingTest));
- jobs.add(buildJob(app3.id(), stagingTest));
+ jobs.add(buildJob(instance2.id(), stagingTest));
+ jobs.add(buildJob(instance1.id(), stagingTest));
+ jobs.add(buildJob(instance3.id(), stagingTest));
assertJobsInOrder(jobs, tester.buildService().jobs());
// Remove the jobs for app1 and app2, and then let app3 fail with outOfCapacity.
// All three jobs are now eligible, but the one for app3 should trigger first as an outOfCapacity-retry.
- tester.buildService().remove(buildJob(app1.id(), stagingTest));
- tester.buildService().remove(buildJob(app2.id(), stagingTest));
- jobs.remove(buildJob(app1.id(), stagingTest));
- jobs.remove(buildJob(app2.id(), stagingTest));
+ tester.buildService().remove(buildJob(instance1.id(), stagingTest));
+ tester.buildService().remove(buildJob(instance2.id(), stagingTest));
+ jobs.remove(buildJob(instance1.id(), stagingTest));
+ jobs.remove(buildJob(instance2.id(), stagingTest));
tester.jobCompletion(stagingTest).application(app3).error(DeploymentJobs.JobError.outOfCapacity).submit();
assertJobsInOrder(jobs, tester.buildService().jobs());
tester.triggerUntilQuiescence();
- jobs.add(buildJob(app2.id(), stagingTest));
- jobs.add(buildJob(app1.id(), stagingTest));
+ jobs.add(buildJob(instance2.id(), stagingTest));
+ jobs.add(buildJob(instance1.id(), stagingTest));
assertJobsInOrder(jobs, tester.buildService().jobs());
// Finish deployment for apps 2 and 3, then release a new version, leaving only app1 with an application upgrade.
@@ -1209,29 +1214,29 @@ public class DeploymentTriggerTest {
tester.buildService().clear();
tester.jobCompletion(component).application(app1).nextBuildNumber().uploadArtifact(applicationPackage).submit();
jobs.clear();
- jobs.add(buildJob(app1.id(), stagingTest));
- jobs.add(buildJob(app1.id(), systemTest));
+ jobs.add(buildJob(instance1.id(), stagingTest));
+ jobs.add(buildJob(instance1.id(), systemTest));
// Tests for app1 trigger before the others since it carries an application upgrade.
assertJobsInOrder(jobs, tester.buildService().jobs());
// Let the test jobs start, remove everything expect system test for app3, which fails with outOfCapacity again.
tester.triggerUntilQuiescence();
- tester.buildService().remove(buildJob(app1.id(), systemTest));
- tester.buildService().remove(buildJob(app2.id(), systemTest));
- tester.buildService().remove(buildJob(app1.id(), stagingTest));
- tester.buildService().remove(buildJob(app2.id(), stagingTest));
- tester.buildService().remove(buildJob(app3.id(), stagingTest));
+ tester.buildService().remove(buildJob(instance1.id(), systemTest));
+ tester.buildService().remove(buildJob(instance2.id(), systemTest));
+ tester.buildService().remove(buildJob(instance1.id(), stagingTest));
+ tester.buildService().remove(buildJob(instance2.id(), stagingTest));
+ tester.buildService().remove(buildJob(instance3.id(), stagingTest));
tester.jobCompletion(systemTest).application(app3).error(DeploymentJobs.JobError.outOfCapacity).submit();
jobs.clear();
- jobs.add(buildJob(app1.id(), stagingTest));
- jobs.add(buildJob(app3.id(), systemTest));
+ jobs.add(buildJob(instance1.id(), stagingTest));
+ jobs.add(buildJob(instance3.id(), systemTest));
assertJobsInOrder(jobs, tester.buildService().jobs());
tester.triggerUntilQuiescence();
- jobs.add(buildJob(app2.id(), stagingTest));
- jobs.add(buildJob(app1.id(), systemTest));
- jobs.add(buildJob(app3.id(), stagingTest));
- jobs.add(buildJob(app2.id(), systemTest));
+ jobs.add(buildJob(instance2.id(), stagingTest));
+ jobs.add(buildJob(instance1.id(), systemTest));
+ jobs.add(buildJob(instance3.id(), stagingTest));
+ jobs.add(buildJob(instance2.id(), systemTest));
assertJobsInOrder(jobs, tester.buildService().jobs());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
index c0618937bdf..bcc6fcfda44 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.log.LogLevel;
import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyUtils;
@@ -25,6 +26,7 @@ import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGeneratorMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
@@ -69,8 +71,9 @@ public class InternalDeploymentTester {
.emailAddress("b@a")
.trust(generateCertificate())
.build();
- public static final ApplicationId appId = ApplicationId.from("tenant", "application", "default");
- public static final TesterId testerId = TesterId.of(appId);
+ public static final TenantAndApplicationId appId = TenantAndApplicationId.from("tenant", "application");
+ public static final ApplicationId instanceId = appId.defaultInstance();
+ public static final TesterId testerId = TesterId.of(instanceId);
public static final String athenzDomain = "domain";
private final DeploymentTester tester;
@@ -88,12 +91,12 @@ public class InternalDeploymentTester {
public ApplicationController applications() { return tester.applications(); }
public ManualClock clock() { return tester.clock(); }
public Application application() { return tester.application(appId); }
- public Instance instance() { return tester.instance(appId); }
+ public Instance instance() { return tester.instance(instanceId); }
public InternalDeploymentTester() {
tester = new DeploymentTester();
- tester.controllerTester().createApplication(tester.controllerTester().createTenant(appId.tenant().value(), athenzDomain, 1L),
- appId.application().value(),
+ tester.controllerTester().createApplication(tester.controllerTester().createTenant(instanceId.tenant().value(), athenzDomain, 1L),
+ instanceId.application().value(),
"default",
1);
jobs = tester.controller().jobController();
@@ -117,7 +120,7 @@ public class InternalDeploymentTester {
* Submits a new application, and returns the version of the new submission.
*/
public ApplicationVersion newSubmission() {
- return jobs.submit(appId, BuildJob.defaultSourceRevision, "a@b", 2,
+ return jobs.submit(instanceId, BuildJob.defaultSourceRevision, "a@b", 2,
tester.controller().system().isPublic() ? publicCdApplicationPackage : applicationPackage, new byte[0]);
}
@@ -179,13 +182,13 @@ public class InternalDeploymentTester {
assertTrue(instance().productionDeployments().values().stream()
.allMatch(deployment -> deployment.version().equals(version)));
assertTrue(tester.configServer().nodeRepository()
- .list(JobType.productionAwsUsEast1a.zone(tester.controller().system()), appId).stream()
+ .list(JobType.productionAwsUsEast1a.zone(tester.controller().system()), instanceId).stream()
.allMatch(node -> node.currentVersion().equals(version)));
assertTrue(tester.configServer().nodeRepository()
- .list(JobType.productionUsEast3.zone(tester.controller().system()), appId).stream()
+ .list(JobType.productionUsEast3.zone(tester.controller().system()), instanceId).stream()
.allMatch(node -> node.currentVersion().equals(version)));
assertTrue(tester.configServer().nodeRepository()
- .list(JobType.productionUsEast3.zone(tester.controller().system()), appId).stream()
+ .list(JobType.productionUsEast3.zone(tester.controller().system()), instanceId).stream()
.allMatch(node -> node.currentVersion().equals(version)));
assertFalse(instance().change().hasTargets());
}
@@ -203,15 +206,15 @@ public class InternalDeploymentTester {
assertNotSame(aborted, run.status());
ZoneId zone = type.zone(tester.controller().system());
- DeploymentId deployment = new DeploymentId(appId, zone);
+ DeploymentId deployment = new DeploymentId(instanceId, zone);
// First steps are always deployments.
runner.run();
if (type == JobType.stagingTest) { // Do the initial deployment and installation of the real application.
assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installInitialReal));
- tester.configServer().convergeServices(appId, zone);
- setEndpoints(appId, zone);
+ tester.configServer().convergeServices(instanceId, zone);
+ setEndpoints(instanceId, zone);
run.versions().sourcePlatform().ifPresent(version -> tester.configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), version));
runner.run();
assertEquals(Step.Status.succeeded, jobs.active(run.id()).get().steps().get(Step.installInitialReal));
@@ -221,12 +224,12 @@ public class InternalDeploymentTester {
tester.configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), run.versions().targetPlatform());
runner.run();
assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installReal));
- tester.configServer().convergeServices(appId, zone);
+ tester.configServer().convergeServices(instanceId, zone);
runner.run();
if ( ! (run.versions().sourceApplication().isPresent() && type.isProduction())
&& type != JobType.stagingTest) {
assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installReal));
- setEndpoints(appId, zone);
+ setEndpoints(instanceId, zone);
}
runner.run();
if (type.environment().isManuallyDeployed()) {
@@ -266,9 +269,9 @@ public class InternalDeploymentTester {
public RunId startSystemTestTests() {
RunId id = newRun(JobType.systemTest);
runner.run();
- tester.configServer().convergeServices(appId, JobType.systemTest.zone(tester.controller().system()));
+ tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(tester.controller().system()));
tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(tester.controller().system()));
- setEndpoints(appId, JobType.systemTest.zone(tester.controller().system()));
+ setEndpoints(instanceId, JobType.systemTest.zone(tester.controller().system()));
setEndpoints(testerId.id(), JobType.systemTest.zone(tester.controller().system()));
runner.run();
assertEquals(unfinished, jobs.run(id).get().steps().get(Step.endTests));
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 6813e7f1206..034fd8a64b5 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
@@ -50,7 +50,7 @@ import java.util.concurrent.Future;
import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.error;
import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.info;
import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.warning;
-import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.appId;
+import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.instanceId;
import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.applicationPackage;
import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.publicCdApplicationPackage;
import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.testerId;
@@ -91,9 +91,9 @@ public class InternalStepRunnerTest {
public void canSwitchFromScrewdriverAndBackAgain() {
// Deploys a default application package with default build number.
tester.tester().deployCompletely(tester.application(), InternalDeploymentTester.applicationPackage);
- tester.setEndpoints(appId, JobType.productionUsCentral1.zone(system()));
- tester.setEndpoints(appId, JobType.productionUsWest1.zone(system()));
- tester.setEndpoints(appId, JobType.productionUsEast3.zone(system()));
+ tester.setEndpoints(instanceId, JobType.productionUsCentral1.zone(system()));
+ tester.setEndpoints(instanceId, JobType.productionUsWest1.zone(system()));
+ tester.setEndpoints(instanceId, JobType.productionUsEast3.zone(system()));
// Let application have an ongoing upgrade when it switches (but kill the jobs, as the tester assumes they aren't running).
tester.tester().upgradeSystem(new Version("7.1"));
@@ -104,7 +104,7 @@ public class InternalStepRunnerTest {
tester.deployNewPlatform(new Version("7.2"));
- tester.jobs().unregister(appId);
+ tester.jobs().unregister(instanceId);
try {
tester.tester().deployCompletely(tester.application(), InternalDeploymentTester.applicationPackage, BuildJob.defaultBuildNumber + 1);
throw new IllegalStateException("Component job should get even again with build numbers to produce a change.");
@@ -133,8 +133,8 @@ public class InternalStepRunnerTest {
tester.runner().run();
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installInitialReal));
- tester.setEndpoints(appId, JobType.stagingTest.zone(system()));
- tester.configServer().convergeServices(appId, JobType.stagingTest.zone(system()));
+ tester.setEndpoints(instanceId, JobType.stagingTest.zone(system()));
+ tester.configServer().convergeServices(instanceId, JobType.stagingTest.zone(system()));
tester.configServer().setConfigChangeActions(new ConfigChangeActions(Collections.emptyList(),
singletonList(new RefeedAction("Refeed",
false,
@@ -151,7 +151,7 @@ public class InternalStepRunnerTest {
public void restartsServicesAndWaitsForRestartAndReboot() {
RunId id = tester.newRun(JobType.productionUsCentral1);
ZoneId zone = id.type().zone(system());
- HostName host = tester.configServer().hostFor(appId, zone);
+ HostName host = tester.configServer().hostFor(instanceId, zone);
tester.setEndpoints(testerId.id(), JobType.productionUsCentral1.zone(system()));
tester.runner().run();
@@ -168,11 +168,11 @@ public class InternalStepRunnerTest {
tester.runner().run();
assertEquals(succeeded, tester.jobs().run(id).get().steps().get(Step.deployReal));
- tester.configServer().convergeServices(appId, zone);
+ tester.configServer().convergeServices(instanceId, zone);
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
- tester.configServer().nodeRepository().doRestart(new DeploymentId(appId, zone), Optional.of(host));
- tester.configServer().nodeRepository().requestReboot(new DeploymentId(appId, zone), Optional.of(host));
+ tester.configServer().nodeRepository().doRestart(new DeploymentId(instanceId, zone), Optional.of(host));
+ tester.configServer().nodeRepository().requestReboot(new DeploymentId(instanceId, zone), Optional.of(host));
tester.runner().run();
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
@@ -187,67 +187,67 @@ public class InternalStepRunnerTest {
// Tester fails to show up for staging tests, and the real deployment for system tests.
tester.setEndpoints(testerId.id(), JobType.systemTest.zone(system()));
- tester.setEndpoints(appId, JobType.stagingTest.zone(system()));
+ tester.setEndpoints(instanceId, JobType.stagingTest.zone(system()));
tester.runner().run();
- tester.configServer().convergeServices(appId, JobType.stagingTest.zone(system()));
+ tester.configServer().convergeServices(instanceId, JobType.stagingTest.zone(system()));
tester.runner().run();
- tester.configServer().convergeServices(appId, JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(system()));
tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(system()));
- tester.configServer().convergeServices(appId, JobType.stagingTest.zone(system()));
+ tester.configServer().convergeServices(instanceId, JobType.stagingTest.zone(system()));
tester.configServer().convergeServices(testerId.id(), JobType.stagingTest.zone(system()));
tester.runner().run();
tester.clock().advance(InternalStepRunner.endpointTimeout.plus(Duration.ofSeconds(1)));
tester.runner().run();
- assertEquals(failed, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installReal));
- assertEquals(failed, tester.jobs().last(appId, JobType.stagingTest).get().steps().get(Step.installTester));
+ assertEquals(failed, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(failed, tester.jobs().last(instanceId, JobType.stagingTest).get().steps().get(Step.installTester));
}
@Test
public void installationFailsIfDeploymentExpires() {
tester.newRun(JobType.systemTest);
tester.runner().run();
- tester.configServer().convergeServices(appId, JobType.systemTest.zone(system()));
- tester.setEndpoints(appId, JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(system()));
+ tester.setEndpoints(instanceId, JobType.systemTest.zone(system()));
tester.runner().run();
- assertEquals(succeeded, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(succeeded, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installReal));
- tester.applications().deactivate(appId, JobType.systemTest.zone(system()));
+ tester.applications().deactivate(instanceId, JobType.systemTest.zone(system()));
tester.runner().run();
- assertEquals(failed, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installTester));
- assertTrue(tester.jobs().last(appId, JobType.systemTest).get().hasEnded());
- assertTrue(tester.jobs().last(appId, JobType.systemTest).get().hasFailed());
+ assertEquals(failed, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installTester));
+ assertTrue(tester.jobs().last(instanceId, JobType.systemTest).get().hasEnded());
+ assertTrue(tester.jobs().last(instanceId, JobType.systemTest).get().hasFailed());
}
@Test
public void startTestsFailsIfDeploymentExpires() {
tester.newRun(JobType.systemTest);
tester.runner().run();
- tester.configServer().convergeServices(appId, JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(system()));
tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(system()));
tester.runner().run();
- tester.applications().deactivate(appId, JobType.systemTest.zone(system()));
+ tester.applications().deactivate(instanceId, JobType.systemTest.zone(system()));
tester.runner().run();
- assertEquals(unfinished, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.startTests));
+ assertEquals(unfinished, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.startTests));
}
@Test
public void alternativeEndpointsAreDetected() {
tester.newRun(JobType.systemTest);
tester.runner().run();;
- tester.configServer().convergeServices(appId, JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(system()));
tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(system()));
- assertEquals(unfinished, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installReal));
- assertEquals(unfinished, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installTester));
-
- tester.tester().controller().curator().writeRoutingPolicies(appId, Set.of(new RoutingPolicy(appId,
- ClusterSpec.Id.from("default"),
- JobType.systemTest.zone(system()),
- HostName.from("host"),
- Optional.empty(),
- emptySet())));
+ assertEquals(unfinished, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(unfinished, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installTester));
+
+ tester.tester().controller().curator().writeRoutingPolicies(instanceId, Set.of(new RoutingPolicy(instanceId,
+ ClusterSpec.Id.from("default"),
+ JobType.systemTest.zone(system()),
+ HostName.from("host"),
+ Optional.empty(),
+ emptySet())));
tester.tester().controller().curator().writeRoutingPolicies(testerId.id(), Set.of(new RoutingPolicy(testerId.id(),
ClusterSpec.Id.from("default"),
JobType.systemTest.zone(system()),
@@ -255,8 +255,8 @@ public class InternalStepRunnerTest {
Optional.empty(),
emptySet())));
tester.runner().run();;
- assertEquals(succeeded, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installReal));
- assertEquals(succeeded, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installTester));
+ assertEquals(succeeded, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(succeeded, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installTester));
}
@Test
@@ -303,12 +303,12 @@ public class InternalStepRunnerTest {
assertEquals(URI.create(tester.routing().endpoints(new DeploymentId(testerId.id(), JobType.systemTest.zone(system()))).get(0).endpoint()),
tester.cloud().testerUrl());
Inspector configObject = SlimeUtils.jsonToSlime(tester.cloud().config()).get();
- assertEquals(appId.serializedForm(), configObject.field("application").asString());
+ assertEquals(instanceId.serializedForm(), configObject.field("application").asString());
assertEquals(JobType.systemTest.zone(system()).value(), configObject.field("zone").asString());
assertEquals(system().value(), configObject.field("system").asString());
assertEquals(1, configObject.field("endpoints").children());
assertEquals(1, configObject.field("endpoints").field(JobType.systemTest.zone(system()).value()).entries());
- configObject.field("endpoints").field(JobType.systemTest.zone(system()).value()).traverse((ArrayTraverser) (__, endpoint) -> assertEquals(tester.routing().endpoints(new DeploymentId(appId, JobType.systemTest.zone(system()))).get(0).endpoint(), endpoint.asString()));
+ configObject.field("endpoints").field(JobType.systemTest.zone(system()).value()).traverse((ArrayTraverser) (__, endpoint) -> assertEquals(tester.routing().endpoints(new DeploymentId(instanceId, JobType.systemTest.zone(system()))).get(0).endpoint(), endpoint.asString()));
long lastId = tester.jobs().details(id).get().lastId().getAsLong();
tester.cloud().add(new LogEntry(0, Instant.ofEpochMilli(123), info, "Ready!"));
@@ -336,36 +336,36 @@ public class InternalStepRunnerTest {
@Test
public void deployToDev() {
ZoneId zone = JobType.devUsEast1.zone(system());
- tester.jobs().deploy(appId, JobType.devUsEast1, Optional.empty(), applicationPackage);
+ tester.jobs().deploy(instanceId, JobType.devUsEast1, Optional.empty(), applicationPackage);
tester.runner().run();
- RunId id = tester.jobs().last(appId, JobType.devUsEast1).get().id();
+ RunId id = tester.jobs().last(instanceId, JobType.devUsEast1).get().id();
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
Version version = new Version("7.8.9");
Future<?> concurrentDeployment = Executors.newSingleThreadExecutor().submit(() -> {
- tester.jobs().deploy(appId, JobType.devUsEast1, Optional.of(version), applicationPackage);
+ tester.jobs().deploy(instanceId, JobType.devUsEast1, Optional.of(version), applicationPackage);
});
while ( ! concurrentDeployment.isDone())
tester.runner().run();
- assertEquals(id.number() + 1, tester.jobs().last(appId, JobType.devUsEast1).get().id().number());
+ assertEquals(id.number() + 1, tester.jobs().last(instanceId, JobType.devUsEast1).get().id().number());
ApplicationPackage otherPackage = new ApplicationPackageBuilder().region("us-central-1").build();
- tester.jobs().deploy(appId, JobType.perfUsEast3, Optional.empty(), otherPackage);
+ tester.jobs().deploy(instanceId, JobType.perfUsEast3, Optional.empty(), otherPackage);
tester.runner().run(); // Job run order determined by JobType enum order per application.
- tester.configServer().convergeServices(appId, zone);
- tester.setEndpoints(appId, zone);
+ tester.configServer().convergeServices(instanceId, zone);
+ tester.setEndpoints(instanceId, zone);
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
- assertEquals(applicationPackage.hash(), tester.configServer().application(appId, zone).get().applicationPackage().hash());
- assertEquals(otherPackage.hash(), tester.configServer().application(appId, JobType.perfUsEast3.zone(system())).get().applicationPackage().hash());
+ assertEquals(applicationPackage.hash(), tester.configServer().application(instanceId, zone).get().applicationPackage().hash());
+ assertEquals(otherPackage.hash(), tester.configServer().application(instanceId, JobType.perfUsEast3.zone(system())).get().applicationPackage().hash());
- tester.configServer().setVersion(appId, zone, version);
+ tester.configServer().setVersion(instanceId, zone, version);
tester.runner().run();
assertEquals(1, tester.jobs().active().size());
- assertEquals(version, tester.tester().instance(appId).deployments().get(zone).version());
+ assertEquals(version, tester.tester().instance(instanceId).deployments().get(zone).version());
try {
- tester.jobs().deploy(appId, JobType.productionApNortheast1, Optional.empty(), applicationPackage);
+ tester.jobs().deploy(instanceId, JobType.productionApNortheast1, Optional.empty(), applicationPackage);
fail("Deployments outside dev should not be allowed.");
}
catch (IllegalArgumentException expected) { }
@@ -416,7 +416,7 @@ public class InternalStepRunnerTest {
List<X509Certificate> trusted = new ArrayList<>(publicCdApplicationPackage.trustedCertificates());
trusted.add(tester.jobs().run(id).get().testerCertificate().get());
- assertEquals(trusted, tester.configServer().application(appId, id.type().zone(system())).get().applicationPackage().trustedCertificates());
+ assertEquals(trusted, tester.configServer().application(instanceId, id.type().zone(system())).get().applicationPackage().trustedCertificates());
tester.clock().advance(InternalStepRunner.certificateTimeout.plus(Duration.ofSeconds(1)));
tester.runner().run();
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 d4550ecc338..2ddef642065 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
@@ -14,7 +14,7 @@ import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
-import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.appId;
+import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.instanceId;
import static org.junit.Assert.assertEquals;
/**
@@ -25,7 +25,7 @@ public class TestConfigSerializerTest {
@Test
public void testConfig() throws IOException {
ZoneId zone = JobType.systemTest.zone(SystemName.PublicCd);
- byte[] json = new TestConfigSerializer(SystemName.PublicCd).configJson(appId,
+ byte[] json = new TestConfigSerializer(SystemName.PublicCd).configJson(instanceId,
JobType.systemTest,
Map.of(zone, Map.of(ClusterSpec.Id.from("ai"),
URI.create("https://server/"))),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
index 98e8eff508e..ec98fbf69cb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
@@ -44,12 +44,12 @@ public class ApplicationOwnershipConfirmerTest {
Optional<Contact> contact = Optional.of(tester.controllerTester().serviceRegistry().contactRetrieverMock().contact());
TenantName property = tester.controllerTester().createTenant("property", "domain", 1L, contact);
tester.createAndDeploy(property, "application", 1, "default");
- Supplier<Instance> propertyApp = () -> tester.controller().applications().require(ApplicationId.from("property", "application", "default"));
+ Supplier<Instance> propertyApp = () -> tester.controller().applications().requireInstance(ApplicationId.from("property", "application", "default"));
UserTenant user = UserTenant.create("by-user", contact);
tester.controller().tenants().createUser(user);
tester.createAndDeploy(user.name(), "application", 2, "default");
- Supplier<Instance> userApp = () -> tester.controller().applications().require(ApplicationId.from("by-user", "application", "default"));
+ Supplier<Instance> userApp = () -> tester.controller().applications().requireInstance(ApplicationId.from("by-user", "application", "default"));
assertFalse("No issue is initially stored for a new application.", propertyApp.get().ownershipIssueId().isPresent());
assertFalse("No issue is initially stored for a new application.", userApp.get().ownershipIssueId().isPresent());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainerTest.java
index 1f5cc100fac..9739a24af01 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainerTest.java
@@ -4,16 +4,13 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.application.Deployment;
-import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import org.junit.Test;
@@ -33,12 +30,12 @@ public class ClusterInfoMaintainerTest {
@Test
public void maintain() {
tester.createTenant("tenant1", "domain123", 321L);
- ApplicationId app = tester.createApplication(TenantName.from("tenant1"), "app1", "default", 123).id();
+ ApplicationId app = tester.createApplication(TenantName.from("tenant1"), "app1", "default", 123).id().defaultInstance();
ZoneId zone = ZoneId.from("dev", "us-east-1");
tester.deploy(app, zone);
// Precondition: no cluster info attached to the deployments
- Deployment deployment = tester.controller().applications().get(app).get().deployments().values().stream()
+ Deployment deployment = tester.controller().applications().getInstance(app).get().deployments().values().stream()
.findFirst()
.get();
assertEquals(0, deployment.clusterInfo().size());
@@ -48,7 +45,7 @@ public class ClusterInfoMaintainerTest {
new JobControl(new MockCuratorDb()));
maintainer.maintain();
- deployment = tester.controller().applications().get(app).get().deployments().values().stream()
+ deployment = tester.controller().applications().getInstance(app).get().deployments().values().stream()
.findFirst()
.get();
assertEquals(2, deployment.clusterInfo().size());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
index f3c387eca1b..d43526ce5fa 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
@@ -37,8 +37,8 @@ public class DeploymentExpirerTest {
Application devApp = tester.createApplication("app1", "tenant1", 123L, 1L);
Application prodApp = tester.createApplication("app2", "tenant2", 456L, 2L);
- Instance devInstance = tester.instance(devApp.id());
- Instance prodInstance = tester.instance(prodApp.id());
+ Instance devInstance = tester.defaultInstance(devApp.id());
+ Instance prodInstance = tester.defaultInstance(prodApp.id());
// Deploy dev
tester.controllerTester().deploy(devInstance.id(), tester.controllerTester().toZone(Environment.dev));
@@ -65,7 +65,7 @@ public class DeploymentExpirerTest {
}
private List<Deployment> permanentDeployments(Instance instance) {
- return tester.controller().applications().get(instance.id()).get().deployments().values().stream()
+ return tester.controller().applications().getInstance(instance.id()).get().deployments().values().stream()
.filter(deployment -> deployment.zone().environment() != Environment.test &&
deployment.zone().environment() != Environment.staging)
.collect(Collectors.toList());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
index 7a4d6821637..3c1fb2fffd0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
@@ -10,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
@@ -87,16 +88,16 @@ public class DeploymentIssueReporterTest {
// apps 1 and 3 have one failure each.
tester.jobCompletion(component).application(app1).uploadArtifact(applicationPackage).submit();
- tester.deployAndNotify(app1.id(), applicationPackage, true, systemTest);
- tester.deployAndNotify(app1.id(), applicationPackage, false, stagingTest);
+ tester.deployAndNotify(app1.id().defaultInstance(), applicationPackage, true, systemTest);
+ tester.deployAndNotify(app1.id().defaultInstance(), applicationPackage, false, stagingTest);
// app2 is successful, but will fail later.
tester.deployCompletely(app2, canaryPackage);
tester.jobCompletion(component).application(app3).uploadArtifact(applicationPackage).submit();
- tester.deployAndNotify(app3.id(), applicationPackage, true, systemTest);
- tester.deployAndNotify(app3.id(), applicationPackage, true, stagingTest);
- tester.deployAndNotify(app3.id(), applicationPackage, false, productionUsWest1);
+ tester.deployAndNotify(app3.id().defaultInstance(), applicationPackage, true, systemTest);
+ tester.deployAndNotify(app3.id().defaultInstance(), applicationPackage, true, stagingTest);
+ tester.deployAndNotify(app3.id().defaultInstance(), applicationPackage, false, productionUsWest1);
reporter.maintain();
reporter.maintain();
@@ -131,7 +132,7 @@ public class DeploymentIssueReporterTest {
// app3 fixes their problems, but the ticket for app3 is left open; see the resolved ticket is not escalated when another escalation period has passed.
- tester.deployAndNotify(app3.id(), applicationPackage, true, productionUsWest1);
+ tester.deployAndNotify(app3.id().defaultInstance(), applicationPackage, true, productionUsWest1);
tester.clock().advance(maxInactivity.plus(Duration.ofDays(1)));
reporter.maintain();
@@ -143,7 +144,7 @@ public class DeploymentIssueReporterTest {
// app3 now has a new failure past max failure age; see that a new issue is filed.
tester.jobCompletion(component).application(app3).nextBuildNumber().uploadArtifact(applicationPackage).submit();
- tester.deployAndNotify(app3.id(), applicationPackage, false, systemTest);
+ tester.deployAndNotify(app3.id().defaultInstance(), applicationPackage, false, systemTest);
tester.clock().advance(maxInactivity.plus(maxFailureAge));
reporter.maintain();
@@ -176,7 +177,7 @@ public class DeploymentIssueReporterTest {
class MockDeploymentIssues extends LoggingDeploymentIssues {
- private final Map<ApplicationId, IssueId> applicationIssues = new HashMap<>();
+ private final Map<TenantAndApplicationId, IssueId> applicationIssues = new HashMap<>();
private final Map<IssueId, Integer> issueLevels = new HashMap<>();
MockDeploymentIssues() {
@@ -192,24 +193,24 @@ public class DeploymentIssueReporterTest {
@Override
protected IssueId fileIssue(ApplicationId applicationId) {
IssueId issueId = super.fileIssue(applicationId);
- applicationIssues.put(applicationId, issueId);
+ applicationIssues.put(TenantAndApplicationId.from(applicationId), issueId);
return issueId;
}
- void closeFor(ApplicationId applicationId) {
- issueUpdates.remove(applicationIssues.remove(applicationId));
+ void closeFor(TenantAndApplicationId id) {
+ issueUpdates.remove(applicationIssues.remove(id));
}
- void touchFor(ApplicationId applicationId) {
- issueUpdates.put(applicationIssues.get(applicationId), tester.clock().instant());
+ void touchFor(TenantAndApplicationId id) {
+ issueUpdates.put(applicationIssues.get(id), tester.clock().instant());
}
- boolean isOpenFor(ApplicationId applicationId) {
- return applicationIssues.containsKey(applicationId);
+ boolean isOpenFor(TenantAndApplicationId id) {
+ return applicationIssues.containsKey(id);
}
- int escalationLevelFor(ApplicationId applicationId) {
- return issueLevels.getOrDefault(applicationIssues.get(applicationId), 0);
+ int escalationLevelFor(TenantAndApplicationId id) {
+ return issueLevels.getOrDefault(applicationIssues.get(id), 0);
}
int size() {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
index 7f537c2e4bb..cf33b354139 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
@@ -37,10 +37,10 @@ public class DeploymentMetricsMaintainerTest {
@Test
public void updates_metrics() {
var application = tester.createApplication("app1", "tenant1", 123L, 1L);
- deploy(application.id(), Version.fromString("7.1"));
+ deploy(application.id().defaultInstance(), Version.fromString("7.1"));
DeploymentMetricsMaintainer maintainer = maintainer(tester.controller());
- Supplier<Instance> app = () -> tester.instance(application.id());
+ Supplier<Instance> app = () -> tester.defaultInstance(application.id());
Supplier<Deployment> deployment = () -> app.get().deployments().values().stream().findFirst().get();
// No metrics gathered yet
@@ -51,7 +51,7 @@ public class DeploymentMetricsMaintainerTest {
assertFalse("Never received any writes", deployment.get().activity().lastWritten().isPresent());
// Only get application metrics for old version
- deploy(application.id(), Version.fromString("6.3.3"));
+ deploy(application.id().defaultInstance(), Version.fromString("6.3.3"));
maintainer.maintain();
assertEquals(0, app.get().metrics().queryServiceQuality(), 0);
assertEquals(0, deployment.get().metrics().documentCount(), 0);
@@ -60,13 +60,13 @@ public class DeploymentMetricsMaintainerTest {
assertFalse("Never received any writes", deployment.get().activity().lastWritten().isPresent());
// Metrics are gathered and saved to application
- deploy(application.id(), Version.fromString("7.5.5"));
+ deploy(application.id().defaultInstance(), Version.fromString("7.5.5"));
var metrics0 = Map.of(ClusterMetrics.QUERIES_PER_SECOND, 1D,
ClusterMetrics.FEED_PER_SECOND, 2D,
ClusterMetrics.DOCUMENT_COUNT, 3D,
ClusterMetrics.QUERY_LATENCY, 4D,
ClusterMetrics.FEED_LATENCY, 5D);
- setMetrics(application.id(), metrics0);
+ setMetrics(application.id().defaultInstance(), metrics0);
maintainer.maintain();
Instant t1 = tester.clock().instant().truncatedTo(MILLIS);
assertEquals(0.0, app.get().metrics().queryServiceQuality(), Double.MIN_VALUE);
@@ -96,7 +96,7 @@ public class DeploymentMetricsMaintainerTest {
var metrics1 = new HashMap<>(metrics0);
metrics1.put(ClusterMetrics.QUERIES_PER_SECOND, 0D);
metrics1.put(ClusterMetrics.FEED_PER_SECOND, 5D);
- setMetrics(application.id(), metrics1);
+ setMetrics(application.id().defaultInstance(), metrics1);
maintainer.maintain();
assertEquals(t2, deployment.get().activity().lastQueried().get());
assertEquals(t3, deployment.get().activity().lastWritten().get());
@@ -107,7 +107,7 @@ public class DeploymentMetricsMaintainerTest {
tester.clock().advance(Duration.ofHours(1));
var metrics2 = new HashMap<>(metrics1);
metrics2.put(ClusterMetrics.FEED_PER_SECOND, 0D);
- setMetrics(application.id(), metrics2);
+ setMetrics(application.id().defaultInstance(), metrics2);
maintainer.maintain();
assertEquals(t2, deployment.get().activity().lastQueried().get());
assertEquals(t3, deployment.get().activity().lastWritten().get());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
index d0896b5e60b..ff245e2e488 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
@@ -8,6 +8,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationV
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.deployment.Run;
@@ -83,7 +84,7 @@ public class JobRunnerTest {
JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()),
phasedExecutor(phaser), stepRunner);
- ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
+ ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id().defaultInstance();
jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
jobs.start(id, systemTest, versions);
@@ -114,7 +115,7 @@ public class JobRunnerTest {
JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()),
inThreadExecutor(), mappedRunner(outcomes));
- ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
+ ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id().defaultInstance();
jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
Supplier<Run> run = () -> jobs.last(id, systemTest).get();
@@ -202,7 +203,7 @@ public class JobRunnerTest {
JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()),
Executors.newFixedThreadPool(32), waitingRunner(barrier));
- ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
+ ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id().defaultInstance();
jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
RunId runId = new RunId(id, systemTest, 1);
@@ -217,7 +218,7 @@ public class JobRunnerTest {
// Thread is still trying to deploy tester -- delete application, and see all data is garbage collected.
assertEquals(Collections.singletonList(runId), jobs.active().stream().map(run -> run.id()).collect(Collectors.toList()));
- tester.controllerTester().controller().applications().deleteApplication(id.tenant(), id.application(), tester.controllerTester().credentialsFor(id));
+ tester.controllerTester().controller().applications().deleteApplication(id.tenant(), id.application(), tester.controllerTester().credentialsFor(TenantAndApplicationId.from(id)));
assertEquals(Collections.emptyList(), jobs.active());
assertEquals(runId, jobs.last(id, systemTest).get().id());
@@ -238,7 +239,7 @@ public class JobRunnerTest {
JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()),
inThreadExecutor(), (id, step) -> Optional.of(running));
- ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
+ ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id().defaultInstance();
jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
for (int i = 0; i < jobs.historyLength(); i++) {
@@ -266,7 +267,7 @@ public class JobRunnerTest {
JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()),
inThreadExecutor(), mappedRunner(outcomes));
- ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id();
+ ApplicationId id = tester.createApplication("real", "tenant", 1, 1L).id().defaultInstance();
jobs.submit(id, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
jobs.start(id, systemTest, versions);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
index 11749afb6ed..4fc952b0b15 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
@@ -6,7 +6,6 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
-import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
@@ -63,7 +62,7 @@ public class MetricsReporterTest {
// 1 app fails system-test
tester.jobCompletion(component).application(app4).nextBuildNumber().uploadArtifact(applicationPackage).submit();
- tester.deployAndNotify(app4.id(), Optional.of(applicationPackage), false, systemTest);
+ tester.deployAndNotify(app4.id().defaultInstance(), Optional.of(applicationPackage), false, systemTest);
metricsReporter.maintain();
assertEquals(25.0, metrics.getMetric(MetricsReporter.DEPLOYMENT_FAIL_METRIC));
@@ -82,22 +81,22 @@ public class MetricsReporterTest {
Application app = tester.createApplication("app1", "tenant1", 1, 11L);
tester.deployCompletely(app, applicationPackage);
reporter.maintain();
- assertEquals(Duration.ZERO, getAverageDeploymentDuration(app.id())); // An exceptionally fast deployment :-)
+ assertEquals(Duration.ZERO, getAverageDeploymentDuration(app.id().defaultInstance())); // An exceptionally fast deployment :-)
// App spends 3 hours deploying
tester.jobCompletion(component).application(app).nextBuildNumber().uploadArtifact(applicationPackage).submit();
tester.clock().advance(Duration.ofHours(1));
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, systemTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, systemTest);
tester.clock().advance(Duration.ofMinutes(30));
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, stagingTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, stagingTest);
tester.clock().advance(Duration.ofMinutes(90));
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, productionUsWest1);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, productionUsWest1);
reporter.maintain();
// Average time is 1 hour (system-test) + 90 minutes (staging-test runs in parallel with system-test) + 90 minutes (production) / 3 jobs
- assertEquals(Duration.ofMinutes(80), getAverageDeploymentDuration(app.id()));
+ assertEquals(Duration.ofMinutes(80), getAverageDeploymentDuration(app.id().defaultInstance()));
// Another deployment starts and stalls for 12 hours
tester.jobCompletion(component).application(app).nextBuildNumber(2).uploadArtifact(applicationPackage).submit();
@@ -108,7 +107,7 @@ public class MetricsReporterTest {
.plus(Duration.ofHours(12)) // hanging staging-test
.plus(Duration.ofMinutes(90)) // previous production job
.dividedBy(3), // Total number of orchestrated jobs
- getAverageDeploymentDuration(app.id()));
+ getAverageDeploymentDuration(app.id().defaultInstance()));
}
@Test
@@ -125,41 +124,41 @@ public class MetricsReporterTest {
// Initial deployment without failures
tester.deployCompletely(app, applicationPackage);
reporter.maintain();
- assertEquals(0, getDeploymentsFailingUpgrade(app.id()));
+ assertEquals(0, getDeploymentsFailingUpgrade(app.id().defaultInstance()));
// Failing application change is not counted
tester.jobCompletion(component).application(app).nextBuildNumber().uploadArtifact(applicationPackage).submit();
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), false, systemTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), false, systemTest);
reporter.maintain();
- assertEquals(0, getDeploymentsFailingUpgrade(app.id()));
+ assertEquals(0, getDeploymentsFailingUpgrade(app.id().defaultInstance()));
// Application change completes
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, systemTest);
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, stagingTest);
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, productionUsWest1);
- assertFalse("Change deployed", tester.controller().applications().require(app.id()).change().hasTargets());
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, systemTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, stagingTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, productionUsWest1);
+ assertFalse("Change deployed", tester.controller().applications().requireApplication(app.id()).change().hasTargets());
// New versions is released and upgrade fails in test environments
Version version = Version.fromString("7.1");
tester.upgradeSystem(version);
tester.upgrader().maintain();
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), false, systemTest);
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), false, stagingTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), false, systemTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), false, stagingTest);
reporter.maintain();
- assertEquals(2, getDeploymentsFailingUpgrade(app.id()));
+ assertEquals(2, getDeploymentsFailingUpgrade(app.id().defaultInstance()));
// Test and staging pass and upgrade fails in production
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, systemTest);
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, stagingTest);
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), false, productionUsWest1);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, systemTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, stagingTest);
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), false, productionUsWest1);
reporter.maintain();
- assertEquals(1, getDeploymentsFailingUpgrade(app.id()));
+ assertEquals(1, getDeploymentsFailingUpgrade(app.id().defaultInstance()));
// Upgrade eventually succeeds
- tester.deployAndNotify(app.id(), Optional.of(applicationPackage), true, productionUsWest1);
- assertFalse("Upgrade deployed", tester.controller().applications().require(app.id()).change().hasTargets());
+ tester.deployAndNotify(app.id().defaultInstance(), Optional.of(applicationPackage), true, productionUsWest1);
+ assertFalse("Upgrade deployed", tester.controller().applications().requireApplication(app.id()).change().hasTargets());
reporter.maintain();
- assertEquals(0, getDeploymentsFailingUpgrade(app.id()));
+ assertEquals(0, getDeploymentsFailingUpgrade(app.id().defaultInstance()));
}
@Test
@@ -172,11 +171,11 @@ public class MetricsReporterTest {
.build();
MetricsReporter reporter = createReporter(tester.controller());
Application application = tester.createApplication("app1", "tenant1", 1, 11L);
- tester.configServer().generateWarnings(new DeploymentId(application.id(), ZoneId.from("prod", "us-west-1")), 3);
- tester.configServer().generateWarnings(new DeploymentId(application.id(), ZoneId.from("prod", "us-east-3")), 4);
+ tester.configServer().generateWarnings(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-west-1")), 3);
+ tester.configServer().generateWarnings(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-east-3")), 4);
tester.deployCompletely(application, applicationPackage);
reporter.maintain();
- assertEquals(4, getDeploymentWarnings(application.id()));
+ assertEquals(4, getDeploymentWarnings(application.id().defaultInstance()));
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
index e438aab007f..02282894544 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
@@ -42,11 +42,11 @@ public class OutstandingChangeDeployerTest {
Application app2 = tester.createAndDeploy("app2", 22, applicationPackage);
Version version = new Version(6, 2);
- tester.deploymentTrigger().triggerChange(tester.instance("app1").id(), Change.of(version));
+ tester.deploymentTrigger().triggerChange(app1.id(), Change.of(version));
tester.deploymentTrigger().triggerReadyJobs();
- assertEquals(Change.of(version), tester.instance("app1").change());
- assertFalse(tester.instance("app1").outstandingChange().hasTargets());
+ assertEquals(Change.of(version), tester.defaultInstance("app1").change());
+ assertFalse(tester.defaultInstance("app1").outstandingChange().hasTargets());
tester.jobCompletion(JobType.component)
.application(app1)
@@ -55,7 +55,7 @@ public class OutstandingChangeDeployerTest {
.uploadArtifact(applicationPackage)
.submit();
- Instance instance = tester.instance("app1");
+ Instance instance = tester.defaultInstance("app1");
assertTrue(instance.outstandingChange().hasTargets());
assertEquals("1.0.43-cafed00d", instance.outstandingChange().application().get().id());
assertEquals(2, tester.buildService().jobs().size());
@@ -74,13 +74,13 @@ public class OutstandingChangeDeployerTest {
deployer.maintain();
tester.deploymentTrigger().triggerReadyJobs();
- instance = tester.instance("app1");
+ instance = tester.defaultInstance("app1");
assertEquals("1.0.43-cafed00d", instance.change().application().get().id());
List<BuildService.BuildJob> jobs = tester.buildService().jobs();
assertEquals(1, jobs.size());
assertEquals(JobType.productionUsWest1.jobName(), jobs.get(0).jobName());
- assertEquals(11, jobs.get(0).projectId());
- assertFalse(tester.instance("app1").outstandingChange().hasTargets());
+ assertEquals(app1.id().defaultInstance(), jobs.get(0).applicationId());
+ assertFalse(tester.defaultInstance("app1").outstandingChange().hasTargets());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java
index 0eaa3e19927..b0201a21de5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java
@@ -42,7 +42,7 @@ public class RotationStatusUpdaterTest {
.build();
tester.deployCompletely(application, applicationPackage);
- Supplier<Instance> app = () -> tester.instance(application.id());
+ Supplier<Instance> app = () -> tester.defaultInstance(application.id());
Supplier<Deployment> deployment1 = () -> app.get().deployments().get(zone1);
Supplier<Deployment> deployment2 = () -> app.get().deployments().get(zone2);
Supplier<Deployment> deployment3 = () -> app.get().deployments().get(zone3);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
index c1e1f0d0607..78a9be9f1d3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
@@ -41,8 +41,8 @@ public class RoutingPoliciesTest {
private final Application app1 = tester.createApplication("app1", "tenant1", 1, 1L);
private final Application app2 = tester.createApplication("app2", "tenant1", 1, 1L);
- private final Instance instance1 = tester.instance(app1.id());
- private final Instance instance2 = tester.instance(app2.id());
+ private final Instance instance1 = tester.defaultInstance(app1.id());
+ private final Instance instance2 = tester.defaultInstance(app2.id());
private final ZoneId zone1 = ZoneId.from("prod", "us-west-1");
private final ZoneId zone2 = ZoneId.from("prod", "us-central-1");
@@ -65,7 +65,7 @@ public class RoutingPoliciesTest {
.endpoint("r1", "c0", "us-west-1")
.endpoint("r2", "c1")
.build();
- provisionLoadBalancers(clustersPerZone, app1.id(), zone1, zone2);
+ provisionLoadBalancers(clustersPerZone, instance1.id(), zone1, zone2);
// Creates alias records
tester.deployCompletely(app1, applicationPackage, ++buildNumber);
@@ -86,7 +86,7 @@ public class RoutingPoliciesTest {
aliasDataOf(endpoint3));
assertEquals("Routing policy count is equal to cluster count",
numberOfDeployments * clustersPerZone,
- tester.controller().applications().routingPolicies().get(app1.id()).size());
+ tester.controller().applications().routingPolicies().get(instance1.id()).size());
// Applications gains a new deployment
ApplicationPackage applicationPackage2 = new ApplicationPackageBuilder()
@@ -98,7 +98,7 @@ public class RoutingPoliciesTest {
.endpoint("r2", "c1")
.build();
numberOfDeployments++;
- provisionLoadBalancers(clustersPerZone, app1.id(), zone3);
+ provisionLoadBalancers(clustersPerZone, instance1.id(), zone3);
tester.deployCompletely(app1, applicationPackage2, ++buildNumber);
// Endpoint is updated to contain cluster in new deployment
@@ -110,7 +110,7 @@ public class RoutingPoliciesTest {
// Another application is deployed with a single cluster and global endpoint
var endpoint4 = "r0.app2.tenant1.global.vespa.oath.cloud";
- provisionLoadBalancers(1, app2.id(), zone1, zone2);
+ provisionLoadBalancers(1, instance2.id(), zone1, zone2);
var applicationPackage3 = new ApplicationPackageBuilder()
.region(zone1.region())
.region(zone2.region())
@@ -132,7 +132,7 @@ public class RoutingPoliciesTest {
assertEquals("DNS records are removed", List.of(), aliasDataOf(endpoint1));
assertEquals("DNS records are removed", List.of(), aliasDataOf(endpoint2));
assertEquals("DNS records are removed", List.of(), aliasDataOf(endpoint3));
- Set<RoutingPolicy> policies = tester.controller().curator().readRoutingPolicies(app1.id());
+ Set<RoutingPolicy> policies = tester.controller().curator().readRoutingPolicies(instance1.id());
assertEquals(clustersPerZone * numberOfDeployments, policies.size());
assertTrue("Rotation membership is removed from all policies",
policies.stream().allMatch(policy -> policy.endpoints().isEmpty()));
@@ -144,7 +144,7 @@ public class RoutingPoliciesTest {
// Deploy application
int clustersPerZone = 2;
int buildNumber = 42;
- provisionLoadBalancers(clustersPerZone, app1.id(), zone1, zone2);
+ provisionLoadBalancers(clustersPerZone, instance1.id(), zone1, zone2);
tester.deployCompletely(app1, applicationPackage, ++buildNumber);
// Deployment creates records and policies for all clusters in all zones
@@ -163,7 +163,7 @@ public class RoutingPoliciesTest {
assertEquals(4, policies(instance1).size());
// Add 1 cluster in each zone and deploy
- provisionLoadBalancers(clustersPerZone + 1, app1.id(), zone1, zone2);
+ provisionLoadBalancers(clustersPerZone + 1, instance1.id(), zone1, zone2);
tester.deployCompletely(app1, applicationPackage, ++buildNumber);
expectedRecords = Set.of(
"c0.app1.tenant1.us-west-1.vespa.oath.cloud",
@@ -177,7 +177,7 @@ public class RoutingPoliciesTest {
assertEquals(6, policies(instance1).size());
// Deploy another application
- provisionLoadBalancers(clustersPerZone, app2.id(), zone1, zone2);
+ provisionLoadBalancers(clustersPerZone, instance2.id(), zone1, zone2);
tester.deployCompletely(app2, applicationPackage, ++buildNumber);
expectedRecords = Set.of(
"c0.app1.tenant1.us-west-1.vespa.oath.cloud",
@@ -195,7 +195,7 @@ public class RoutingPoliciesTest {
assertEquals(4, policies(instance2).size());
// Deploy removes cluster from app1
- provisionLoadBalancers(clustersPerZone, app1.id(), zone1, zone2);
+ provisionLoadBalancers(clustersPerZone, instance1.id(), zone1, zone2);
tester.deployCompletely(app1, applicationPackage, ++buildNumber);
expectedRecords = Set.of(
"c0.app1.tenant1.us-west-1.vespa.oath.cloud",
@@ -210,10 +210,10 @@ public class RoutingPoliciesTest {
assertEquals(expectedRecords, recordNames());
// Remove app2 completely
- tester.controller().applications().require(app2.id()).deployments().keySet()
+ tester.controller().applications().requireInstance(instance2.id()).deployments().keySet()
.forEach(zone -> {
- tester.configServer().removeLoadBalancers(app2.id(), zone);
- tester.controller().applications().deactivate(app2.id(), zone);
+ tester.configServer().removeLoadBalancers(instance2.id(), zone);
+ tester.controller().applications().deactivate(instance2.id(), zone);
});
tester.flushDnsRequests();
expectedRecords = Set.of(
@@ -223,22 +223,22 @@ public class RoutingPoliciesTest {
"c1.app1.tenant1.us-central-1.vespa.oath.cloud"
);
assertEquals(expectedRecords, recordNames());
- assertTrue("Removes stale routing policies " + app2, tester.controller().applications().routingPolicies().get(app2.id()).isEmpty());
- assertEquals("Keeps routing policies for " + app1, 4, tester.controller().applications().routingPolicies().get(app1.id()).size());
+ assertTrue("Removes stale routing policies " + app2, tester.controller().applications().routingPolicies().get(instance2.id()).isEmpty());
+ assertEquals("Keeps routing policies for " + app1, 4, tester.controller().applications().routingPolicies().get(instance1.id()).size());
}
@Test
public void cluster_endpoints_resolve_from_policies() {
- provisionLoadBalancers(3, app1.id(), zone1);
+ provisionLoadBalancers(3, instance1.id(), zone1);
tester.deployCompletely(app1, applicationPackage);
- tester.controllerTester().serviceRegistry().routingGeneratorMock().putEndpoints(new DeploymentId(app1.id(), zone1), Collections.emptyList());
+ tester.controllerTester().serviceRegistry().routingGeneratorMock().putEndpoints(new DeploymentId(instance1.id(), zone1), Collections.emptyList());
assertEquals(Map.of(ClusterSpec.Id.from("c0"),
URI.create("https://c0.app1.tenant1.us-west-1.vespa.oath.cloud/"),
ClusterSpec.Id.from("c1"),
URI.create("https://c1.app1.tenant1.us-west-1.vespa.oath.cloud/"),
ClusterSpec.Id.from("c2"),
URI.create("https://c2.app1.tenant1.us-west-1.vespa.oath.cloud/")),
- tester.controller().applications().clusterEndpoints(new DeploymentId(app1.id(), zone1)));
+ tester.controller().applications().clusterEndpoints(new DeploymentId(instance1.id(), zone1)));
}
private Set<RoutingPolicy> policies(Instance instance) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
index f6121760567..e247c2af3c9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
@@ -62,12 +62,12 @@ public class UpgraderTest {
Application default2 = tester.createAndDeploy("default2", 5, "default");
Application conservative0 = tester.createAndDeploy("conservative0", 6, "conservative");
- Instance canary0default = tester.instance(canary0.id());
- Instance canary1default = tester.instance(canary1.id());
- Instance default0default = tester.instance(default0.id());
- Instance default1default = tester.instance(default1.id());
- Instance default2default = tester.instance(default2.id());
- Instance conservative0default = tester.instance(conservative0.id());
+ Instance canary0default = tester.defaultInstance(canary0.id());
+ Instance canary1default = tester.defaultInstance(canary1.id());
+ Instance default0default = tester.defaultInstance(default0.id());
+ Instance default1default = tester.defaultInstance(default1.id());
+ Instance default2default = tester.defaultInstance(default2.id());
+ Instance conservative0default = tester.defaultInstance(conservative0.id());
tester.upgrader().maintain();
tester.triggerUntilQuiescence();
@@ -134,9 +134,9 @@ public class UpgraderTest {
tester.upgradeSystem(version3);
assertEquals(version3, tester.controller().versionStatus().systemVersion().get().versionNumber());
tester.upgrader().maintain();
- tester.buildService().remove(ControllerTester.buildJob(canary0.id(), stagingTest));
- tester.buildService().remove(ControllerTester.buildJob(canary1.id(), systemTest));
- tester.buildService().remove(ControllerTester.buildJob(canary1.id(), stagingTest));
+ tester.buildService().remove(ControllerTester.buildJob(canary0default.id(), stagingTest));
+ tester.buildService().remove(ControllerTester.buildJob(canary1default.id(), systemTest));
+ tester.buildService().remove(ControllerTester.buildJob(canary1default.id(), stagingTest));
tester.triggerUntilQuiescence();
assertEquals("New system version: Should upgrade Canaries", 4, tester.buildService().jobs().size());
@@ -204,11 +204,11 @@ public class UpgraderTest {
tester.triggerUntilQuiescence();
assertEquals("Upgrade of defaults are scheduled", 10, tester.buildService().jobs().size());
- assertEquals(version4, tester.instance(default0.id()).change().platform().get());
- assertEquals(version4, tester.instance(default1.id()).change().platform().get());
- assertEquals(version4, tester.instance(default2.id()).change().platform().get());
- assertEquals(version4, tester.instance(default3.id()).change().platform().get());
- assertEquals(version4, tester.instance(default4.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default0.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default1.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default2.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default3.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default4.id()).change().platform().get());
tester.completeUpgrade(default0, version4, "default");
// State: Default applications started upgrading to version4 (and one completed)
@@ -224,11 +224,11 @@ public class UpgraderTest {
tester.triggerUntilQuiescence();
assertEquals("Upgrade of defaults are scheduled", 10, tester.buildService().jobs().size());
- assertEquals(version5, tester.instance(default0.id()).change().platform().get());
- assertEquals(version4, tester.instance(default1.id()).change().platform().get());
- assertEquals(version4, tester.instance(default2.id()).change().platform().get());
- assertEquals(version4, tester.instance(default3.id()).change().platform().get());
- assertEquals(version4, tester.instance(default4.id()).change().platform().get());
+ assertEquals(version5, tester.defaultInstance(default0.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default1.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default2.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default3.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default4.id()).change().platform().get());
tester.completeUpgrade(default1, version4, "default");
tester.completeUpgrade(default2, version4, "default");
@@ -260,7 +260,7 @@ public class UpgraderTest {
assertEquals("Upgrade of defaults are scheduled on " + version4 + " instead, since " + version5 + " is broken: " +
"This is default3 since it failed upgrade on both " + version4 + " and " + version5,
2, tester.buildService().jobs().size());
- assertEquals(version4, tester.instance(default3.id()).change().platform().get());
+ assertEquals(version4, tester.defaultInstance(default3.id()).change().platform().get());
}
@Test
@@ -347,7 +347,7 @@ public class UpgraderTest {
tester.upgradeSystem(version);
Application app = tester.createApplication("app1", "tenant1", 1, 11L);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
tester.jobCompletion(component).application(app).uploadArtifact(applicationPackage).submit();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, systemTest);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, stagingTest);
@@ -370,8 +370,8 @@ public class UpgraderTest {
// staging-test fails and failure is recorded
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), false, stagingTest);
- assertTrue("Failure is recorded", tester.instance(app.id()).deploymentJobs().hasFailures());
- assertTrue("Application has pending change", tester.instance(app.id()).change().hasTargets());
+ assertTrue("Failure is recorded", tester.defaultInstance(app.id()).deploymentJobs().hasFailures());
+ assertTrue("Application has pending change", tester.defaultInstance(app.id()).change().hasTargets());
// New version is released
version = Version.fromString("6.4");
@@ -382,7 +382,7 @@ public class UpgraderTest {
tester.upgrader().maintain();
tester.triggerUntilQuiescence();
tester.jobCompletion(stagingTest).application(app).unsuccessful().submit();
- assertTrue("Application still has failures", tester.instance(app.id()).deploymentJobs().hasFailures());
+ assertTrue("Application still has failures", tester.defaultInstance(app.id()).deploymentJobs().hasFailures());
assertEquals(2, tester.buildService().jobs().size());
// Upgrader runs again, nothing happens as test jobs are already running.
@@ -435,7 +435,7 @@ public class UpgraderTest {
tester.triggerUntilQuiescence();
// apps pass system-test, but do not trigger next jobs as upgrade is cancelled
- assertFalse("No change present", tester.applications().require(default4.id()).change().hasTargets());
+ assertFalse("No change present", tester.applications().requireInstance(default4.id().defaultInstance()).change().hasTargets());
tester.jobCompletion(systemTest).application(default0).submit();
tester.jobCompletion(systemTest).application(default1).submit();
tester.jobCompletion(systemTest).application(default2).submit();
@@ -510,7 +510,7 @@ public class UpgraderTest {
tester.upgrader().maintain();
tester.triggerUntilQuiescence();
assertEquals("Upgrade scheduled for remaining apps", 10, tester.buildService().jobs().size());
- assertEquals("default4 is still upgrading to 5.1", v1, tester.instance(default4.id()).change().platform().get());
+ assertEquals("default4 is still upgrading to 5.1", v1, tester.defaultInstance(default4.id()).change().platform().get());
// 4/5 applications fail (in the last prod zone) and lowers confidence
tester.completeUpgradeWithError(default0, v2, "default", productionUsEast3);
@@ -520,8 +520,8 @@ public class UpgraderTest {
tester.upgradeSystem(v2);
assertEquals(VespaVersion.Confidence.broken, tester.controller().versionStatus().systemVersion().get().confidence());
- assertEquals(v2, tester.instance("default0").deployments().get(ZoneId.from("prod.us-west-1")).version());
- assertEquals(v0, tester.instance("default0").deployments().get(ZoneId.from("prod.us-east-3")).version());
+ assertEquals(v2, tester.defaultInstance("default0").deployments().get(ZoneId.from("prod.us-west-1")).version());
+ assertEquals(v0, tester.defaultInstance("default0").deployments().get(ZoneId.from("prod.us-east-3")).version());
tester.upgrader().maintain();
tester.buildService().clear();
tester.triggerUntilQuiescence();
@@ -529,16 +529,16 @@ public class UpgraderTest {
assertEquals("Upgrade to 5.1 scheduled for apps not completely on 5.1 or 5.2", 10, tester.buildService().jobs().size());
// The tester code for completing upgrades does not handle this scenario, so we trigger each step manually (for one app)
- tester.deployAndNotify(tester.instance("default0").id(), DeploymentTester.applicationPackage("default"), true, systemTest);
- tester.deployAndNotify(tester.instance("default0").id(), DeploymentTester.applicationPackage("default"), true, stagingTest);
+ tester.deployAndNotify(tester.defaultInstance("default0").id(), DeploymentTester.applicationPackage("default"), true, systemTest);
+ tester.deployAndNotify(tester.defaultInstance("default0").id(), DeploymentTester.applicationPackage("default"), true, stagingTest);
// prod zone on 5.2 (usWest1) is skipped, but we still trigger the next zone from triggerReadyJobs:
- tester.deployAndNotify(tester.instance("default0").id(), DeploymentTester.applicationPackage("default"), true, productionUsEast3);
+ tester.deployAndNotify(tester.defaultInstance("default0").id(), DeploymentTester.applicationPackage("default"), true, productionUsEast3);
// Resulting state:
- assertEquals(v2, tester.instance("default0").deployments().get(ZoneId.from("prod.us-west-1")).version());
+ assertEquals(v2, tester.defaultInstance("default0").deployments().get(ZoneId.from("prod.us-west-1")).version());
assertEquals("Last zone is upgraded to v1",
- v1, tester.instance("default0").deployments().get(ZoneId.from("prod.us-east-3")).version());
- assertFalse(tester.instance("default0").change().hasTargets());
+ v1, tester.defaultInstance("default0").deployments().get(ZoneId.from("prod.us-east-3")).version());
+ assertFalse(tester.defaultInstance("default0").change().hasTargets());
}
@Test
@@ -653,7 +653,7 @@ public class UpgraderTest {
.build();
Application app = tester.createAndDeploy("app1", 1, applicationPackage);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
// New version is released
version = Version.fromString("6.3");
@@ -691,7 +691,7 @@ public class UpgraderTest {
.build();
Application app = tester.createAndDeploy("app1", 1, applicationPackage);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
// New version is released
version = Version.fromString("6.3");
@@ -748,7 +748,7 @@ public class UpgraderTest {
assertTrue("All jobs consumed", tester.buildService().jobs().isEmpty());
// App is completely upgraded to the latest version
- for (Deployment deployment : tester.applications().require(app.id()).deployments().values())
+ for (Deployment deployment : tester.applications().requireInstance(instance.id()).deployments().values())
assertEquals(version, deployment.version());
}
@@ -822,7 +822,7 @@ public class UpgraderTest {
tester.jobCompletion(stagingTest).application(default4).unsuccessful().submit();
// 5th app never reports back and has a dead job, but no ongoing change
- Instance deadLocked = tester.applications().require(default4.id());
+ Instance deadLocked = tester.applications().requireInstance(default4.id().defaultInstance());
tester.assertRunning(systemTest, deadLocked.id());
assertFalse("No change present", deadLocked.change().hasTargets());
@@ -850,10 +850,10 @@ public class UpgraderTest {
tester.completeUpgrade(default2, version, defaultApplicationPackageV2);
tester.completeUpgrade(default3, version, defaultApplicationPackageV2);
- assertEquals(version, tester.instance(default0.id()).oldestDeployedPlatform().get());
- assertEquals(version, tester.instance(default1.id()).oldestDeployedPlatform().get());
- assertEquals(version, tester.instance(default2.id()).oldestDeployedPlatform().get());
- assertEquals(version, tester.instance(default3.id()).oldestDeployedPlatform().get());
+ assertEquals(version, tester.defaultInstance(default0.id()).oldestDeployedPlatform().get());
+ assertEquals(version, tester.defaultInstance(default1.id()).oldestDeployedPlatform().get());
+ assertEquals(version, tester.defaultInstance(default2.id()).oldestDeployedPlatform().get());
+ assertEquals(version, tester.defaultInstance(default3.id()).oldestDeployedPlatform().get());
}
@Test
@@ -878,7 +878,7 @@ public class UpgraderTest {
// Dev deployment which should be ignored
Application dev0 = tester.createApplication("dev0", "tenant1", 7, 1L);
- tester.controllerTester().deploy(dev0.id(), ZoneId.from(Environment.dev, RegionName.from("dev-region")));
+ tester.controllerTester().deploy(dev0.id().defaultInstance(), ZoneId.from(Environment.dev, RegionName.from("dev-region")));
// New version is released and canaries upgrade
version = Version.fromString("6.3");
@@ -959,7 +959,7 @@ public class UpgraderTest {
Application canary0 = tester.createAndDeploy("canary0", 1, "canary");
Application default0 = tester.createAndDeploy("default0", 2, default0ApplicationPackage);
tester.applications().lockApplicationOrThrow(default0.id(), a -> tester.applications().store(a.withMajorVersion(6)));
- assertEquals(OptionalInt.of(6), tester.applications().get(default0.id()).get().majorVersion());
+ assertEquals(OptionalInt.of(6), tester.applications().getInstance(default0.id().defaultInstance()).get().majorVersion());
// New major version is released
version = Version.fromString("7.0");
@@ -1047,7 +1047,7 @@ public class UpgraderTest {
.build();
Application app = tester.createAndDeploy("app1", 1, applicationPackage);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
// New version is released
version = Version.fromString("6.3");
@@ -1076,7 +1076,7 @@ public class UpgraderTest {
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsWest1);
assertTrue("All jobs consumed", tester.buildService().jobs().isEmpty());
- instance = tester.instance(app.id());
+ instance = tester.defaultInstance(app.id());
for (Deployment deployment : instance.deployments().values()) {
assertEquals(version, deployment.version());
assertEquals(applicationVersion, deployment.applicationVersion().id());
@@ -1101,7 +1101,7 @@ public class UpgraderTest {
.build();
Application app = tester.createAndDeploy("app1", 1, applicationPackage);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
tester.jobCompletion(component).application(app).nextBuildNumber().uploadArtifact(applicationPackage).submit();
@@ -1154,7 +1154,7 @@ public class UpgraderTest {
.build();
Application app = tester.createAndDeploy("app1", 1, applicationPackage);
- Instance instance = tester.instance(app.id());
+ Instance instance = tester.defaultInstance(app.id());
tester.jobCompletion(component).application(app).nextBuildNumber().uploadArtifact(applicationPackage).submit();
@@ -1179,16 +1179,16 @@ public class UpgraderTest {
assertEquals(Collections.emptyList(), tester.buildService().jobs()); // No jobs left.
tester.outstandingChangeDeployer().run();
- assertFalse(tester.instance(app.id()).change().hasTargets());
+ assertFalse(tester.defaultInstance(app.id()).change().hasTargets());
tester.clock().advance(Duration.ofHours(2));
tester.outstandingChangeDeployer().run();
- assertTrue(tester.instance(app.id()).change().hasTargets());
+ assertTrue(tester.defaultInstance(app.id()).change().hasTargets());
tester.readyJobTrigger().run();
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsWest1);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsCentral1);
tester.deployAndNotify(instance.id(), Optional.of(applicationPackage), true, productionUsEast3);
- assertFalse(tester.instance(app.id()).change().hasTargets());
+ assertFalse(tester.defaultInstance(app.id()).change().hasTargets());
}
@Test
@@ -1199,7 +1199,7 @@ public class UpgraderTest {
// Create an application with pinned platform version.
Application application = tester.createApplication("application", "tenant", 2, 3);
- Instance instance = tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder().environment(Environment.prod)
.region("us-east-3")
.region("us-west-1")
@@ -1207,33 +1207,33 @@ public class UpgraderTest {
tester.deploymentTrigger().forceChange(application.id(), Change.empty().withPin());
tester.deployCompletely(application, applicationPackage);
- assertFalse(tester.instance(application.id()).change().hasTargets());
- assertTrue(tester.instance(application.id()).change().isPinned());
- assertEquals(2, tester.instance(application.id()).deployments().size());
+ assertFalse(tester.defaultInstance(application.id()).change().hasTargets());
+ assertTrue(tester.defaultInstance(application.id()).change().isPinned());
+ assertEquals(2, tester.defaultInstance(application.id()).deployments().size());
// Application does not upgrade.
Version version1 = Version.fromString("6.3");
tester.upgradeSystem(version1);
tester.upgrader().maintain();
- assertFalse(tester.instance(application.id()).change().hasTargets());
- assertTrue(tester.instance(application.id()).change().isPinned());
+ assertFalse(tester.defaultInstance(application.id()).change().hasTargets());
+ assertTrue(tester.defaultInstance(application.id()).change().isPinned());
// New application package is deployed.
tester.deployCompletely(application, applicationPackage, BuildJob.defaultBuildNumber + 1);
- assertFalse(tester.instance(application.id()).change().hasTargets());
- assertTrue(tester.instance(application.id()).change().isPinned());
+ assertFalse(tester.defaultInstance(application.id()).change().hasTargets());
+ assertTrue(tester.defaultInstance(application.id()).change().isPinned());
// Application upgrades to new version when pin is removed.
tester.deploymentTrigger().cancelChange(application.id(), PIN);
tester.upgrader().maintain();
- assertTrue(tester.instance(application.id()).change().hasTargets());
- assertFalse(tester.instance(application.id()).change().isPinned());
+ assertTrue(tester.defaultInstance(application.id()).change().hasTargets());
+ assertFalse(tester.defaultInstance(application.id()).change().isPinned());
// Application is pinned to new version, and upgrade is therefore not cancelled, even though confidence is broken.
tester.deploymentTrigger().forceChange(application.id(), Change.empty().withPin());
tester.upgrader().maintain();
tester.readyJobTrigger().maintain();
- assertEquals(version1, tester.instance(application.id()).change().platform().get());
+ assertEquals(version1, tester.defaultInstance(application.id()).change().platform().get());
// Application fails upgrade after one zone is complete, and is pinned again to the old version.
tester.deployAndNotify(instance.id(), Optional.empty(), true, systemTest);
@@ -1244,16 +1244,16 @@ public class UpgraderTest {
tester.deploymentTrigger().cancelChange(application.id(), ALL);
tester.deploymentTrigger().forceChange(application.id(), Change.of(version0).withPin());
tester.buildService().clear();
- assertEquals(version0, tester.instance(application.id()).change().platform().get());
+ assertEquals(version0, tester.defaultInstance(application.id()).change().platform().get());
// Application downgrades to pinned version.
tester.readyJobTrigger().maintain();
tester.deployAndNotify(instance.id(), Optional.empty(), true, systemTest);
tester.deployAndNotify(instance.id(), Optional.empty(), true, stagingTest);
tester.deployAndNotify(instance.id(), Optional.empty(), true, productionUsEast3);
- assertTrue(tester.instance(application.id()).change().hasTargets());
+ assertTrue(tester.defaultInstance(application.id()).change().hasTargets());
tester.deployAndNotify(instance.id(), Optional.empty(), true, productionUsWest1);
- assertFalse(tester.instance(application.id()).change().hasTargets());
+ assertFalse(tester.defaultInstance(application.id()).change().hasTargets());
}
@Test
@@ -1288,22 +1288,22 @@ public class UpgraderTest {
// App 2 is allowed on new major and upgrades
tester.controller().applications().lockApplicationIfPresent(app2.id(), app -> tester.applications().store(app.withMajorVersion(7)));
tester.upgrader().maintain();
- assertEquals(version2, tester.controller().applications().require(app2.id()).change().platform().get());
+ assertEquals(version2, tester.controller().applications().requireInstance(app2.id().defaultInstance()).change().platform().get());
// App 1 is unpinned and upgrades to latest 6
tester.controller().applications().lockApplicationIfPresent(app1.id(), app ->
tester.controller().applications().store(app.withChange(app.get().change().withoutPin())));
tester.upgrader().maintain();
assertEquals("Application upgrades to latest allowed major", version1,
- tester.controller().applications().require(app1.id()).change().platform().get());
+ tester.controller().applications().requireInstance(app1.id().defaultInstance()).change().platform().get());
}
@Test
public void testsEachUpgradeCombinationWithFailingDeployments() {
DeploymentTester tester = new DeploymentTester();
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- Instance instance = tester.instance(application.id());
- Supplier<Instance> app = () -> tester.instance(application.id());
+ Instance instance = tester.defaultInstance(application.id());
+ Supplier<Instance> app = () -> tester.defaultInstance(application.id());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-central-1")
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
new file mode 100644
index 00000000000..5889b71bdd9
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -0,0 +1,250 @@
+// Copyright 2018 Yahoo Holdings. 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;
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.application.api.ValidationOverrides;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
+import com.yahoo.vespa.hosted.controller.application.Change;
+import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
+import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
+import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.application.DeploymentActivity;
+import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
+import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
+import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
+import com.yahoo.vespa.hosted.controller.application.JobStatus;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
+import com.yahoo.vespa.hosted.controller.rotation.RotationState;
+import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
+import org.junit.Test;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+import java.util.Set;
+
+import static com.yahoo.config.provision.SystemName.main;
+import static java.util.Optional.empty;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author bratseth
+ */
+
+public class ApplicationSerializerTest {
+
+ private static final ApplicationSerializer APPLICATION_SERIALIZER = new ApplicationSerializer();
+ private static final Path testData = Paths.get("src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/");
+ private static final ZoneId zone1 = ZoneId.from("prod", "us-west-1");
+ private static final ZoneId zone2 = ZoneId.from("prod", "us-east-3");
+
+ @Test
+ public void testSerialization() {
+ DeploymentSpec deploymentSpec = DeploymentSpec.fromXml("<deployment version='1.0'>" +
+ " <staging/>" +
+ "</deployment>");
+ ValidationOverrides validationOverrides = ValidationOverrides.fromXml("<validation-overrides version='1.0'>" +
+ " <allow until='2017-06-15'>deployment-removal</allow>" +
+ "</validation-overrides>");
+
+ OptionalLong projectId = OptionalLong.of(123L);
+
+ List<Deployment> deployments = new ArrayList<>();
+ ApplicationVersion applicationVersion1 = ApplicationVersion.from(new SourceRevision("repo1", "branch1", "commit1"), 31);
+ ApplicationVersion applicationVersion2 = ApplicationVersion
+ .from(new SourceRevision("repo1", "branch1", "commit1"), 32, "a@b",
+ Version.fromString("6.3.1"), Instant.ofEpochMilli(496));
+ Instant activityAt = Instant.parse("2018-06-01T10:15:30.00Z");
+ deployments.add(new Deployment(zone1, applicationVersion1, Version.fromString("1.2.3"), Instant.ofEpochMilli(3))); // One deployment without cluster info and utils
+ deployments.add(new Deployment(zone2, applicationVersion2, Version.fromString("1.2.3"), Instant.ofEpochMilli(5),
+ createClusterUtils(3, 0.2), createClusterInfo(3, 4),
+ new DeploymentMetrics(2, 3, 4, 5, 6,
+ Optional.of(Instant.now().truncatedTo(ChronoUnit.MILLIS)),
+ Map.of(DeploymentMetrics.Warning.all, 3)),
+ DeploymentActivity.create(Optional.of(activityAt), Optional.of(activityAt),
+ OptionalDouble.of(200), OptionalDouble.of(10))));
+
+ List<JobStatus> statusList = new ArrayList<>();
+
+ statusList.add(JobStatus.initial(JobType.systemTest)
+ .withTriggering(Version.fromString("5.6.7"), ApplicationVersion.unknown, empty(), "Test", Instant.ofEpochMilli(7))
+ .withCompletion(30, empty(), Instant.ofEpochMilli(8))
+ .withPause(OptionalLong.of(1L << 32)));
+ statusList.add(JobStatus.initial(JobType.stagingTest)
+ .withTriggering(Version.fromString("5.6.6"), ApplicationVersion.unknown, empty(), "Test 2", Instant.ofEpochMilli(5))
+ .withCompletion(11, Optional.of(JobError.unknown), Instant.ofEpochMilli(6)));
+ statusList.add(JobStatus.initial(JobType.from(main, zone1).get())
+ .withTriggering(Version.fromString("5.6.6"), ApplicationVersion.unknown, deployments.stream().findFirst(), "Test 3", Instant.ofEpochMilli(6))
+ .withCompletion(11, empty(), Instant.ofEpochMilli(7)));
+
+ DeploymentJobs deploymentJobs = new DeploymentJobs(OptionalLong.empty(), statusList, empty(), true);
+
+ var rotationStatus = RotationStatus.from(Map.of(new RotationId("my-rotation"),
+ Map.of(ZoneId.from("prod", "us-west-1"), RotationState.in,
+ ZoneId.from("prod", "us-east-3"), RotationState.out)));
+
+ ApplicationId id1 = ApplicationId.from("t1", "a1", "i1");
+ ApplicationId id3 = ApplicationId.from("t1", "a1", "i3");
+ List<Instance> instances = List.of(new Instance(id1,
+ deployments,
+ deploymentJobs,
+ List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of())),
+ rotationStatus),
+ new Instance(id3,
+ List.of(),
+ new DeploymentJobs(OptionalLong.empty(), List.of(), empty(), true),
+ List.of(),
+ RotationStatus.EMPTY));
+
+ Application original = new Application(TenantAndApplicationId.from(id1),
+ Instant.now().truncatedTo(ChronoUnit.MILLIS),
+ deploymentSpec,
+ validationOverrides,
+ Change.of(Version.fromString("6.7")).withPin(),
+ Change.of(ApplicationVersion.from(new SourceRevision("repo", "master", "deadcafe"), 42)),
+ Optional.of(IssueId.from("4321")),
+ Optional.of(IssueId.from("1234")),
+ Optional.of(User.from("by-username")),
+ OptionalInt.of(7),
+ new ApplicationMetrics(0.5, 0.9),
+ Optional.of("-----BEGIN PUBLIC KEY-----\n∠( ᐛ 」∠)_\n-----END PUBLIC KEY-----"),
+ projectId,
+ true,
+ instances);
+
+ Application serialized = APPLICATION_SERIALIZER.fromSlime(APPLICATION_SERIALIZER.toSlime(original));
+
+ assertEquals(original.id(), serialized.id());
+ assertEquals(original.createdAt(), serialized.createdAt());
+
+ assertEquals(original.deploymentSpec().xmlForm(), serialized.deploymentSpec().xmlForm());
+ assertEquals(original.validationOverrides().xmlForm(), serialized.validationOverrides().xmlForm());
+
+ assertEquals(original.projectId(), serialized.projectId());
+ assertEquals(original.internal(), serialized.internal());
+ assertEquals(original.deploymentIssueId(), serialized.deploymentIssueId());
+
+ assertEquals(0, serialized.require(id3.instance()).deployments().size());
+ assertEquals(0, serialized.require(id3.instance()).deploymentJobs().jobStatus().size());
+ assertEquals(0, serialized.require(id3.instance()).rotations().size());
+ assertEquals(RotationStatus.EMPTY, serialized.require(id3.instance()).rotationStatus());
+
+ assertEquals(2, serialized.require(id1.instance()).deployments().size());
+ assertEquals(original.require(id1.instance()).deployments().get(zone1).applicationVersion(), serialized.require(id1.instance()).deployments().get(zone1).applicationVersion());
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).applicationVersion(), serialized.require(id1.instance()).deployments().get(zone2).applicationVersion());
+ assertEquals(original.require(id1.instance()).deployments().get(zone1).version(), serialized.require(id1.instance()).deployments().get(zone1).version());
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).version(), serialized.require(id1.instance()).deployments().get(zone2).version());
+ assertEquals(original.require(id1.instance()).deployments().get(zone1).at(), serialized.require(id1.instance()).deployments().get(zone1).at());
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).at(), serialized.require(id1.instance()).deployments().get(zone2).at());
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).activity().lastQueried().get(), serialized.require(id1.instance()).deployments().get(zone2).activity().lastQueried().get());
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).activity().lastWritten().get(), serialized.require(id1.instance()).deployments().get(zone2).activity().lastWritten().get());
+
+ assertEquals(original.require(id1.instance()).deploymentJobs().projectId(), serialized.require(id1.instance()).deploymentJobs().projectId());
+ assertEquals(original.require(id1.instance()).deploymentJobs().jobStatus().size(), serialized.require(id1.instance()).deploymentJobs().jobStatus().size());
+ assertEquals( original.require(id1.instance()).deploymentJobs().jobStatus().get(JobType.systemTest),
+ serialized.require(id1.instance()).deploymentJobs().jobStatus().get(JobType.systemTest));
+ assertEquals( original.require(id1.instance()).deploymentJobs().jobStatus().get(JobType.stagingTest),
+ serialized.require(id1.instance()).deploymentJobs().jobStatus().get(JobType.stagingTest));
+
+ assertEquals(original.outstandingChange(), serialized.outstandingChange());
+
+ assertEquals(original.ownershipIssueId(), serialized.ownershipIssueId());
+ assertEquals(original.owner(), serialized.owner());
+ assertEquals(original.majorVersion(), serialized.majorVersion());
+ assertEquals(original.change(), serialized.change());
+ assertEquals(original.pemDeployKey(), serialized.pemDeployKey());
+
+ assertEquals(original.require(id1.instance()).rotations(), serialized.require(id1.instance()).rotations());
+ assertEquals(original.require(id1.instance()).rotationStatus(), serialized.require(id1.instance()).rotationStatus());
+
+ // Test cluster utilization
+ assertEquals(0, serialized.require(id1.instance()).deployments().get(zone1).clusterUtils().size());
+ assertEquals(3, serialized.require(id1.instance()).deployments().get(zone2).clusterUtils().size());
+ assertEquals(0.4, serialized.require(id1.instance()).deployments().get(zone2).clusterUtils().get(ClusterSpec.Id.from("id2")).getCpu(), 0.01);
+ assertEquals(0.2, serialized.require(id1.instance()).deployments().get(zone2).clusterUtils().get(ClusterSpec.Id.from("id1")).getCpu(), 0.01);
+ assertEquals(0.2, serialized.require(id1.instance()).deployments().get(zone2).clusterUtils().get(ClusterSpec.Id.from("id1")).getMemory(), 0.01);
+
+ // Test cluster info
+ assertEquals(3, serialized.require(id1.instance()).deployments().get(zone2).clusterInfo().size());
+ assertEquals(10, serialized.require(id1.instance()).deployments().get(zone2).clusterInfo().get(ClusterSpec.Id.from("id2")).getFlavorCost());
+ assertEquals(ClusterSpec.Type.content, serialized.require(id1.instance()).deployments().get(zone2).clusterInfo().get(ClusterSpec.Id.from("id2")).getClusterType());
+ assertEquals("flavor2", serialized.require(id1.instance()).deployments().get(zone2).clusterInfo().get(ClusterSpec.Id.from("id2")).getFlavor());
+ assertEquals(4, serialized.require(id1.instance()).deployments().get(zone2).clusterInfo().get(ClusterSpec.Id.from("id2")).getHostnames().size());
+ assertEquals(2, serialized.require(id1.instance()).deployments().get(zone2).clusterInfo().get(ClusterSpec.Id.from("id2")).getFlavorCPU(), Double.MIN_VALUE);
+ assertEquals(4, serialized.require(id1.instance()).deployments().get(zone2).clusterInfo().get(ClusterSpec.Id.from("id2")).getFlavorMem(), Double.MIN_VALUE);
+ assertEquals(50, serialized.require(id1.instance()).deployments().get(zone2).clusterInfo().get(ClusterSpec.Id.from("id2")).getFlavorDisk(), Double.MIN_VALUE);
+
+ // Test metrics
+ assertEquals(original.metrics().queryServiceQuality(), serialized.metrics().queryServiceQuality(), Double.MIN_VALUE);
+ assertEquals(original.metrics().writeServiceQuality(), serialized.metrics().writeServiceQuality(), Double.MIN_VALUE);
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).metrics().queriesPerSecond(), serialized.require(id1.instance()).deployments().get(zone2).metrics().queriesPerSecond(), Double.MIN_VALUE);
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).metrics().writesPerSecond(), serialized.require(id1.instance()).deployments().get(zone2).metrics().writesPerSecond(), Double.MIN_VALUE);
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).metrics().documentCount(), serialized.require(id1.instance()).deployments().get(zone2).metrics().documentCount(), Double.MIN_VALUE);
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).metrics().queryLatencyMillis(), serialized.require(id1.instance()).deployments().get(zone2).metrics().queryLatencyMillis(), Double.MIN_VALUE);
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).metrics().writeLatencyMillis(), serialized.require(id1.instance()).deployments().get(zone2).metrics().writeLatencyMillis(), Double.MIN_VALUE);
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).metrics().instant(), serialized.require(id1.instance()).deployments().get(zone2).metrics().instant());
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).metrics().warnings(), serialized.require(id1.instance()).deployments().get(zone2).metrics().warnings());
+ }
+
+ private Map<ClusterSpec.Id, ClusterInfo> createClusterInfo(int clusters, int hosts) {
+ Map<ClusterSpec.Id, ClusterInfo> result = new HashMap<>();
+
+ for (int cluster = 0; cluster < clusters; cluster++) {
+ List<String> hostnames = new ArrayList<>();
+ for (int host = 0; host < hosts; host++) {
+ hostnames.add("hostname" + cluster*host + host);
+ }
+
+ result.put(ClusterSpec.Id.from("id" + cluster), new ClusterInfo("flavor" + cluster, 10,
+ 2, 4, 50, ClusterSpec.Type.content, hostnames));
+ }
+ return result;
+ }
+
+ private Map<ClusterSpec.Id, ClusterUtilization> createClusterUtils(int clusters, double inc) {
+ Map<ClusterSpec.Id, ClusterUtilization> result = new HashMap<>();
+
+ ClusterUtilization util = new ClusterUtilization(0,0,0,0);
+ for (int cluster = 0; cluster < clusters; cluster++) {
+ double agg = cluster*inc;
+ result.put(ClusterSpec.Id.from("id" + cluster), new ClusterUtilization(
+ util.getMemory()+ agg,
+ util.getCpu()+ agg,
+ util.getDisk() + agg,
+ util.getDiskBusy() + agg));
+ }
+ return result;
+ }
+
+ @Test
+ public void testCompleteApplicationDeserialization() throws Exception {
+ byte[] applicationJson = Files.readAllBytes(testData.resolve("complete-application.json"));
+ APPLICATION_SERIALIZER.fromSlime(SlimeUtils.jsonToSlime(applicationJson));
+ // ok if no error
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/InstanceSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/InstanceSerializerTest.java
index 2d41d50dba3..3bb5e3d0c2b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/InstanceSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/InstanceSerializerTest.java
@@ -46,7 +46,6 @@ import java.util.OptionalLong;
import java.util.Set;
import static com.yahoo.config.provision.SystemName.main;
-import static com.yahoo.vespa.hosted.controller.ControllerTester.writable;
import static java.util.Optional.empty;
import static org.junit.Assert.assertEquals;
@@ -216,7 +215,7 @@ public class InstanceSerializerTest {
@Test
public void testCompleteApplicationDeserialization() throws Exception {
- byte[] applicationJson = Files.readAllBytes(testData.resolve("complete-application.json"));
+ byte[] applicationJson = Files.readAllBytes(testData.resolve("complete-instance.json"));
INSTANCE_SERIALIZER.fromSlime(SlimeUtils.jsonToSlime(applicationJson));
// ok if no error
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-application.json
index 28f505e88ec..9e3bb2beb3b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-application.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-application.json
@@ -1,299 +1,34 @@
{
- "id": "tenant1:app1:default",
+ "id": "tenant1:app1",
+ "internal": true,
+ "deploymentIssueId": "321",
"deploymentSpecField": "<deployment version='1.0'>\n <test />\n <!--<staging />-->\n <prod global-service-id=\"foo\">\n <region active=\"true\">us-east-3</region>\n <region active=\"true\">us-west-1</region>\n </prod>\n</deployment>\n",
"validationOverrides": "<validation-overrides>\n <allow until=\"2016-04-28\" comment=\"Renaming content cluster\">content-cluster-removal</allow>\n <allow until=\"2016-08-22\" comment=\"Migrating us-east-3 to C-2E\">cluster-size-reduction</allow>\n <allow until=\"2017-06-30\" comment=\"Test Vespa upgrade tests\">force-automatic-tenant-upgrade-test</allow>\n</validation-overrides>\n",
- "deployments": [
- {
- "zone": {
- "environment": "prod",
- "region": "us-west-1"
- },
- "version": "6.173.62",
- "deployTime": 1510837817704,
- "applicationPackageRevision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
- }
- },
- "clusterInfo": {
- "cluster1": {
- "flavor": "d-3-16-100",
- "cost": 9,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "container",
- "hostnames": [
- "node1",
- "node2"
- ]
- },
- "cluster2": {
- "flavor": "d-12-64-400",
- "cost": 38,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "content",
- "hostnames": [
- "node3",
- "node4",
- "node5"
- ]
- },
- "cluster3": {
- "flavor": "d-12-64-400",
- "cost": 38,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "content",
- "hostnames": [
- "node6",
- "node7",
- "node8",
- "node9"
- ]
- }
- },
- "clusterUtils": {
- "cluster1": {
- "cpu": 0.1720353499228221,
- "mem": 0.4986146831512451,
- "disk": 0.0617671330041831,
- "diskbusy": 0
- },
- "cluster2": {
- "cpu": 0.07505730001866318,
- "mem": 0.7936344432830811,
- "disk": 0.2260549694485994,
- "diskbusy": 0
- },
- "cluster3": {
- "cpu": 0.01712671480989384,
- "mem": 0.0225852754983035,
- "disk": 0.006084436856721915,
- "diskbusy": 0
- }
- },
- "metrics": {
- "queriesPerSecond": 1.25,
- "writesPerSecond": 43.83199977874756,
- "documentCount": 525880277.9999999,
- "queryLatencyMillis": 5.607503938674927,
- "writeLatencyMillis": 20.57866265104621
- }
- },
- {
- "zone": {
- "environment": "test",
- "region": "us-east-1"
- },
- "version": "6.173.62",
- "deployTime": 1511256872316,
- "applicationPackageRevision": {
- "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
- }
- },
- "clusterInfo": {},
- "clusterUtils": {},
- "metrics": {
- "queriesPerSecond": 0,
- "writesPerSecond": 0,
- "documentCount": 0,
- "queryLatencyMillis": 0,
- "writeLatencyMillis": 0
- }
- },
- {
- "zone": {
- "environment": "dev",
- "region": "us-east-1"
- },
- "version": "6.173.62",
- "deployTime": 1510597489464,
- "applicationPackageRevision": {
- "applicationPackageHash": "59b883f263c2a3c23dfab249730097d7e0e1ed32"
- },
- "clusterInfo": {
- "cluster1": {
- "flavor": "d-2-8-50",
- "cost": 5,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "container",
- "hostnames": [
- "node1"
- ]
- },
- "cluster2": {
- "flavor": "d-2-8-50",
- "cost": 5,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "content",
- "hostnames": [
- "node2"
- ]
- },
- "cluster3": {
- "flavor": "d-2-8-50",
- "cost": 5,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "content",
- "hostnames": [
- "node3"
- ]
- }
- },
- "clusterUtils": {
- "cluster1": {
- "cpu": 0.191833330678661,
- "mem": 0.4625738318415235,
- "disk": 0.05582004563850269,
- "diskbusy": 0
- },
- "cluster2": {
- "cpu": 0.2227037978608054,
- "mem": 0.2051752598416401,
- "disk": 0.05471533698695047,
- "diskbusy": 0
- },
- "cluster3": {
- "cpu": 0.1869410834020498,
- "mem": 0.1691722576000564,
- "disk": 0.04977374774258153,
- "diskbusy": 0
- }
- },
- "metrics": {
- "queriesPerSecond": 0,
- "writesPerSecond": 0,
- "documentCount": 30916,
- "queryLatencyMillis": 0,
- "writeLatencyMillis": 0
- }
- },
- {
- "zone": {
- "environment": "prod",
- "region": "us-east-3"
- },
- "version": "6.173.62",
- "deployTime": 1510817190016,
- "applicationPackageRevision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
- }
- },
- "clusterInfo": {
- "cluster1": {
- "flavor": "d-3-16-100",
- "cost": 9,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "container",
- "hostnames": [
- "node1",
- "node2"
- ]
- },
- "cluster2": {
- "flavor": "d-12-64-400",
- "cost": 38,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "content",
- "hostnames": [
- "node1",
- "node2",
- "node3"
- ]
- },
- "cluster3": {
- "flavor": "d-12-64-400",
- "cost": 38,
- "flavorCpu": 0,
- "flavorMem": 0,
- "flavorDisk": 0,
- "clusterType": "content",
- "hostnames": [
- "node1",
- "node2",
- "node3",
- "node4"
- ]
- }
- },
- "clusterUtils": {
- "cluster1": {
- "cpu": 0.2295038983007097,
- "mem": 0.4627357390237263,
- "disk": 0.05559941525894966,
- "diskbusy": 0
- },
- "cluster2": {
- "cpu": 0.05340429087579549,
- "mem": 0.8107630891552372,
- "disk": 0.226444914138854,
- "diskbusy": 0
- },
- "cluster3": {
- "cpu": 0.02148227413975218,
- "mem": 0.02162174219104161,
- "disk": 0.006057760545243265,
- "diskbusy": 0
- }
- },
- "metrics": {
- "queriesPerSecond": 1.734000012278557,
- "writesPerSecond": 44.59999895095825,
- "documentCount": 525868193.9999999,
- "queryLatencyMillis": 5.65284947195106,
- "writeLatencyMillis": 17.34593812832452
- }
+ "projectId": 102889,
+ "deployingField": {
+ "buildNumber": 42,
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
}
- ],
- "deploymentJobs": {
- "projectId": 102889,
- "jobStatus": [
- {
- "jobType": "staging-test",
- "lastTriggered": {
- "id": -1,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
- }
+ },
+ "outstandingChangeField": false,
+ "queryQuality": 100,
+ "writeQuality": 99.99894341115082,
+ "pemDeployKey": "-----BEGIN PUBLIC KEY-----\n∠( ᐛ 」∠)_\n-----END PUBLIC KEY-----",
+ "instances": [
+ {
+ "instanceName": "default",
+ "deployments": [
+ {
+ "zone": {
+ "environment": "prod",
+ "region": "us-west-1"
},
- "upgrade": true,
- "reason": "system-test completed",
- "at": 1510830134259
- },
- "lastCompleted": {
- "id": 1184,
"version": "6.173.62",
- "revision": {
+ "deployTime": 1510837817704,
+ "applicationPackageRevision": {
"applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
"sourceRevision": {
"repositoryField": "git@git.host:user/repo.git",
@@ -301,112 +36,181 @@
"commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
}
},
- "upgrade": true,
- "reason": "system-test completed",
- "at": 1510830684960
- },
- "lastSuccess": {
- "id": 1184,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ "clusterInfo": {
+ "cluster1": {
+ "flavor": "d-3-16-100",
+ "cost": 9,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "container",
+ "hostnames": [
+ "node1",
+ "node2"
+ ]
+ },
+ "cluster2": {
+ "flavor": "d-12-64-400",
+ "cost": 38,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node3",
+ "node4",
+ "node5"
+ ]
+ },
+ "cluster3": {
+ "flavor": "d-12-64-400",
+ "cost": 38,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node6",
+ "node7",
+ "node8",
+ "node9"
+ ]
}
},
- "upgrade": true,
- "reason": "system-test completed",
- "at": 1510830684960
- }
- },
- {
- "jobType": "component",
- "lastCompleted": {
- "id": 849,
- "version": "6.174.156",
- "upgrade": false,
- "reason": "Application commit",
- "at": 1511217733555
- },
- "lastSuccess": {
- "id": 849,
- "version": "6.174.156",
- "upgrade": false,
- "reason": "Application commit",
- "at": 1511217733555
- }
- },
- {
- "jobType": "production-us-east-3",
- "lastTriggered": {
- "id": -1,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ "clusterUtils": {
+ "cluster1": {
+ "cpu": 0.1720353499228221,
+ "mem": 0.4986146831512451,
+ "disk": 0.0617671330041831,
+ "diskbusy": 0
+ },
+ "cluster2": {
+ "cpu": 0.07505730001866318,
+ "mem": 0.7936344432830811,
+ "disk": 0.2260549694485994,
+ "diskbusy": 0
+ },
+ "cluster3": {
+ "cpu": 0.01712671480989384,
+ "mem": 0.0225852754983035,
+ "disk": 0.006084436856721915,
+ "diskbusy": 0
}
},
- "upgrade": true,
- "reason": "staging-test completed",
- "at": 1510830685127
+ "metrics": {
+ "queriesPerSecond": 1.25,
+ "writesPerSecond": 43.83199977874756,
+ "documentCount": 525880277.9999999,
+ "queryLatencyMillis": 5.607503938674927,
+ "writeLatencyMillis": 20.57866265104621
+ }
},
- "lastCompleted": {
- "id": 923,
+ {
+ "zone": {
+ "environment": "test",
+ "region": "us-east-1"
+ },
"version": "6.173.62",
- "revision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "deployTime": 1511256872316,
+ "applicationPackageRevision": {
+ "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
"sourceRevision": {
"repositoryField": "git@git.host:user/repo.git",
"branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
}
},
- "upgrade": true,
- "reason": "staging-test completed",
- "at": 1510837650046
+ "clusterInfo": {},
+ "clusterUtils": {},
+ "metrics": {
+ "queriesPerSecond": 0,
+ "writesPerSecond": 0,
+ "documentCount": 0,
+ "queryLatencyMillis": 0,
+ "writeLatencyMillis": 0
+ }
},
- "lastSuccess": {
- "id": 923,
+ {
+ "zone": {
+ "environment": "dev",
+ "region": "us-east-1"
+ },
"version": "6.173.62",
- "revision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ "deployTime": 1510597489464,
+ "applicationPackageRevision": {
+ "applicationPackageHash": "59b883f263c2a3c23dfab249730097d7e0e1ed32"
+ },
+ "clusterInfo": {
+ "cluster1": {
+ "flavor": "d-2-8-50",
+ "cost": 5,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "container",
+ "hostnames": [
+ "node1"
+ ]
+ },
+ "cluster2": {
+ "flavor": "d-2-8-50",
+ "cost": 5,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node2"
+ ]
+ },
+ "cluster3": {
+ "flavor": "d-2-8-50",
+ "cost": 5,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node3"
+ ]
}
},
- "upgrade": true,
- "reason": "staging-test completed",
- "at": 1510837650046
- }
- },
- {
- "jobType": "production-us-west-1",
- "lastTriggered": {
- "id": -1,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ "clusterUtils": {
+ "cluster1": {
+ "cpu": 0.191833330678661,
+ "mem": 0.4625738318415235,
+ "disk": 0.05582004563850269,
+ "diskbusy": 0
+ },
+ "cluster2": {
+ "cpu": 0.2227037978608054,
+ "mem": 0.2051752598416401,
+ "disk": 0.05471533698695047,
+ "diskbusy": 0
+ },
+ "cluster3": {
+ "cpu": 0.1869410834020498,
+ "mem": 0.1691722576000564,
+ "disk": 0.04977374774258153,
+ "diskbusy": 0
}
},
- "upgrade": true,
- "reason": "production-us-east-3 completed",
- "at": 1510837650139
+ "metrics": {
+ "queriesPerSecond": 0,
+ "writesPerSecond": 0,
+ "documentCount": 30916,
+ "queryLatencyMillis": 0,
+ "writeLatencyMillis": 0
+ }
},
- "lastCompleted": {
- "id": 646,
+ {
+ "zone": {
+ "environment": "prod",
+ "region": "us-east-3"
+ },
"version": "6.173.62",
- "revision": {
+ "deployTime": 1510817190016,
+ "applicationPackageRevision": {
"applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
"sourceRevision": {
"repositoryField": "git@git.host:user/repo.git",
@@ -414,121 +218,333 @@
"commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
}
},
- "upgrade": true,
- "reason": "production-us-east-3 completed",
- "at": 1510843559162
- },
- "lastSuccess": {
- "id": 646,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ "clusterInfo": {
+ "cluster1": {
+ "flavor": "d-3-16-100",
+ "cost": 9,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "container",
+ "hostnames": [
+ "node1",
+ "node2"
+ ]
+ },
+ "cluster2": {
+ "flavor": "d-12-64-400",
+ "cost": 38,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node1",
+ "node2",
+ "node3"
+ ]
+ },
+ "cluster3": {
+ "flavor": "d-12-64-400",
+ "cost": 38,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node1",
+ "node2",
+ "node3",
+ "node4"
+ ]
}
},
- "upgrade": true,
- "reason": "production-us-east-3 completed",
- "at": 1510843559162
+ "clusterUtils": {
+ "cluster1": {
+ "cpu": 0.2295038983007097,
+ "mem": 0.4627357390237263,
+ "disk": 0.05559941525894966,
+ "diskbusy": 0
+ },
+ "cluster2": {
+ "cpu": 0.05340429087579549,
+ "mem": 0.8107630891552372,
+ "disk": 0.226444914138854,
+ "diskbusy": 0
+ },
+ "cluster3": {
+ "cpu": 0.02148227413975218,
+ "mem": 0.02162174219104161,
+ "disk": 0.006057760545243265,
+ "diskbusy": 0
+ }
+ },
+ "metrics": {
+ "queriesPerSecond": 1.734000012278557,
+ "writesPerSecond": 44.59999895095825,
+ "documentCount": 525868193.9999999,
+ "queryLatencyMillis": 5.65284947195106,
+ "writeLatencyMillis": 17.34593812832452
+ }
}
- },
- {
- "jobType": "system-test",
- "jobError": "unknown",
- "lastTriggered": {
- "id": -1,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ ],
+ "deploymentJobs": {
+ "jobStatus": [
+ {
+ "jobType": "staging-test",
+ "lastTriggered": {
+ "id": -1,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "system-test completed",
+ "at": 1510830134259
+ },
+ "lastCompleted": {
+ "id": 1184,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "system-test completed",
+ "at": 1510830684960
+ },
+ "lastSuccess": {
+ "id": 1184,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "system-test completed",
+ "at": 1510830684960
}
},
- "upgrade": false,
- "reason": "Available change in component",
- "at": 1511256608649
- },
- "lastCompleted": {
- "id": 1686,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ {
+ "jobType": "component",
+ "lastCompleted": {
+ "id": 849,
+ "version": "6.174.156",
+ "upgrade": false,
+ "reason": "Application commit",
+ "at": 1511217733555
+ },
+ "lastSuccess": {
+ "id": 849,
+ "version": "6.174.156",
+ "upgrade": false,
+ "reason": "Application commit",
+ "at": 1511217733555
}
},
- "upgrade": false,
- "reason": "Available change in component",
- "at": 1511256603353
- },
- "firstFailing": {
- "id": 1659,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ {
+ "jobType": "production-us-east-3",
+ "lastTriggered": {
+ "id": -1,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "staging-test completed",
+ "at": 1510830685127
+ },
+ "lastCompleted": {
+ "id": 923,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "staging-test completed",
+ "at": 1510837650046
+ },
+ "lastSuccess": {
+ "id": 923,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "staging-test completed",
+ "at": 1510837650046
}
},
- "upgrade": false,
- "reason": "component completed",
- "at": 1511219070725
- },
- "lastSuccess": {
- "id": 1658,
- "version": "6.173.62",
- "revision": {
- "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ {
+ "jobType": "production-us-west-1",
+ "lastTriggered": {
+ "id": -1,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "production-us-east-3 completed",
+ "at": 1510837650139
+ },
+ "lastCompleted": {
+ "id": 646,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "production-us-east-3 completed",
+ "at": 1510843559162
+ },
+ "lastSuccess": {
+ "id": 646,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "production-us-east-3 completed",
+ "at": 1510843559162
}
},
- "upgrade": true,
- "reason": "Upgrading to 6.173.62",
- "at": 1511175754163
+ {
+ "jobType": "system-test",
+ "jobError": "unknown",
+ "lastTriggered": {
+ "id": -1,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ }
+ },
+ "upgrade": false,
+ "reason": "Available change in component",
+ "at": 1511256608649
+ },
+ "lastCompleted": {
+ "id": 1686,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ }
+ },
+ "upgrade": false,
+ "reason": "Available change in component",
+ "at": 1511256603353
+ },
+ "firstFailing": {
+ "id": 1659,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ }
+ },
+ "upgrade": false,
+ "reason": "component completed",
+ "at": 1511219070725
+ },
+ "lastSuccess": {
+ "id": 1658,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "Upgrading to 6.173.62",
+ "at": 1511175754163
+ }
+ }
+ ]
+ },
+ "assignedRotations": [
+ {
+ "rotationId": "rotation-foo",
+ "clusterId": "qrs",
+ "endpointId": "default"
}
- }
- ]
- },
- "deployingField": {
- "buildNumber": 42,
- "sourceRevision": {
- "repositoryField": "git@git.host:user/repo.git",
- "branchField": "origin/master",
- "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
- }
- },
- "outstandingChangeField": false,
- "queryQuality": 100,
- "writeQuality": 99.99894341115082,
- "pemDeployKey": "-----BEGIN PUBLIC KEY-----\n∠( ᐛ 」∠)_\n-----END PUBLIC KEY-----",
- "assignedRotations": [
- {
- "rotationId": "rotation-foo",
- "clusterId": "qrs",
- "endpointId": "default"
- }
- ],
- "rotationStatus2": [
- {
- "rotationId": "rotation-foo",
- "status": [
+ ],
+ "rotationStatus2": [
{
- "environment": "prod",
- "region": "us-east-3",
- "state": "in"
+ "rotationId": "rotation-foo",
+ "status": [
+ {
+ "environment": "prod",
+ "region": "us-east-3",
+ "state": "in"
+ }
+ ]
}
]
+ },
+ {
+ "instanceName": "empty-instance",
+ "deployments": [],
+ "deploymentJobs": {
+ "jobStatus": []
+ },
+ "assignedRotations": [],
+ "rotationStatus2": []
}
]
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-instance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-instance.json
new file mode 100644
index 00000000000..28f505e88ec
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/complete-instance.json
@@ -0,0 +1,534 @@
+{
+ "id": "tenant1:app1:default",
+ "deploymentSpecField": "<deployment version='1.0'>\n <test />\n <!--<staging />-->\n <prod global-service-id=\"foo\">\n <region active=\"true\">us-east-3</region>\n <region active=\"true\">us-west-1</region>\n </prod>\n</deployment>\n",
+ "validationOverrides": "<validation-overrides>\n <allow until=\"2016-04-28\" comment=\"Renaming content cluster\">content-cluster-removal</allow>\n <allow until=\"2016-08-22\" comment=\"Migrating us-east-3 to C-2E\">cluster-size-reduction</allow>\n <allow until=\"2017-06-30\" comment=\"Test Vespa upgrade tests\">force-automatic-tenant-upgrade-test</allow>\n</validation-overrides>\n",
+ "deployments": [
+ {
+ "zone": {
+ "environment": "prod",
+ "region": "us-west-1"
+ },
+ "version": "6.173.62",
+ "deployTime": 1510837817704,
+ "applicationPackageRevision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "clusterInfo": {
+ "cluster1": {
+ "flavor": "d-3-16-100",
+ "cost": 9,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "container",
+ "hostnames": [
+ "node1",
+ "node2"
+ ]
+ },
+ "cluster2": {
+ "flavor": "d-12-64-400",
+ "cost": 38,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node3",
+ "node4",
+ "node5"
+ ]
+ },
+ "cluster3": {
+ "flavor": "d-12-64-400",
+ "cost": 38,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node6",
+ "node7",
+ "node8",
+ "node9"
+ ]
+ }
+ },
+ "clusterUtils": {
+ "cluster1": {
+ "cpu": 0.1720353499228221,
+ "mem": 0.4986146831512451,
+ "disk": 0.0617671330041831,
+ "diskbusy": 0
+ },
+ "cluster2": {
+ "cpu": 0.07505730001866318,
+ "mem": 0.7936344432830811,
+ "disk": 0.2260549694485994,
+ "diskbusy": 0
+ },
+ "cluster3": {
+ "cpu": 0.01712671480989384,
+ "mem": 0.0225852754983035,
+ "disk": 0.006084436856721915,
+ "diskbusy": 0
+ }
+ },
+ "metrics": {
+ "queriesPerSecond": 1.25,
+ "writesPerSecond": 43.83199977874756,
+ "documentCount": 525880277.9999999,
+ "queryLatencyMillis": 5.607503938674927,
+ "writeLatencyMillis": 20.57866265104621
+ }
+ },
+ {
+ "zone": {
+ "environment": "test",
+ "region": "us-east-1"
+ },
+ "version": "6.173.62",
+ "deployTime": 1511256872316,
+ "applicationPackageRevision": {
+ "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ }
+ },
+ "clusterInfo": {},
+ "clusterUtils": {},
+ "metrics": {
+ "queriesPerSecond": 0,
+ "writesPerSecond": 0,
+ "documentCount": 0,
+ "queryLatencyMillis": 0,
+ "writeLatencyMillis": 0
+ }
+ },
+ {
+ "zone": {
+ "environment": "dev",
+ "region": "us-east-1"
+ },
+ "version": "6.173.62",
+ "deployTime": 1510597489464,
+ "applicationPackageRevision": {
+ "applicationPackageHash": "59b883f263c2a3c23dfab249730097d7e0e1ed32"
+ },
+ "clusterInfo": {
+ "cluster1": {
+ "flavor": "d-2-8-50",
+ "cost": 5,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "container",
+ "hostnames": [
+ "node1"
+ ]
+ },
+ "cluster2": {
+ "flavor": "d-2-8-50",
+ "cost": 5,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node2"
+ ]
+ },
+ "cluster3": {
+ "flavor": "d-2-8-50",
+ "cost": 5,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node3"
+ ]
+ }
+ },
+ "clusterUtils": {
+ "cluster1": {
+ "cpu": 0.191833330678661,
+ "mem": 0.4625738318415235,
+ "disk": 0.05582004563850269,
+ "diskbusy": 0
+ },
+ "cluster2": {
+ "cpu": 0.2227037978608054,
+ "mem": 0.2051752598416401,
+ "disk": 0.05471533698695047,
+ "diskbusy": 0
+ },
+ "cluster3": {
+ "cpu": 0.1869410834020498,
+ "mem": 0.1691722576000564,
+ "disk": 0.04977374774258153,
+ "diskbusy": 0
+ }
+ },
+ "metrics": {
+ "queriesPerSecond": 0,
+ "writesPerSecond": 0,
+ "documentCount": 30916,
+ "queryLatencyMillis": 0,
+ "writeLatencyMillis": 0
+ }
+ },
+ {
+ "zone": {
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ "version": "6.173.62",
+ "deployTime": 1510817190016,
+ "applicationPackageRevision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "clusterInfo": {
+ "cluster1": {
+ "flavor": "d-3-16-100",
+ "cost": 9,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "container",
+ "hostnames": [
+ "node1",
+ "node2"
+ ]
+ },
+ "cluster2": {
+ "flavor": "d-12-64-400",
+ "cost": 38,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node1",
+ "node2",
+ "node3"
+ ]
+ },
+ "cluster3": {
+ "flavor": "d-12-64-400",
+ "cost": 38,
+ "flavorCpu": 0,
+ "flavorMem": 0,
+ "flavorDisk": 0,
+ "clusterType": "content",
+ "hostnames": [
+ "node1",
+ "node2",
+ "node3",
+ "node4"
+ ]
+ }
+ },
+ "clusterUtils": {
+ "cluster1": {
+ "cpu": 0.2295038983007097,
+ "mem": 0.4627357390237263,
+ "disk": 0.05559941525894966,
+ "diskbusy": 0
+ },
+ "cluster2": {
+ "cpu": 0.05340429087579549,
+ "mem": 0.8107630891552372,
+ "disk": 0.226444914138854,
+ "diskbusy": 0
+ },
+ "cluster3": {
+ "cpu": 0.02148227413975218,
+ "mem": 0.02162174219104161,
+ "disk": 0.006057760545243265,
+ "diskbusy": 0
+ }
+ },
+ "metrics": {
+ "queriesPerSecond": 1.734000012278557,
+ "writesPerSecond": 44.59999895095825,
+ "documentCount": 525868193.9999999,
+ "queryLatencyMillis": 5.65284947195106,
+ "writeLatencyMillis": 17.34593812832452
+ }
+ }
+ ],
+ "deploymentJobs": {
+ "projectId": 102889,
+ "jobStatus": [
+ {
+ "jobType": "staging-test",
+ "lastTriggered": {
+ "id": -1,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "system-test completed",
+ "at": 1510830134259
+ },
+ "lastCompleted": {
+ "id": 1184,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "system-test completed",
+ "at": 1510830684960
+ },
+ "lastSuccess": {
+ "id": 1184,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "system-test completed",
+ "at": 1510830684960
+ }
+ },
+ {
+ "jobType": "component",
+ "lastCompleted": {
+ "id": 849,
+ "version": "6.174.156",
+ "upgrade": false,
+ "reason": "Application commit",
+ "at": 1511217733555
+ },
+ "lastSuccess": {
+ "id": 849,
+ "version": "6.174.156",
+ "upgrade": false,
+ "reason": "Application commit",
+ "at": 1511217733555
+ }
+ },
+ {
+ "jobType": "production-us-east-3",
+ "lastTriggered": {
+ "id": -1,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "staging-test completed",
+ "at": 1510830685127
+ },
+ "lastCompleted": {
+ "id": 923,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "staging-test completed",
+ "at": 1510837650046
+ },
+ "lastSuccess": {
+ "id": 923,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "staging-test completed",
+ "at": 1510837650046
+ }
+ },
+ {
+ "jobType": "production-us-west-1",
+ "lastTriggered": {
+ "id": -1,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "production-us-east-3 completed",
+ "at": 1510837650139
+ },
+ "lastCompleted": {
+ "id": 646,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "production-us-east-3 completed",
+ "at": 1510843559162
+ },
+ "lastSuccess": {
+ "id": 646,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "production-us-east-3 completed",
+ "at": 1510843559162
+ }
+ },
+ {
+ "jobType": "system-test",
+ "jobError": "unknown",
+ "lastTriggered": {
+ "id": -1,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ }
+ },
+ "upgrade": false,
+ "reason": "Available change in component",
+ "at": 1511256608649
+ },
+ "lastCompleted": {
+ "id": 1686,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ }
+ },
+ "upgrade": false,
+ "reason": "Available change in component",
+ "at": 1511256603353
+ },
+ "firstFailing": {
+ "id": 1659,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "ec548fa61cbfab7a270a51d46b1263ec1be5d9a8",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ }
+ },
+ "upgrade": false,
+ "reason": "component completed",
+ "at": 1511219070725
+ },
+ "lastSuccess": {
+ "id": 1658,
+ "version": "6.173.62",
+ "revision": {
+ "applicationPackageHash": "9db423e1021d7b452d37ec6372bc757d9c1bda87",
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "49cd7bbb1ed9f4b922083cb042590b0885ffe22b"
+ }
+ },
+ "upgrade": true,
+ "reason": "Upgrading to 6.173.62",
+ "at": 1511175754163
+ }
+ }
+ ]
+ },
+ "deployingField": {
+ "buildNumber": 42,
+ "sourceRevision": {
+ "repositoryField": "git@git.host:user/repo.git",
+ "branchField": "origin/master",
+ "commitField": "234f3e4e77049d0b9538c9e1b356d17eb1dedb6a"
+ }
+ },
+ "outstandingChangeField": false,
+ "queryQuality": 100,
+ "writeQuality": 99.99894341115082,
+ "pemDeployKey": "-----BEGIN PUBLIC KEY-----\n∠( ᐛ 」∠)_\n-----END PUBLIC KEY-----",
+ "assignedRotations": [
+ {
+ "rotationId": "rotation-foo",
+ "clusterId": "qrs",
+ "endpointId": "default"
+ }
+ ],
+ "rotationStatus2": [
+ {
+ "rotationId": "rotation-foo",
+ "status": [
+ {
+ "environment": "prod",
+ "region": "us-east-3",
+ "state": "in"
+ }
+ ]
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
index 290969623d8..2d8c937097a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
@@ -22,6 +22,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockBuildService;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
@@ -94,16 +95,17 @@ public class ContainerControllerTester {
.uploadArtifact(applicationPackage)
.submit();
DeploymentSteps steps = controller().applications().deploymentTrigger().steps(applicationPackage.deploymentSpec());
+ // TODO jonmv: Connect instances from deployment spec to deployments below.
boolean succeeding = true;
for (var job : steps.jobs()) {
if (!succeeding) return;
var zone = job.zone(controller().system());
- deploy(application.id(), applicationPackage, zone);
+ deploy(application.id().defaultInstance(), applicationPackage, zone);
if (failStaging && zone.environment() == Environment.staging) {
succeeding = false;
}
if (zone.environment().isTest()) {
- controller().applications().deactivate(application.id(), zone);
+ controller().applications().deactivate(application.id().defaultInstance(), zone);
}
jobCompletion(job).application(application).success(succeeding).projectId(projectId).submit();
}
@@ -127,7 +129,7 @@ public class ContainerControllerTester {
/*
* Authorize action on tenantDomain/application for a given screwdriverId
*/
- public void authorize(AthenzDomain tenantDomain, ScrewdriverId screwdriverId, ApplicationAction action, ApplicationId id) {
+ public void authorize(AthenzDomain tenantDomain, ScrewdriverId screwdriverId, ApplicationAction action, TenantAndApplicationId id) {
AthenzClientFactoryMock mock = (AthenzClientFactoryMock) containerTester.container().components()
.getComponent(AthenzClientFactoryMock.class.getName());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 7158c293d7a..836e3c07763 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -50,6 +50,7 @@ import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
@@ -319,7 +320,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(app2.application().value()));
// Trigger upgrade and then application change
- controllerTester.controller().applications().deploymentTrigger().triggerChange(app2, Change.of(Version.fromString("7.0")));
+ controllerTester.controller().applications().deploymentTrigger().triggerChange(TenantAndApplicationId.from(app2), Change.of(Version.fromString("7.0")));
controllerTester.jobCompletion(JobType.component)
.application(app2)
@@ -398,7 +399,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
.userIdentity(USER_ID),
new File("deployment.json"));
- addIssues(controllerTester, ApplicationId.from("tenant1", "application1", "instance1"));
+ addIssues(controllerTester, TenantAndApplicationId.from("tenant1", "application1"));
// GET at root, with "&recursive=deployment", returns info about all tenants, their applications and their deployments
tester.assertResponse(request("/application/v4/", GET)
.userIdentity(USER_ID)
@@ -433,19 +434,19 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE (cancel) ongoing change
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", DELETE)
.userIdentity(HOSTED_VESPA_OPERATOR),
- "{\"message\":\"Changed deployment from 'application change to 1.0.42-commit1' to 'no change' for application 'tenant1.application1.instance1'\"}");
+ "{\"message\":\"Changed deployment from 'application change to 1.0.42-commit1' to 'no change' for application 'tenant1.application1'\"}");
// DELETE (cancel) again is a no-op
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", DELETE)
.userIdentity(USER_ID)
.data("{\"cancel\":\"all\"}"),
- "{\"message\":\"No deployment in progress for application 'tenant1.application1.instance1' at this time\"}");
+ "{\"message\":\"No deployment in progress for application 'tenant1.application1' at this time\"}");
// POST pinning to a given version to an application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", POST)
.userIdentity(USER_ID)
.data("6.1.0"),
- "{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
+ "{\"message\":\"Triggered pin to 6.1 for tenant1.application1\"}");
assertTrue("Action is logged to audit log",
tester.controller().auditLogger().readLog().entries().stream()
.anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin")));
@@ -457,7 +458,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE only the pin to a given version
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Changed deployment from 'pin to 6.1' to 'upgrade to 6.1' for application 'tenant1.application1.instance1'\"}");
+ "{\"message\":\"Changed deployment from 'pin to 6.1' to 'upgrade to 6.1' for application 'tenant1.application1'\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":false}");
@@ -465,21 +466,21 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", POST)
.userIdentity(USER_ID)
.data("6.1"),
- "{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
+ "{\"message\":\"Triggered pin to 6.1 for tenant1.application1\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
// DELETE only the version, but leave the pin
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Changed deployment from 'pin to 6.1' to 'pin to current platform' for application 'tenant1.application1.instance1'\"}");
+ "{\"message\":\"Changed deployment from 'pin to 6.1' to 'pin to current platform' for application 'tenant1.application1'\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{\"pinned\":true}");
// DELETE also the pin to a given version
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Changed deployment from 'pin to current platform' to 'no change' for application 'tenant1.application1.instance1'\"}");
+ "{\"message\":\"Changed deployment from 'pin to current platform' to 'no change' for application 'tenant1.application1'\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{}");
@@ -686,7 +687,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("tenant-without-applications.json"));
}
- private void addIssues(ContainerControllerTester tester, ApplicationId id) {
+ private void addIssues(ContainerControllerTester tester, TenantAndApplicationId id) {
tester.controller().applications().lockApplicationOrThrow(id, application ->
tester.controller().applications().store(application.withDeploymentIssueId(IssueId.from("123"))
.withOwnershipIssueId(IssueId.from("321"))
@@ -1250,7 +1251,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application.id());
controllerTester.jobCompletion(JobType.component)
- .application(application.id())
+ .application(application)
.projectId(screwdriverProjectId)
.uploadArtifact(applicationPackage)
.submit();
@@ -1280,9 +1281,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default");
controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application.id());
- // Allow systemtest to succeed by notifying completion of system test
+ // Allow systemtest to succeed by notifying completion of component
controllerTester.jobCompletion(JobType.component)
- .application(application.id())
+ .application(application)
.projectId(screwdriverProjectId)
.uploadArtifact(applicationPackage)
.submit();
@@ -1379,7 +1380,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Allow systemtest to succeed by notifying completion of system test
controllerTester.jobCompletion(JobType.component)
- .application(application.id())
+ .application(application)
.projectId(screwdriverProjectId)
.uploadArtifact(applicationPackage)
.submit();
@@ -1414,9 +1415,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default");
controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application.id());
- // Allow systemtest to succeed by notifying completion of system test
+ // Allow systemtest to succeed by notifying completion of component
controllerTester.jobCompletion(JobType.component)
- .application(application.id())
+ .application(application)
.projectId(screwdriverProjectId)
.uploadArtifact(applicationPackage)
.submit();
@@ -1449,7 +1450,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
.application(app)
.projectId(projectId);
job.type(JobType.component).uploadArtifact(applicationPackage).submit();
- controllerTester.deploy(app.id(), applicationPackage, TEST_ZONE);
+ controllerTester.deploy(app.id().defaultInstance(), applicationPackage, TEST_ZONE);
job.type(JobType.systemTest).submit();
// Notifying about job started not by the controller fails
@@ -1459,7 +1460,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
.get();
tester.assertResponse(request, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Notified of completion " +
"of system-test for tenant1.application1, but that has not been triggered; last was " +
- controllerTester.controller().applications().require(app.id()).deploymentJobs().jobStatus().get(JobType.systemTest).lastTriggered().get().at() + "\"}", 400);
+ controllerTester.controller().applications().requireInstance(app.id().defaultInstance()).deploymentJobs().jobStatus().get(JobType.systemTest).lastTriggered().get().at() + "\"}", 400);
// Notifying about unknown job fails
request = request("/application/v4/tenant/tenant1/application/application1/jobreport", POST)
@@ -1472,14 +1473,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
// ... and assert it was recorded
JobStatus recordedStatus =
- tester.controller().applications().get(app.id()).get().deploymentJobs().jobStatus().get(JobType.component);
+ tester.controller().applications().getInstance(app.id().defaultInstance()).get().deploymentJobs().jobStatus().get(JobType.component);
assertNotNull("Status was recorded", recordedStatus);
assertTrue(recordedStatus.isSuccess());
assertEquals(vespaVersion, recordedStatus.lastCompleted().get().platform());
recordedStatus =
- tester.controller().applications().get(app.id()).get().deploymentJobs().jobStatus().get(JobType.productionApNortheast2);
+ tester.controller().applications().getInstance(app.id().defaultInstance()).get().deploymentJobs().jobStatus().get(JobType.productionApNortheast2);
assertNull("Status of never-triggered jobs is empty", recordedStatus);
assertTrue("All jobs have been run", tester.controller().applications().deploymentTrigger().jobsToRun().isEmpty());
}
@@ -1501,17 +1502,16 @@ public class ApplicationApiTest extends ControllerContainerTest {
.projectId(projectId);
job.type(JobType.component).uploadArtifact(applicationPackage).submit();
- controllerTester.deploy(app.id(), applicationPackage, TEST_ZONE);
+ controllerTester.deploy(app.id().defaultInstance(), applicationPackage, TEST_ZONE);
job.type(JobType.systemTest).submit();
- controllerTester.deploy(app.id(), applicationPackage, STAGING_ZONE);
+ controllerTester.deploy(app.id().defaultInstance(), applicationPackage, STAGING_ZONE);
job.type(JobType.stagingTest).error(DeploymentJobs.JobError.outOfCapacity).submit();
// Appropriate error is recorded
- JobStatus jobStatus = tester.controller().applications().get(app.id())
- .get()
- .deploymentJobs()
- .jobStatus()
- .get(JobType.stagingTest);
+ JobStatus jobStatus = tester.controller().applications().getInstance(app.id().defaultInstance()).get()
+ .deploymentJobs()
+ .jobStatus()
+ .get(JobType.stagingTest);
assertFalse(jobStatus.isSuccess());
assertEquals(DeploymentJobs.JobError.outOfCapacity, jobStatus.jobError().get());
}
@@ -1524,12 +1524,12 @@ public class ApplicationApiTest extends ControllerContainerTest {
.region("us-west-1")
.build();
controllerTester.deployCompletely(app, applicationPackage, 1, false);
- RoutingPolicy policy = new RoutingPolicy(app.id(),
+ RoutingPolicy policy = new RoutingPolicy(app.id().defaultInstance(),
ClusterSpec.Id.from("default"),
ZoneId.from(Environment.prod, RegionName.from("us-west-1")),
HostName.from("lb-0-canonical-name"),
Optional.of("dns-zone-1"), Set.of(EndpointId.of("c0")));
- tester.controller().curator().writeRoutingPolicies(app.id(), Set.of(policy));
+ tester.controller().curator().writeRoutingPolicies(app.id().defaultInstance(), Set.of(policy));
// GET application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET)
@@ -1722,30 +1722,32 @@ public class ApplicationApiTest extends ControllerContainerTest {
* This sets these values as if the maintainers has been ran.
*/
private void setDeploymentMaintainedInfo(ContainerControllerTester controllerTester) {
- for (Instance instance : controllerTester.controller().applications().asList()) {
- controllerTester.controller().applications().lockOrThrow(instance.id(), lockedApplication -> {
+ for (Application application : controllerTester.controller().applications().asList()) {
+ controllerTester.controller().applications().lockApplicationOrThrow(application.id(), lockedApplication -> {
lockedApplication = lockedApplication.with(new ApplicationMetrics(0.5, 0.7));
- for (Deployment deployment : instance.deployments().values()) {
- Map<ClusterSpec.Id, ClusterInfo> clusterInfo = new HashMap<>();
- List<String> hostnames = new ArrayList<>();
- hostnames.add("host1");
- hostnames.add("host2");
- clusterInfo.put(ClusterSpec.Id.from("cluster1"),
- new ClusterInfo("flavor1", 37, 2, 4, 50,
- ClusterSpec.Type.content, hostnames));
- Map<ClusterSpec.Id, ClusterUtilization> clusterUtils = new HashMap<>();
- clusterUtils.put(ClusterSpec.Id.from("cluster1"), new ClusterUtilization(0.3, 0.6, 0.4, 0.3));
- DeploymentMetrics metrics = new DeploymentMetrics(1, 2, 3, 4, 5,
- Optional.of(Instant.ofEpochMilli(123123)), Map.of());
-
- lockedApplication = lockedApplication
- .withClusterInfo(deployment.zone(), clusterInfo)
- .withClusterUtilization(deployment.zone(), clusterUtils)
- .with(deployment.zone(), metrics)
- .recordActivityAt(Instant.parse("2018-06-01T10:15:30.00Z"), deployment.zone());
+ for (Instance instance : application.instances().values()) {
+ for (Deployment deployment : instance.deployments().values()) {
+ Map<ClusterSpec.Id, ClusterInfo> clusterInfo = new HashMap<>();
+ List<String> hostnames = new ArrayList<>();
+ hostnames.add("host1");
+ hostnames.add("host2");
+ clusterInfo.put(ClusterSpec.Id.from("cluster1"),
+ new ClusterInfo("flavor1", 37, 2, 4, 50,
+ ClusterSpec.Type.content, hostnames));
+ Map<ClusterSpec.Id, ClusterUtilization> clusterUtils = new HashMap<>();
+ clusterUtils.put(ClusterSpec.Id.from("cluster1"), new ClusterUtilization(0.3, 0.6, 0.4, 0.3));
+ DeploymentMetrics metrics = new DeploymentMetrics(1, 2, 3, 4, 5,
+ Optional.of(Instant.ofEpochMilli(123123)), Map.of());
+
+ lockedApplication = lockedApplication.with(instance.name(),
+ lockedInstance -> lockedInstance.withClusterInfo(deployment.zone(), clusterInfo)
+ .withClusterUtilization(deployment.zone(), clusterUtils)
+ .with(deployment.zone(), metrics)
+ .recordActivityAt(Instant.parse("2018-06-01T10:15:30.00Z"), deployment.zone()));
+ }
+ controllerTester.controller().applications().store(lockedApplication);
}
- controllerTester.controller().applications().store(lockedApplication);
});
}
}
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 2f009d16254..fb5f28e1963 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
@@ -33,7 +33,7 @@ import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobTy
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.stagingTest;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud.Status.FAILURE;
-import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.appId;
+import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.instanceId;
import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.applicationPackage;
import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.testerId;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.deploymentFailed;
@@ -69,17 +69,17 @@ public class JobControllerApiHandlerHelperTest {
// us-east-3 eats the deployment failure and fails before deployment, while us-west-1 fails after.
tester.configServer().throwOnNextPrepare(new ConfigServerException(URI.create("url"), "ERROR!", INVALID_APPLICATION_PACKAGE, null));
tester.runner().run();
- assertEquals(deploymentFailed, tester.jobs().last(appId, productionUsEast3).get().status());
+ assertEquals(deploymentFailed, tester.jobs().last(instanceId, productionUsEast3).get().status());
ZoneId usWest1 = productionUsWest1.zone(tester.tester().controller().system());
- tester.configServer().convergeServices(appId, usWest1);
+ tester.configServer().convergeServices(instanceId, usWest1);
tester.configServer().convergeServices(testerId.id(), usWest1);
- tester.setEndpoints(appId, usWest1);
+ tester.setEndpoints(instanceId, usWest1);
tester.setEndpoints(testerId.id(), usWest1);
tester.runner().run();
tester.cloud().set(FAILURE);
tester.runner().run();
- assertEquals(testFailure, tester.jobs().last(appId, productionUsWest1).get().status());
+ assertEquals(testFailure, tester.jobs().last(instanceId, productionUsWest1).get().status());
assertEquals(revision2, tester.instance().deployments().get(productionUsCentral1.zone(tester.tester().controller().system())).applicationVersion());
assertEquals(revision1, tester.instance().deployments().get(productionUsEast3.zone(tester.tester().controller().system())).applicationVersion());
assertEquals(revision2, tester.instance().deployments().get(productionUsWest1.zone(tester.tester().controller().system())).applicationVersion());
@@ -93,17 +93,17 @@ public class JobControllerApiHandlerHelperTest {
tester.tester().readyJobTrigger().maintain(); // Starts a run for us-central-1.
tester.tester().readyJobTrigger().maintain(); // Starts a new staging test run.
tester.runner().run();
- assertEquals(running, tester.jobs().last(appId, productionUsCentral1).get().status());
- assertEquals(running, tester.jobs().last(appId, stagingTest).get().status());
+ assertEquals(running, tester.jobs().last(instanceId, productionUsCentral1).get().status());
+ assertEquals(running, tester.jobs().last(instanceId, stagingTest).get().status());
// Staging is expired, and the job fails and won't be retried immediately.
- tester.tester().controller().applications().deactivate(appId, stagingTest.zone(tester.tester().controller().system()));
+ tester.tester().controller().applications().deactivate(instanceId, stagingTest.zone(tester.tester().controller().system()));
tester.runner().run();
- assertEquals(installationFailed, tester.jobs().last(appId, stagingTest).get().status());
+ assertEquals(installationFailed, tester.jobs().last(instanceId, stagingTest).get().status());
tester.clock().advance(Duration.ofMillis(100_000)); // More than the minute within which there are immediate retries.
tester.tester().readyJobTrigger().maintain();
- assertEquals(installationFailed, tester.jobs().last(appId, stagingTest).get().status());
+ assertEquals(installationFailed, tester.jobs().last(instanceId, stagingTest).get().status());
// System upgrades to a new version, which won't yet start.
Version platform = new Version("7.1");
@@ -114,13 +114,13 @@ public class JobControllerApiHandlerHelperTest {
// Only us-east-3 is verified, on revision1.
// staging-test has 4 runs: one success without sources on revision1, one success from revision1 to revision2,
// one success from revision2 to revision3 and one failure from revision1 to revision3.
- assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(appId, stagingTest), URI.create("https://some.url:43/root")), "staging-runs.json");
- assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(appId, productionUsEast3).get().id(), "0"), "us-east-3-log-without-first.json");
- assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), appId, URI.create("https://some.url:43/root/")), "overview.json");
+ assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(instanceId, stagingTest), URI.create("https://some.url:43/root")), "staging-runs.json");
+ assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(instanceId, productionUsEast3).get().id(), "0"), "us-east-3-log-without-first.json");
+ assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), instanceId, URI.create("https://some.url:43/root/")), "overview.json");
- tester.jobs().deploy(appId, JobType.devAwsUsEast2a, Optional.empty(), applicationPackage);
+ tester.jobs().deploy(instanceId, JobType.devAwsUsEast2a, Optional.empty(), applicationPackage);
tester.runJob(JobType.devAwsUsEast2a);
- assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(appId, devAwsUsEast2a), URI.create("https://some.url:43/root")), "dev-aws-us-east-2a-runs.json");
+ assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(instanceId, devAwsUsEast2a), URI.create("https://some.url:43/root")), "dev-aws-us-east-2a-runs.json");
}
@Test
@@ -129,17 +129,17 @@ public class JobControllerApiHandlerHelperTest {
tester.clock().setInstant(Instant.EPOCH);
ZoneId zone = JobType.devUsEast1.zone(tester.tester().controller().system());
- tester.jobs().deploy(appId, JobType.devUsEast1, Optional.empty(), applicationPackage);
+ tester.jobs().deploy(instanceId, JobType.devUsEast1, Optional.empty(), applicationPackage);
tester.configServer().setLogStream("1554970337.935104\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n");
- assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(appId, devUsEast1).get().id(), null), "dev-us-east-1-log-first-part.json");
+ assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(instanceId, devUsEast1).get().id(), null), "dev-us-east-1-log-first-part.json");
tester.configServer().setLogStream("Nope, this won't be logged");
- tester.configServer().convergeServices(appId, zone);
- tester.setEndpoints(appId, zone);
+ tester.configServer().convergeServices(instanceId, zone);
+ tester.setEndpoints(instanceId, zone);
tester.runner().run();
- assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), appId, URI.create("https://some.url:43/root")), "dev-overview.json");
- assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(appId, devUsEast1).get().id(), "9"), "dev-us-east-1-log-second-part.json");
+ assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), instanceId, URI.create("https://some.url:43/root")), "dev-overview.json");
+ assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(instanceId, devUsEast1).get().id(), "9"), "dev-us-east-1-log-second-part.json");
}
@Test
@@ -153,7 +153,7 @@ public class JobControllerApiHandlerHelperTest {
Optional.of(applicationPackage),
new DeployOptions(true, Optional.empty(),
false, false));
- assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), appId, URI.create("https://some.url:43/root/")),
+ assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), instanceId, URI.create("https://some.url:43/root/")),
"jobs-direct-deployment.json");
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json
index ad7e4f00027..c9ed2ad3391 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json
@@ -1,4 +1,4 @@
{
"error-code": "BAD_REQUEST",
- "message": "Could not delete 'application 'tenant1.application1.instance1'': It has active deployments in: dev.us-west-1, prod.us-central-1"
+ "message": "Could not delete 'application 'tenant1.application1'': It has active deployments in: dev.us-west-1, prod.us-central-1"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
index 35ec5b0e37e..8db759d855d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
@@ -26,7 +26,7 @@ public class BadgeApiTest extends ControllerContainerTest {
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain"), AthenzService.from("service"))
.region("us-west-1")
.build();
- tester.controller().jobController().submit(application.id(),
+ tester.controller().jobController().submit(application.id().defaultInstance(),
new SourceRevision("repository", "branch", "commit"),
"foo@bar",
123,
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
index 128ea0e6f7e..084b235943e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
@@ -48,7 +48,7 @@ public class DeploymentApiTest extends ControllerContainerTest {
// Deploy once so that job information is stored, then remove the deployment
tester.deployCompletely(instanceWithoutDeployment, applicationPackage, 3L, false);
- tester.controller().applications().deactivate(instanceWithoutDeployment.id(), ZoneId.from("prod", "us-west-1"));
+ tester.controller().applications().deactivate(instanceWithoutDeployment.id().defaultInstance(), ZoneId.from("prod", "us-west-1"));
// New version released
version = Version.fromString("5.1");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
index 820aa61617f..3b7d55f8cef 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
@@ -10,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper;
import org.junit.Before;
import org.junit.Test;
@@ -42,7 +43,8 @@ public class SignatureFilterTest {
"PbS2nguIJ64OJH7gFnxM6sxUVj+Nm2HlXw==\n" +
"-----END EC PRIVATE KEY-----\n";
- private static final ApplicationId id = ApplicationId.from("my-tenant", "my-app", "default");
+ private static final TenantAndApplicationId appId = TenantAndApplicationId.from("my-tenant", "my-app");
+ private static final ApplicationId id = appId.defaultInstance();
private ControllerTester tester;
private ApplicationController applications;
@@ -77,12 +79,12 @@ public class SignatureFilterTest {
assertNull(signed.getAttribute(SecurityContext.ATTRIBUTE_NAME));
// Signed request gets no role when a non-matching key is stored for the application.
- applications.lockOrThrow(id, application -> applications.store(application.withPemDeployKey(otherPublicKey)));
+ applications.lockApplicationOrThrow(appId, application -> applications.store(application.withPemDeployKey(otherPublicKey)));
filter.filter(signed);
assertNull(signed.getAttribute(SecurityContext.ATTRIBUTE_NAME));
// Signed request gets a build service role when a matching key is stored for the application.
- applications.lockOrThrow(id, application -> applications.store(application.withPemDeployKey(publicKey)));
+ applications.lockApplicationOrThrow(appId, application -> applications.store(application.withPemDeployKey(publicKey)));
assertTrue(filter.filter(signed).isEmpty());
SecurityContext securityContext = (SecurityContext) signed.getAttribute(SecurityContext.ATTRIBUTE_NAME);
assertEquals("buildService@my-tenant.my-app", securityContext.principal().getName());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
index c563a4950de..25ae7c88e84 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
@@ -59,7 +59,7 @@ public class RotationRepositoryTest {
tester = new DeploymentTester(new ControllerTester(rotationsConfig));
repository = tester.controller().applications().rotationRepository();
application = tester.createApplication("app1", "tenant1", 11L, 1L);
- instance = tester.instance(application.id());
+ instance = tester.defaultInstance(application.id());
}
@Test
@@ -68,12 +68,12 @@ public class RotationRepositoryTest {
tester.deployCompletely(application, applicationPackage);
Rotation expected = new Rotation(new RotationId("foo-1"), "foo-1.com");
- instance = tester.applications().require(instance.id());
+ instance = tester.applications().requireInstance(instance.id());
assertEquals(List.of(expected.id()), rotationIds(instance.rotations()));
assertEquals(URI.create("https://app1--tenant1.global.vespa.oath.cloud:4443/"),
instance.endpointsIn(SystemName.main).main().get().url());
try (RotationLock lock = repository.lock()) {
- Rotation rotation = repository.getOrAssignRotation(tester.applications().require(instance.id()), lock);
+ Rotation rotation = repository.getOrAssignRotation(application.deploymentSpec(), tester.applications().requireInstance(instance.id()), lock);
assertEquals(expected, rotation);
}
@@ -85,7 +85,7 @@ public class RotationRepositoryTest {
.searchDefinition("search foo { }") // Update application package so there is something to deploy
.build();
tester.deployCompletely(application, applicationPackage, 43);
- assertEquals(List.of(expected.id()), rotationIds(tester.applications().require(instance.id()).rotations()));
+ assertEquals(List.of(expected.id()), rotationIds(tester.applications().requireInstance(instance.id()).rotations()));
}
@Test
@@ -95,10 +95,10 @@ public class RotationRepositoryTest {
Application application2 = tester.createApplication("app2", "tenant2", 22L, 2L);
tester.deployCompletely(application2, applicationPackage);
- Instance instance2 = tester.applications().require(application2.id());
+ Instance instance2 = tester.defaultInstance(application2.id());
try (RotationLock lock = repository.lock()) {
- Rotation rotation = repository.getOrAssignRotation(instance2, lock);
+ Rotation rotation = repository.getOrAssignRotation(application2.deploymentSpec(), instance2, lock);
Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com");
assertEquals(assignedRotation, rotation);
}
@@ -139,7 +139,7 @@ public class RotationRepositoryTest {
.region("us-west-1")
.build();
tester.deployCompletely(application, applicationPackage);
- assertTrue(tester.instance(application.id()).rotations().isEmpty());
+ assertTrue(tester.defaultInstance(application.id()).rotations().isEmpty());
}
@Test
@@ -151,8 +151,8 @@ public class RotationRepositoryTest {
.build();
Application application2 = tester.createApplication("app2", "tenant2", 22L, 2L);
tester.deployCompletely(application2, applicationPackage);
- assertEquals(List.of(new RotationId("foo-1")), rotationIds(tester.instance(application2.id()).rotations()));
- assertEquals("https://cd--app2--tenant2.global.vespa.oath.cloud:4443/", tester.instance(application2.id())
+ assertEquals(List.of(new RotationId("foo-1")), rotationIds(tester.defaultInstance(application2.id()).rotations()));
+ assertEquals("https://cd--app2--tenant2.global.vespa.oath.cloud:4443/", tester.defaultInstance(application2.id())
.endpointsIn(SystemName.cd).main().get().url().toString());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
index 3aae5b9b2ae..ba8309de286 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
@@ -158,13 +158,13 @@ public class VersionStatusTest {
VespaVersion v1 = versions.get(0);
assertEquals(version1, v1.versionNumber());
assertEquals("No applications are failing on version1.", ImmutableSet.of(), v1.statistics().failing());
- assertEquals("All applications have at least one active production deployment on version 1.", ImmutableSet.of(app1.id(), app2.id(), app3.id()), v1.statistics().production());
+ assertEquals("All applications have at least one active production deployment on version 1.", ImmutableSet.of(app1.id().defaultInstance(), app2.id().defaultInstance(), app3.id().defaultInstance()), v1.statistics().production());
assertEquals("No applications have active deployment jobs on version1.", ImmutableSet.of(), v1.statistics().deploying());
VespaVersion v2 = versions.get(1);
assertEquals(version2, v2.versionNumber());
- assertEquals("All applications have failed on version2 in at least one zone.", ImmutableSet.of(app1.id(), app2.id(), app3.id()), v2.statistics().failing());
- assertEquals("Only app2 has successfully deployed to production on version2.", ImmutableSet.of(app2.id()), v2.statistics().production());
+ assertEquals("All applications have failed on version2 in at least one zone.", ImmutableSet.of(app1.id().defaultInstance(), app2.id().defaultInstance(), app3.id().defaultInstance()), v2.statistics().failing());
+ assertEquals("Only app2 has successfully deployed to production on version2.", ImmutableSet.of(app2.id().defaultInstance()), v2.statistics().production());
// Should test the below, but can't easily be done with current test framework. This test passes in DeploymentApiTest.
// assertEquals("All applications are being retried on version2.", ImmutableSet.of(app1.id(), app2.id(), app3.id()), v2.statistics().deploying());
}