summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java46
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentCost.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentSpecValidator.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java28
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java38
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java78
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java60
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java58
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java59
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java72
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java122
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java24
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java35
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java56
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java25
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java56
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java16
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/os-version-status-legacy-format.json22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/version-status-legacy-format.json20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java62
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/both-developer-keys.json12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/first-deploy-key.json5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/first-developer-key.json9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/second-developer-key.json8
40 files changed, 641 insertions, 461 deletions
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 bfe7fc1ee2e..8592460a24f 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
@@ -214,7 +214,7 @@ public class ApplicationController {
public ApplicationStore applicationStore() { return applicationStore; }
/** Returns all content clusters in all current deployments of the given application. */
- public Map<ZoneId, List<String>> listClusters(ApplicationId id, Iterable<ZoneId> zones) {
+ public Map<ZoneId, List<String>> contentClustersByZone(ApplicationId id, Iterable<ZoneId> zones) {
ImmutableMap.Builder<ZoneId, List<String>> clusters = ImmutableMap.builder();
for (ZoneId zone : zones)
clusters.put(zone, ImmutableList.copyOf(configServer.getContentClusters(new DeploymentId(id, zone))));
@@ -385,10 +385,9 @@ public class ApplicationController {
}
if (zone.environment().isProduction()) // Assign and register endpoints
- application = withRotation(application, instance);
-
- endpoints = registerEndpointsInDns(application.get().deploymentSpec(), application.get().require(instanceId.instance()), zone);
+ application = withRotation(applicationPackage.deploymentSpec(), application, instance);
+ endpoints = registerEndpointsInDns(applicationPackage.deploymentSpec(), application.get().require(instanceId.instance()), zone);
if (controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) {
// Provisions a new certificate if missing
@@ -518,9 +517,9 @@ public class ApplicationController {
}
/** Makes sure the application has a global rotation, if eligible. */
- private LockedApplication withRotation(LockedApplication application, InstanceName instanceName) {
+ private LockedApplication withRotation(DeploymentSpec deploymentSpec, LockedApplication application, InstanceName instanceName) {
try (RotationLock rotationLock = rotationRepository.lock()) {
- var rotations = rotationRepository.getOrAssignRotations(application.get().deploymentSpec(),
+ var rotations = rotationRepository.getOrAssignRotations(deploymentSpec,
application.get().require(instanceName),
rotationLock);
application = application.with(instanceName, instance -> instance.with(rotations));
@@ -536,7 +535,7 @@ public class ApplicationController {
*/
private Set<ContainerEndpoint> registerEndpointsInDns(DeploymentSpec deploymentSpec, Instance instance, ZoneId zone) {
var containerEndpoints = new HashSet<ContainerEndpoint>();
- var registerLegacyNames = deploymentSpec.globalServiceId().isPresent();
+ boolean registerLegacyNames = deploymentSpec.instance(instance.name()).flatMap(i -> i.globalServiceId()).isPresent();
for (var assignedRotation : instance.rotations()) {
var names = new ArrayList<String>();
var endpoints = instance.endpointsIn(controller.system(), assignedRotation.endpointId())
@@ -628,8 +627,8 @@ public class ApplicationController {
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())))
+ .filter(deployment -> ! deploymentSpec.requireInstance(instance).includes(deployment.zone().environment(),
+ Optional.of(deployment.zone().region())))
.collect(Collectors.toList());
if (deploymentsToRemove.isEmpty()) return application;
@@ -653,7 +652,7 @@ public class ApplicationController {
private Instance withoutUnreferencedDeploymentJobs(DeploymentSpec deploymentSpec, Instance instance) {
for (JobType job : JobList.from(instance).production().mapToList(JobStatus::type)) {
ZoneId zone = job.zone(controller.system());
- if (deploymentSpec.includes(zone.environment(), Optional.of(zone.region())))
+ if (deploymentSpec.requireInstance(instance.name()).includes(zone.environment(), Optional.of(zone.region())))
continue;
instance = instance.withoutDeploymentJob(job);
}
@@ -911,9 +910,9 @@ public class ApplicationController {
* 2. If the principal is given, verify that the principal is tenant admin or admin of the tenant domain
* 3. If the principal is not given, verify that the Athenz domain of the tenant equals Athenz domain given in deployment.xml
*
- * @param tenantName Tenant where application should be deployed
- * @param applicationPackage Application package
- * @param deployer Principal initiating the deployment, possibly empty
+ * @param tenantName tenant where application should be deployed
+ * @param applicationPackage application package
+ * @param deployer principal initiating the deployment, possibly empty
*/
public void verifyApplicationIdentityConfiguration(TenantName tenantName, ApplicationPackage applicationPackage, Optional<Principal> deployer) {
verifyAllowedLaunchAthenzService(applicationPackage.deploymentSpec());
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 f885b7a146e..627cde28fd0 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
@@ -13,7 +13,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationV
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
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;
@@ -87,19 +86,12 @@ public class Instance {
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.
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
index 03d084cd9e3..361dcf9dbf9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
@@ -1,7 +1,6 @@
// 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.application;
-import com.google.common.collect.ImmutableMap;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ClusterSpec.Id;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
@@ -9,7 +8,6 @@ import com.yahoo.config.provision.zone.ZoneId;
import java.time.Instant;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -25,26 +23,24 @@ public class Deployment {
private final ApplicationVersion applicationVersion;
private final Version version;
private final Instant deployTime;
- private final Map<Id, ClusterUtilization> clusterUtilization;
private final Map<Id, ClusterInfo> clusterInfo;
private final DeploymentMetrics metrics;
private final DeploymentActivity activity;
public Deployment(ZoneId zone, ApplicationVersion applicationVersion, Version version, Instant deployTime) {
- this(zone, applicationVersion, version, deployTime, Collections.emptyMap(), Collections.emptyMap(),
+ this(zone, applicationVersion, version, deployTime, Collections.emptyMap(),
DeploymentMetrics.none, DeploymentActivity.none);
}
public Deployment(ZoneId zone, ApplicationVersion applicationVersion, Version version, Instant deployTime,
- Map<Id, ClusterUtilization> clusterUtilization, Map<Id, ClusterInfo> clusterInfo,
+ Map<Id, ClusterInfo> clusterInfo,
DeploymentMetrics metrics,
DeploymentActivity activity) {
this.zone = Objects.requireNonNull(zone, "zone cannot be null");
this.applicationVersion = Objects.requireNonNull(applicationVersion, "applicationVersion cannot be null");
this.version = Objects.requireNonNull(version, "version cannot be null");
this.deployTime = Objects.requireNonNull(deployTime, "deployTime cannot be null");
- this.clusterUtilization = ImmutableMap.copyOf(Objects.requireNonNull(clusterUtilization, "clusterUtilization cannot be null"));
- this.clusterInfo = ImmutableMap.copyOf(Objects.requireNonNull(clusterInfo, "clusterInfo cannot be null"));
+ this.clusterInfo = Map.copyOf(Objects.requireNonNull(clusterInfo, "clusterInfo cannot be null"));
this.metrics = Objects.requireNonNull(metrics, "deploymentMetrics cannot be null");
this.activity = Objects.requireNonNull(activity, "activity cannot be null");
}
@@ -74,52 +70,26 @@ public class Deployment {
return clusterInfo;
}
- /** Returns utilization of the clusters allocated to this */
- // TODO(mpolden): No longer updated. Remove this and associated serialization
- public Map<Id, ClusterUtilization> clusterUtils() {
- return clusterUtilization;
- }
-
public Deployment recordActivityAt(Instant instant) {
- return new Deployment(zone, applicationVersion, version, deployTime, clusterUtilization, clusterInfo, metrics,
+ return new Deployment(zone, applicationVersion, version, deployTime, clusterInfo, metrics,
activity.recordAt(instant, metrics));
}
- public Deployment withClusterUtils(Map<Id, ClusterUtilization> clusterUtilization) {
- return new Deployment(zone, applicationVersion, version, deployTime, clusterUtilization, clusterInfo, metrics,
+ public Deployment withClusterUtils() {
+ return new Deployment(zone, applicationVersion, version, deployTime, clusterInfo, metrics,
activity);
}
public Deployment withClusterInfo(Map<Id, ClusterInfo> newClusterInfo) {
- return new Deployment(zone, applicationVersion, version, deployTime, clusterUtilization, newClusterInfo, metrics,
+ return new Deployment(zone, applicationVersion, version, deployTime, newClusterInfo, metrics,
activity);
}
public Deployment withMetrics(DeploymentMetrics metrics) {
- return new Deployment(zone, applicationVersion, version, deployTime, clusterUtilization, clusterInfo, metrics,
+ return new Deployment(zone, applicationVersion, version, deployTime, clusterInfo, metrics,
activity);
}
- /**
- * Calculate cost for this deployment.
- *
- * This is based on cluster utilization and cluster info.
- */
- public DeploymentCost calculateCost() {
-
- Map<String, ClusterCost> costClusters = new HashMap<>();
- for (Id clusterId : clusterUtilization.keySet()) {
-
- // Only include cluster cost if we have both cluster utilization and cluster info
- if (clusterInfo.containsKey(clusterId)) {
- costClusters.put(clusterId.value(), new ClusterCost(clusterInfo.get(clusterId),
- clusterUtilization.get(clusterId)));
- }
- }
-
- return new DeploymentCost(costClusters);
- }
-
@Override
public String toString() {
return "deployment to " + zone + " of " + applicationVersion + " on version " + version + " at " + deployTime;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentCost.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentCost.java
index 371e1c41e32..393c14b35d3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentCost.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentCost.java
@@ -17,7 +17,7 @@ public class DeploymentCost {
private final Map<String, ClusterCost> clusters;
- DeploymentCost(Map<String, ClusterCost> clusterCosts) {
+ public DeploymentCost(Map<String, ClusterCost> clusterCosts) {
clusters = new HashMap<>(clusterCosts);
double tco = 0;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentSpecValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentSpecValidator.java
index ce7904dc829..5c4d5874e53 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentSpecValidator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentSpecValidator.java
@@ -39,7 +39,7 @@ public class DeploymentSpecValidator {
/** Verify that each of the production zones listed in the deployment spec exist in this system */
private void validateSteps(DeploymentSpec deploymentSpec) {
new DeploymentSteps(deploymentSpec, controller::system).jobs();
- deploymentSpec.zones().stream()
+ deploymentSpec.instances().stream().flatMap(instance -> instance.zones().stream())
.filter(zone -> zone.environment() == Environment.prod)
.forEach(zone -> {
if ( ! controller.zoneRegistry().hasZone(ZoneId.from(zone.environment(),
@@ -51,16 +51,19 @@ public class DeploymentSpecValidator {
/** Verify that no single endpoint contains regions in different clouds */
private void validateEndpoints(DeploymentSpec deploymentSpec) {
- for (var endpoint : deploymentSpec.endpoints()) {
- var clouds = new HashSet<CloudName>();
- for (var region : endpoint.regions()) {
- for (ZoneApi zone : controller.zoneRegistry().zones().all().in(region).zones()) {
- clouds.add(zone.getCloudName());
+ for (var instance : deploymentSpec.instances()) {
+ for (var endpoint : instance.endpoints()) {
+ var clouds = new HashSet<CloudName>();
+ for (var region : endpoint.regions()) {
+ for (ZoneApi zone : controller.zoneRegistry().zones().all().in(region).zones()) {
+ clouds.add(zone.getCloudName());
+ }
+ }
+ if (clouds.size() != 1) {
+ throw new IllegalArgumentException("Endpoint '" + endpoint.endpointId() + "' in " + instance +
+ " cannot contain regions in different clouds: " +
+ endpoint.regions().stream().sorted().collect(Collectors.toList()));
}
- }
- if (clouds.size() != 1) {
- throw new IllegalArgumentException("Endpoint '" + endpoint.endpointId() + "' cannot contain regions in different clouds: " +
- endpoint.regions().stream().sorted().collect(Collectors.toList()));
}
}
}
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 376048143d9..3df889d7a88 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
@@ -372,9 +372,8 @@ public class DeploymentTrigger {
}
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;
+ completedAt = completedAt.map(at -> at.plus(step.delay())).filter(at -> !at.isAfter(clock.instant()));
+ reason += " after a delay of " + step.delay();
}
else {
completedAt = stepJobs.stream().map(job -> instance.deploymentJobs().statusOf(job).get().lastCompleted().get().at()).max(naturalOrder());
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 42e270edd5e..50af8bd8611 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
@@ -1,8 +1,6 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
@@ -468,7 +466,7 @@ public class InternalStepRunner implements StepRunner {
testConfigSerializer.configJson(id.application(),
id.type(),
endpoints,
- controller.applications().listClusters(id.application(), zones)));
+ controller.applications().contentClustersByZone(id.application(), zones)));
return Optional.of(running);
}
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 9253e249765..361cc43da50 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
@@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.JobList;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
+import com.yahoo.vespa.hosted.controller.versions.NodeVersions;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import java.time.Clock;
@@ -20,6 +21,7 @@ import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -36,6 +38,7 @@ public class MetricsReporter extends Maintainer {
public static final String DEPLOYMENT_BUILD_AGE_SECONDS = "deployment.buildAgeSeconds";
public static final String DEPLOYMENT_WARNINGS = "deployment.warnings";
public static final String NODES_FAILING_SYSTEM_UPGRADE = "deployment.nodesFailingSystemUpgrade";
+ public static final String NODES_FAILING_OS_UPGRADE = "deployment.nodesFailingOsUpgrade";
public static final String REMAINING_ROTATIONS = "remaining_rotations";
public static final String NAME_SERVICE_REQUESTS_QUEUED = "dns.queuedRequests";
@@ -56,6 +59,7 @@ public class MetricsReporter extends Maintainer {
reportRemainingRotations();
reportQueuedNameServiceRequests();
reportNodesFailingSystemUpgrade();
+ reportNodesFailingOsUpgrade();
}
private void reportRemainingRotations() {
@@ -103,13 +107,31 @@ public class MetricsReporter extends Maintainer {
metric.set(NODES_FAILING_SYSTEM_UPGRADE, nodesFailingSystemUpgrade(), metric.createContext(Map.of()));
}
+ private void reportNodesFailingOsUpgrade() {
+ metric.set(NODES_FAILING_OS_UPGRADE, nodesFailingOsUpgrade(), metric.createContext(Map.of()));
+ }
+
private int nodesFailingSystemUpgrade() {
if (!controller().versionStatus().isUpgrading()) return 0;
+ return nodesFailingUpgrade(controller().versionStatus().versions(), (vespaVersion) -> {
+ if (vespaVersion.confidence() == VespaVersion.Confidence.broken) return NodeVersions.EMPTY;
+ return vespaVersion.nodeVersions();
+ });
+ }
+
+ private int nodesFailingOsUpgrade() {
+ return nodesFailingUpgrade(controller().osVersionStatus().versions().entrySet(), (kv) -> {
+ var osVersion = kv.getKey();
+ if (osVersion.version().isEmpty()) return NodeVersions.EMPTY;
+ return kv.getValue();
+ });
+ }
+
+ private <V> int nodesFailingUpgrade(Collection<V> collection, Function<V, NodeVersions> nodeVersionsFunction) {
var nodesFailingUpgrade = 0;
var acceptableInstant = clock.instant().minus(NODE_UPGRADE_TIMEOUT);
- for (var vespaVersion : controller().versionStatus().versions()) {
- if (vespaVersion.confidence() == VespaVersion.Confidence.broken) continue;
- for (var nodeVersion : vespaVersion.nodeVersions().asMap().values()) {
+ for (var object : collection) {
+ for (var nodeVersion : nodeVersionsFunction.apply(object).asMap().values()) {
if (!nodeVersion.changing()) continue;
if (nodeVersion.changedAt().isBefore(acceptableInstant)) nodesFailingUpgrade++;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
index 60bc3d15ec6..93d1dac7382 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
@@ -61,7 +61,7 @@ public class OsUpgrader extends InfrastructureUpgrader {
// Return target if we have nodes in this cloud on a lower version
return controller().osVersion(cloud)
.filter(target -> controller().osVersionStatus().nodesIn(cloud).stream()
- .anyMatch(node -> node.version().isBefore(target.version())))
+ .anyMatch(node -> node.currentVersion().isBefore(target.version())))
.map(OsVersion::version);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
index c700ddac51c..0e14b61c5c5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
@@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneApi;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
@@ -15,6 +17,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -47,33 +50,44 @@ public class ResourceMeterMaintainer extends Maintainer {
@Override
protected void maintain() {
- Collection<ResourceSnapshot> resourceSnapshots = getResourceSnapshots(allocatedNodes());
+
+ Collection<ResourceSnapshot> resourceSnapshots = getAllResourceSnapshots();
meteringClient.consume(resourceSnapshots);
metric.set(METERING_LAST_REPORTED, clock.millis() / 1000, metric.createContext(Collections.emptyMap()));
// total metered resource usage, for alerting on drastic changes
metric.set(METERING_TOTAL_REPORTED,
- resourceSnapshots.stream().mapToDouble(r -> r.getCpuCores() + r.getMemoryGb() + r.getDiskGb()).sum(),
+ resourceSnapshots.stream()
+ .mapToDouble(r -> r.getCpuCores() + r.getMemoryGb() + r.getDiskGb()).sum(),
metric.createContext(Collections.emptyMap()));
}
- private List<Node> allocatedNodes() {
+ private Collection<ResourceSnapshot> getAllResourceSnapshots() {
return controller().zoneRegistry().zones()
.ofCloud(CloudName.from("aws"))
.reachable().zones().stream()
- .flatMap(zone -> nodeRepository.list(zone.getId()).stream())
- .filter(node -> node.owner().isPresent())
- .filter(node -> ! node.owner().get().tenant().value().equals("hosted-vespa"))
+ .map(ZoneApi::getId)
+ .map(zoneId -> createResourceSnapshotsFromNodes(zoneId, nodeRepository.list(zoneId)))
+ .flatMap(Collection::stream)
.collect(Collectors.toList());
}
- private Collection<ResourceSnapshot> getResourceSnapshots(List<Node> nodes) {
+ private Collection<ResourceSnapshot> createResourceSnapshotsFromNodes(ZoneId zoneId, List<Node> nodes) {
return nodes.stream()
- .collect(Collectors.groupingBy(node -> node.owner().get(),
- Collectors.collectingAndThen(Collectors.toList(),
- nodeList -> ResourceSnapshot.from(nodeList,
- clock.instant()))
- )).values();
+ .filter(unlessNodeOwnerIsHostedVespa())
+ .collect(Collectors.groupingBy(node ->
+ node.owner().get(),
+ Collectors.collectingAndThen(Collectors.toList(),
+ nodeList -> ResourceSnapshot.from(
+ nodeList,
+ clock.instant(),
+ zoneId))
+ )).values();
}
+ private Predicate<Node> unlessNodeOwnerIsHostedVespa() {
+ return node -> node.owner().map(owner ->
+ !owner.tenant().value().equals("hosted-vespa")
+ ).orElse(false);
+ }
}
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 61fd0b67ec9..e67d5aea45d 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
@@ -23,7 +23,6 @@ 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;
@@ -88,7 +87,6 @@ public class ApplicationSerializer {
private static final String pemDeployKeysField = "pemDeployKeys";
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";
@@ -147,13 +145,6 @@ public class ApplicationSerializer {
private static final String clusterInfoTypeField = "clusterType";
private static final String clusterInfoHostnamesField = "hostnames";
- // ClusterUtils fields
- private static final String clusterUtilsField = "clusterUtils";
- private static final String clusterUtilsCpuField = "cpu";
- private static final String clusterUtilsMemField = "mem";
- private static final String clusterUtilsDiskField = "disk";
- private static final String clusterUtilsDiskBusyField = "diskbusy";
-
// Deployment metrics fields
private static final String deploymentMetricsField = "metrics";
private static final String deploymentMetricsQPSField = "queriesPerSecond";
@@ -220,7 +211,6 @@ public class ApplicationSerializer {
object.setLong(deployTimeField, deployment.at().toEpochMilli());
toSlime(deployment.applicationVersion(), object.setObject(applicationPackageRevisionField));
clusterInfoToSlime(deployment.clusterInfo(), object);
- clusterUtilsToSlime(deployment.clusterUtils(), object);
deploymentMetricsToSlime(deployment.metrics(), object);
deployment.activity().lastQueried().ifPresent(instant -> object.setLong(lastQueriedField, instant.toEpochMilli()));
deployment.activity().lastWritten().ifPresent(instant -> object.setLong(lastWrittenField, instant.toEpochMilli()));
@@ -262,20 +252,6 @@ public class ApplicationSerializer {
}
}
- private void clusterUtilsToSlime(Map<ClusterSpec.Id, ClusterUtilization> clusters, Cursor object) {
- Cursor root = object.setObject(clusterUtilsField);
- for (Map.Entry<ClusterSpec.Id, ClusterUtilization> entry : clusters.entrySet()) {
- toSlime(entry.getValue(), root.setObject(entry.getKey().value()));
- }
- }
-
- private void toSlime(ClusterUtilization utils, Cursor object) {
- object.setDouble(clusterUtilsCpuField, utils.getCpu());
- object.setDouble(clusterUtilsMemField, utils.getMemory());
- object.setDouble(clusterUtilsDiskField, utils.getDisk());
- object.setDouble(clusterUtilsDiskBusyField, utils.getDiskBusy());
- }
-
private void zoneIdToSlime(ZoneId zone, Cursor object) {
object.setString(environmentField, zone.environment().value());
object.setString(regionField, zone.region().value());
@@ -425,7 +401,6 @@ public class ApplicationSerializer {
applicationVersionFromSlime(deploymentObject.field(applicationPackageRevisionField)),
Version.fromString(deploymentObject.field(versionField).asString()),
Instant.ofEpochMilli(deploymentObject.field(deployTimeField).asLong()),
- Map.of(),
clusterInfoMapFromSlime(deploymentObject.field(clusterInfoField)),
deploymentMetricsFromSlime(deploymentObject.field(deploymentMetricsField)),
DeploymentActivity.create(Serializers.optionalInstant(deploymentObject.field(lastQueriedField)),
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 357dbb37b27..dbd52fc6d02 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,7 +6,6 @@ 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;
@@ -82,14 +81,15 @@ public class CuratorDb {
private static final Path applicationCertificateRoot = root.append("applicationCertificates");
private final StringSetSerializer stringSetSerializer = new StringSetSerializer();
- private final VersionStatusSerializer versionStatusSerializer = new VersionStatusSerializer();
+ private final NodeVersionSerializer nodeVersionSerializer = new NodeVersionSerializer();
+ private final VersionStatusSerializer versionStatusSerializer = new VersionStatusSerializer(nodeVersionSerializer);
private final ControllerVersionSerializer controllerVersionSerializer = new ControllerVersionSerializer();
private final ConfidenceOverrideSerializer confidenceOverrideSerializer = new ConfidenceOverrideSerializer();
private final TenantSerializer tenantSerializer = new TenantSerializer();
private final ApplicationSerializer applicationSerializer = new ApplicationSerializer();
private final RunSerializer runSerializer = new RunSerializer();
private final OsVersionSerializer osVersionSerializer = new OsVersionSerializer();
- private final OsVersionStatusSerializer osVersionStatusSerializer = new OsVersionStatusSerializer(osVersionSerializer);
+ private final OsVersionStatusSerializer osVersionStatusSerializer = new OsVersionStatusSerializer(osVersionSerializer, nodeVersionSerializer);
private final RoutingPolicySerializer routingPolicySerializer = new RoutingPolicySerializer();
private final AuditLogSerializer auditLogSerializer = new AuditLogSerializer();
private final NameServiceQueueSerializer nameServiceQueueSerializer = new NameServiceQueueSerializer();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java
new file mode 100644
index 00000000000..4b6e997241d
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java
@@ -0,0 +1,78 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.persistence;
+
+import com.google.common.collect.ImmutableMap;
+import com.yahoo.component.Version;
+import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.slime.ArrayTraverser;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Inspector;
+import com.yahoo.vespa.hosted.controller.versions.NodeVersion;
+import com.yahoo.vespa.hosted.controller.versions.NodeVersions;
+
+import java.time.Instant;
+
+/**
+ * Serializer for {@link com.yahoo.vespa.hosted.controller.versions.NodeVersion}.
+ *
+ * @author mpolden
+ */
+public class NodeVersionSerializer {
+
+ // WARNING: Since there are multiple servers in a ZooKeeper cluster and they upgrade one by one
+ // (and rewrite all nodes on startup), changes to the serialized format must be made
+ // such that what is serialized on version N+1 can be read by version N:
+ // - ADDING FIELDS: Always ok
+ // - REMOVING FIELDS: Stop reading the field first. Stop writing it on a later version.
+ // - CHANGING THE FORMAT OF A FIELD: Don't do it bro.
+
+ private static final String hostnameField = "hostname";
+ private static final String zoneField = "zone";
+ private static final String wantedVersionField = "wantedVersion";
+ private static final String changedAtField = "changedAt";
+
+ // Legacy fields
+ private static final String environmentField = "environment";
+ private static final String regionField = "region";
+
+ public void nodeVersionsToSlime(NodeVersions nodeVersions, Cursor array) {
+ for (var nodeVersion : nodeVersions.asMap().values()) {
+ var nodeVersionObject = array.addObject();
+ nodeVersionObject.setString(hostnameField, nodeVersion.hostname().value());
+ nodeVersionObject.setString(zoneField, nodeVersion.zone().value());
+ nodeVersionObject.setString(wantedVersionField, nodeVersion.wantedVersion().toFullString());
+ nodeVersionObject.setLong(changedAtField, nodeVersion.changedAt().toEpochMilli());
+ }
+ }
+
+ public NodeVersions nodeVersionsFromSlime(Inspector array, Version version) {
+ var nodeVersions = ImmutableMap.<HostName, NodeVersion>builder();
+ array.traverse((ArrayTraverser) (i, entry) -> {
+ var hostname = HostName.from(entry.field(hostnameField).asString());
+ var zone = zoneFromSlime(entry);
+ // TODO(mpolden): Make the following fields non-optional after September 2019
+ var wantedVersion = Serializers.optionalString(entry.field(wantedVersionField))
+ .map(Version::fromString)
+ .orElse(Version.emptyVersion);
+ var changedAt = Serializers.optionalInstant(entry.field(changedAtField)).orElse(Instant.EPOCH);
+ nodeVersions.put(hostname, new NodeVersion(hostname, zone, version, wantedVersion, changedAt));
+ });
+ return new NodeVersions(nodeVersions.build());
+ }
+
+ // TODO(mpolden): Simplify and in-line after September 2019
+ private ZoneId zoneFromSlime(Inspector object) {
+ var zoneInspector = object.field(zoneField);
+ if (zoneInspector.valid()) {
+ return ZoneId.from(zoneInspector.asString());
+ }
+ var regionInspector = object.field(regionField);
+ var environmentInspector = object.field(environmentField);
+ if (regionInspector.valid() && environmentInspector.valid()) {
+ return ZoneId.from(environmentInspector.asString(), regionInspector.asString());
+ }
+ return ZoneId.defaultId();
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
index 88805f54d65..fa29969f166 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
@@ -1,23 +1,19 @@
// 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.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
import com.yahoo.component.Version;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.RegionName;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
+import com.yahoo.vespa.hosted.controller.versions.NodeVersion;
+import com.yahoo.vespa.hosted.controller.versions.NodeVersions;
import com.yahoo.vespa.hosted.controller.versions.OsVersion;
import com.yahoo.vespa.hosted.controller.versions.OsVersionStatus;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.TreeMap;
/**
* Serializer for {@link OsVersionStatus}.
@@ -39,11 +35,14 @@ public class OsVersionStatusSerializer {
private static final String hostnameField = "hostname";
private static final String regionField = "region";
private static final String environmentField = "environment";
+ private static final String nodeVersionsField = "nodeVersions";
private final OsVersionSerializer osVersionSerializer;
+ private final NodeVersionSerializer nodeVersionSerializer;
- public OsVersionStatusSerializer(OsVersionSerializer osVersionSerializer) {
+ public OsVersionStatusSerializer(OsVersionSerializer osVersionSerializer, NodeVersionSerializer nodeVersionSerializer) {
this.osVersionSerializer = Objects.requireNonNull(osVersionSerializer, "osVersionSerializer must be non-null");
+ this.nodeVersionSerializer = Objects.requireNonNull(nodeVersionSerializer, "nodeVersionSerializer must be non-null");
}
public Slime toSlime(OsVersionStatus status) {
@@ -53,6 +52,8 @@ public class OsVersionStatusSerializer {
status.versions().forEach((version, nodes) -> {
Cursor object = versions.addObject();
osVersionSerializer.toSlime(version, object);
+ nodeVersionSerializer.nodeVersionsToSlime(nodes, object.setArray(nodeVersionsField));
+ // TODO(mpolden): Stop writing this after September 2019
nodesToSlime(nodes, object.setArray(nodesField));
});
return slime;
@@ -62,40 +63,33 @@ public class OsVersionStatusSerializer {
return new OsVersionStatus(osVersionsFromSlime(slime.get().field(versionsField)));
}
- private void nodesToSlime(List<OsVersionStatus.Node> nodes, Cursor array) {
- nodes.forEach(node -> nodeToSlime(node, array.addObject()));
+ private void nodesToSlime(NodeVersions nodeVersions, Cursor array) {
+ nodeVersions.asMap().values().forEach(node -> nodeToSlime(node, array.addObject()));
}
- private void nodeToSlime(OsVersionStatus.Node node, Cursor object) {
+ private void nodeToSlime(NodeVersion node, Cursor object) {
object.setString(hostnameField, node.hostname().value());
- object.setString(versionField, node.version().toFullString());
- object.setString(regionField, node.region().value());
- object.setString(environmentField, node.environment().value());
+ object.setString(versionField, node.currentVersion().toFullString());
+ object.setString(regionField, node.zone().region().value());
+ object.setString(environmentField, node.zone().environment().value());
}
- private Map<OsVersion, List<OsVersionStatus.Node>> osVersionsFromSlime(Inspector array) {
- Map<OsVersion, List<OsVersionStatus.Node>> versions = new TreeMap<>();
+ private ImmutableMap<OsVersion, NodeVersions> osVersionsFromSlime(Inspector array) {
+ var versions = ImmutableSortedMap.<OsVersion, NodeVersions>naturalOrder();
array.traverse((ArrayTraverser) (i, object) -> {
OsVersion osVersion = osVersionSerializer.fromSlime(object);
- List<OsVersionStatus.Node> nodes = nodesFromSlime(object.field(nodesField));
- versions.put(osVersion, nodes);
+ versions.put(osVersion, nodesFromSlime(object, osVersion.version()));
});
- return Collections.unmodifiableMap(versions);
+ return versions.build();
}
- private List<OsVersionStatus.Node> nodesFromSlime(Inspector array) {
- List<OsVersionStatus.Node> nodes = new ArrayList<>();
- array.traverse((ArrayTraverser) (i, object) -> nodes.add(nodeFromSlime(object)));
- return Collections.unmodifiableList(nodes);
- }
-
- private OsVersionStatus.Node nodeFromSlime(Inspector object) {
- return new OsVersionStatus.Node(
- HostName.from(object.field(hostnameField).asString()),
- Version.fromString(object.field(versionField).asString()),
- Environment.from(object.field(environmentField).asString()),
- RegionName.from(object.field(regionField).asString())
- );
+ // TODO(mpolden): Simplify and in-line after September 2019
+ private NodeVersions nodesFromSlime(Inspector object, Version version) {
+ var newField = object.field(nodeVersionsField);
+ if (newField.valid()) {
+ return nodeVersionSerializer.nodeVersionsFromSlime(newField, version);
+ }
+ return nodeVersionSerializer.nodeVersionsFromSlime(object.field(nodesField), version);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
index 5061f32da68..366e2c9af4b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
@@ -1,16 +1,13 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
-import com.google.common.collect.ImmutableMap;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.HostName;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.controller.versions.DeploymentStatistics;
-import com.yahoo.vespa.hosted.controller.versions.NodeVersion;
import com.yahoo.vespa.hosted.controller.versions.NodeVersions;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
@@ -19,9 +16,8 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Set;
+import java.util.Objects;
/**
* Serializer for {@link VersionStatus}.
@@ -53,17 +49,18 @@ public class VersionStatusSerializer {
// NodeVersions fields
private static final String nodeVersionsField = "nodeVersions";
- // NodeVersion fields
- private static final String hostnameField = "hostname";
- private static final String wantedVersionField = "wantedVersion";
- private static final String changedAtField = "changedAt";
-
// DeploymentStatistics fields
private static final String versionField = "version";
private static final String failingField = "failing";
private static final String productionField = "production";
private static final String deployingField = "deploying";
+ private final NodeVersionSerializer nodeVersionSerializer;
+
+ public VersionStatusSerializer(NodeVersionSerializer nodeVersionSerializer) {
+ this.nodeVersionSerializer = Objects.requireNonNull(nodeVersionSerializer, "nodeVersionSerializer must be non-null");
+ }
+
public Slime toSlime(VersionStatus status) {
Slime slime = new Slime();
Cursor root = slime.setObject();
@@ -88,22 +85,11 @@ public class VersionStatusSerializer {
object.setBool(isReleasedField, version.isReleased());
deploymentStatisticsToSlime(version.statistics(), object.setObject(deploymentStatisticsField));
object.setString(confidenceField, version.confidence().name());
- configServersToSlime(version.nodeVersions().hostnames(), object.setArray(configServersField));
nodeVersionsToSlime(version.nodeVersions(), object.setArray(nodeVersionsField));
}
private void nodeVersionsToSlime(NodeVersions nodeVersions, Cursor array) {
- for (NodeVersion nodeVersion : nodeVersions.asMap().values()) {
- var nodeVersionObject = array.addObject();
- nodeVersionObject.setString(hostnameField, nodeVersion.hostname().value());
- nodeVersionObject.setString(wantedVersionField, nodeVersion.wantedVersion().toFullString());
- nodeVersionObject.setLong(changedAtField, nodeVersion.changedAt().toEpochMilli());
- }
- }
-
- // TODO(mpolden): Remove after October 2019
- private void configServersToSlime(Set<HostName> configServerHostnames, Cursor array) {
- configServerHostnames.stream().map(HostName::value).forEach(array::addString);
+ nodeVersionSerializer.nodeVersionsToSlime(nodeVersions, array);
}
private void deploymentStatisticsToSlime(DeploymentStatistics statistics, Cursor object) {
@@ -131,37 +117,11 @@ public class VersionStatusSerializer {
object.field(isControllerVersionField).asBool(),
object.field(isSystemVersionField).asBool(),
object.field(isReleasedField).asBool(),
- nodeVersionsFromSlime(object, deploymentStatistics.version()),
+ nodeVersionSerializer.nodeVersionsFromSlime(object.field(nodeVersionsField), deploymentStatistics.version()),
VespaVersion.Confidence.valueOf(object.field(confidenceField).asString())
);
}
- private NodeVersions nodeVersionsFromSlime(Inspector root, Version version) {
- var nodeVersions = ImmutableMap.<HostName, NodeVersion>builder();
- var nodeVersionsRoot = root.field(nodeVersionsField);
- if (nodeVersionsRoot.valid()) {
- nodeVersionsRoot.traverse((ArrayTraverser) (i, entry) -> {
- var hostname = HostName.from(entry.field(hostnameField).asString());
- var wantedVersion = Version.fromString(entry.field(wantedVersionField).asString());
- var changedAt = Instant.ofEpochMilli(entry.field(changedAtField).asLong());
- nodeVersions.put(hostname, new NodeVersion(hostname, version, wantedVersion, changedAt));
- });
- } else {
- // TODO(mpolden): Remove after October 2019
- var configServerHostnames = configServersFromSlime(root.field(configServersField));
- for (var hostname : configServerHostnames) {
- nodeVersions.put(hostname, NodeVersion.empty(hostname));
- }
- }
- return new NodeVersions(nodeVersions.build());
- }
-
- private Set<HostName> configServersFromSlime(Inspector array) {
- Set<HostName> configServerHostnames = new LinkedHashSet<>();
- array.traverse((ArrayTraverser) (i, entry) -> configServerHostnames.add(HostName.from(entry.asString())));
- return Collections.unmodifiableSet(configServerHostnames);
- }
-
private DeploymentStatistics deploymentStatisticsFromSlime(Inspector object) {
return new DeploymentStatistics(Version.fromString(object.field(versionField).asString()),
applicationsFromSlime(object.field(failingField)),
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 b76d0ae1094..c37309b87ad 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
@@ -102,7 +102,6 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -381,9 +380,13 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Principal user = request.getJDiscRequest().getUserPrincipal();
String pemDeveloperKey = toSlime(request.getData()).get().field("key").asString();
PublicKey developerKey = KeyUtils.fromPemEncodedPublicKey(pemDeveloperKey);
- controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, tenant ->
- controller.tenants().store(tenant.withDeveloperKey(developerKey, user)));
- return new MessageResponse("Set developer key " + pemDeveloperKey + " for " + user);
+ Slime root = new Slime();
+ controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, tenant -> {
+ tenant = tenant.withDeveloperKey(developerKey, user);
+ toSlime(root.setObject().setArray("keys"), tenant.get().developerKeys());
+ controller.tenants().store(tenant);
+ });
+ return new SlimeJsonResponse(root);
}
private HttpResponse removeDeveloperKey(String tenantName, HttpRequest request) {
@@ -393,27 +396,49 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
String pemDeveloperKey = toSlime(request.getData()).get().field("key").asString();
PublicKey developerKey = KeyUtils.fromPemEncodedPublicKey(pemDeveloperKey);
Principal user = ((CloudTenant) controller.tenants().require(TenantName.from(tenantName))).developerKeys().get(developerKey);
- controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, tenant ->
- controller.tenants().store(tenant.withoutDeveloperKey(developerKey)));
- return new MessageResponse("Removed developer key " + pemDeveloperKey + " for " + user);
+ Slime root = new Slime();
+ controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, tenant -> {
+ tenant = tenant.withoutDeveloperKey(developerKey);
+ toSlime(root.setObject().setArray("keys"), tenant.get().developerKeys());
+ controller.tenants().store(tenant);
+ });
+ return new SlimeJsonResponse(root);
+ }
+
+ private void toSlime(Cursor keysArray, Map<PublicKey, Principal> keys) {
+ keys.forEach((key, principal) -> {
+ Cursor keyObject = keysArray.addObject();
+ keyObject.setString("key", KeyUtils.toPem(key));
+ keyObject.setString("user", principal.getName());
+ });
}
private HttpResponse addDeployKey(String tenantName, String applicationName, HttpRequest request) {
String pemDeployKey = toSlime(request.getData()).get().field("key").asString();
PublicKey deployKey = KeyUtils.fromPemEncodedPublicKey(pemDeployKey);
- controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(tenantName, applicationName), application ->
- controller.applications().store(application.withDeployKey(deployKey)));
-
- return new MessageResponse("Added deploy key " + pemDeployKey);
+ Slime root = new Slime();
+ controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(tenantName, applicationName), application -> {
+ application = application.withDeployKey(deployKey);
+ application.get().deployKeys().stream()
+ .map(KeyUtils::toPem)
+ .forEach(root.setObject().setArray("keys")::addString);
+ controller.applications().store(application);
+ });
+ return new SlimeJsonResponse(root);
}
private HttpResponse removeDeployKey(String tenantName, String applicationName, HttpRequest request) {
String pemDeployKey = toSlime(request.getData()).get().field("key").asString();
PublicKey deployKey = KeyUtils.fromPemEncodedPublicKey(pemDeployKey);
- controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(tenantName, applicationName), application ->
- controller.applications().store(application.withoutDeployKey(deployKey)));
-
- return new MessageResponse("Removed deploy key " + pemDeployKey);
+ Slime root = new Slime();
+ controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(tenantName, applicationName), application -> {
+ application = application.withoutDeployKey(deployKey);
+ application.get().deployKeys().stream()
+ .map(KeyUtils::toPem)
+ .forEach(root.setObject().setArray("keys")::addString);
+ controller.applications().store(application);
+ });
+ return new SlimeJsonResponse(root);
}
private HttpResponse patchApplication(String tenantName, String applicationName, HttpRequest request) {
@@ -752,7 +777,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
deployment.activity().lastWritesPerSecond().ifPresent(value -> activity.setDouble("lastWritesPerSecond", value));
// Cost
- DeploymentCost appCost = deployment.calculateCost();
+ DeploymentCost appCost = new DeploymentCost(Map.of());
Cursor costObject = response.setObject("cost");
toSlime(appCost, costObject);
@@ -1321,7 +1346,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(testConfigSerializer.configSlime(id,
type,
controller.applications().clusterEndpoints(id, zones),
- controller.applications().listClusters(id, zones)));
+ controller.applications().contentClustersByZone(id, zones)));
}
private static DeploymentJobs.JobReport toJobReport(String tenantName, String applicationName, Inspector report) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
index 450f4481c5f..c168a057bfb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
@@ -160,17 +160,17 @@ public class OsApiHandler extends AuditLoggingRequestHandler {
Set<OsVersion> osVersions = controller.osVersions();
Cursor versions = root.setArray("versions");
- controller.osVersionStatus().versions().forEach((osVersion, nodes) -> {
+ controller.osVersionStatus().versions().forEach((osVersion, nodeVersions) -> {
Cursor currentVersionObject = versions.addObject();
currentVersionObject.setString("version", osVersion.version().toFullString());
currentVersionObject.setBool("targetVersion", osVersions.contains(osVersion));
currentVersionObject.setString("cloud", osVersion.cloud().value());
Cursor nodesArray = currentVersionObject.setArray("nodes");
- nodes.forEach(node -> {
+ nodeVersions.asMap().values().forEach(nodeVersion -> {
Cursor nodeObject = nodesArray.addObject();
- nodeObject.setString("hostname", node.hostname().value());
- nodeObject.setString("environment", node.environment().value());
- nodeObject.setString("region", node.region().value());
+ nodeObject.setString("hostname", nodeVersion.hostname().value());
+ nodeObject.setString("environment", nodeVersion.zone().environment().value());
+ nodeObject.setString("region", nodeVersion.zone().region().value());
});
});
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 a16ca5cb201..9f6bbcd2a5a 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
@@ -77,22 +77,25 @@ 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 deploymentSpec The deployment spec for the application
- * @param instance The instance requesting a rotation
- * @param lock Lock which must be acquired by the caller
+ * @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(DeploymentSpec deploymentSpec, Instance instance, RotationLock lock) {
if ( ! instance.rotations().isEmpty()) {
return allRotations.get(instance.rotations().get(0).rotationId());
}
- if (deploymentSpec.globalServiceId().isEmpty()) {
- throw new IllegalArgumentException("global-service-id is not set in deployment spec");
+
+ if (deploymentSpec.requireInstance(instance.name()).globalServiceId().isEmpty()) {
+ throw new IllegalArgumentException("global-service-id is not set in deployment spec for instance '" +
+ instance.name() + "'");
}
- long productionZones = deploymentSpec.zones().stream()
- .filter(zone -> zone.deploysTo(Environment.prod))
- .count();
+ long productionZones = deploymentSpec.requireInstance(instance.name()).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");
+ throw new IllegalArgumentException("global-service-id is set but less than 2 prod zones are defined " +
+ "in instance '" + instance.name() + "'");
}
return findAvailableRotation(instance.id(), lock);
}
@@ -110,22 +113,23 @@ public class RotationRepository {
* @return List of rotation assignments - either new or existing
*/
public List<AssignedRotation> getOrAssignRotations(DeploymentSpec deploymentSpec, Instance instance, RotationLock lock) {
- if (deploymentSpec.globalServiceId().isPresent() && ! deploymentSpec.endpoints().isEmpty()) {
+ if (deploymentSpec.requireInstance(instance.name()).globalServiceId().isPresent()
+ && ! deploymentSpec.requireInstance(instance.name()).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 (deploymentSpec.globalServiceId().isPresent()) {
- final var regions = deploymentSpec.zones().stream()
- .filter(zone -> zone.environment().isProduction())
- .flatMap(zone -> zone.region().stream())
- .collect(Collectors.toSet());
+ if (deploymentSpec.requireInstance(instance.name()).globalServiceId().isPresent()) {
+ var regions = deploymentSpec.requireInstance(instance.name()).zones().stream()
+ .filter(zone -> zone.environment().isProduction())
+ .flatMap(zone -> zone.region().stream())
+ .collect(Collectors.toSet());
- final var rotation = getOrAssignRotation(deploymentSpec, instance, lock);
+ var rotation = getOrAssignRotation(deploymentSpec, instance, lock);
return List.of(
new AssignedRotation(
- new ClusterSpec.Id(deploymentSpec.globalServiceId().get()),
+ new ClusterSpec.Id(deploymentSpec.requireInstance(instance.name()).globalServiceId().get()),
EndpointId.default_(),
rotation.id(),
regions
@@ -133,8 +137,8 @@ public class RotationRepository {
);
}
- final Map<EndpointId, AssignedRotation> existingAssignments = existingEndpointAssignments(deploymentSpec, instance);
- final Map<EndpointId, AssignedRotation> updatedAssignments = assignRotationsToEndpoints(deploymentSpec, existingAssignments, lock);
+ Map<EndpointId, AssignedRotation> existingAssignments = existingEndpointAssignments(deploymentSpec, instance);
+ Map<EndpointId, AssignedRotation> updatedAssignments = assignRotationsToEndpoints(deploymentSpec, existingAssignments, lock);
existingAssignments.putAll(updatedAssignments);
@@ -142,11 +146,11 @@ public class RotationRepository {
}
private Map<EndpointId, AssignedRotation> assignRotationsToEndpoints(DeploymentSpec deploymentSpec, Map<EndpointId, AssignedRotation> existingAssignments, RotationLock lock) {
- final var availableRotations = new ArrayList<>(availableRotations(lock).values());
+ var availableRotations = new ArrayList<>(availableRotations(lock).values());
- final var neededRotations = deploymentSpec.endpoints().stream()
- .filter(Predicate.not(endpoint -> existingAssignments.containsKey(EndpointId.of(endpoint.endpointId()))))
- .collect(Collectors.toSet());
+ 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());
@@ -172,34 +176,26 @@ public class RotationRepository {
}
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 deploymentSpec.endpoints().stream()
+ Function<EndpointId, Set<RegionName>> configuredRegionsForEndpoint = endpointId ->
+ deploymentSpec.requireInstance(instance.name()).endpoints().stream()
.filter(endpoint -> endpointId.id().equals(endpoint.endpointId()))
.map(Endpoint::regions)
.findFirst()
.orElse(Set.of());
- };
- //
// Build a new AssignedRotation instance where we update set of regions from the configuration instead
- // of using the one already mentioned in the assignment. This allows us to overwrite the set of regions
- // when
- final Function<AssignedRotation, AssignedRotation> assignedRotationWithConfiguredRegions = assignedRotation -> {
- return new AssignedRotation(
+ // of using the one already mentioned in the assignment. This allows us to overwrite the set of regions.
+ Function<AssignedRotation, AssignedRotation> assignedRotationWithConfiguredRegions = assignedRotation ->
+ new AssignedRotation(
assignedRotation.clusterId(),
assignedRotation.endpointId(),
assignedRotation.rotationId(),
- configuredRegionsForEndpoint.apply(assignedRotation.endpointId())
- );
- };
+ configuredRegionsForEndpoint.apply(assignedRotation.endpointId()));
return instance.rotations().stream()
- .collect(
- Collectors.toMap(
+ .collect(Collectors.toMap(
AssignedRotation::endpointId,
assignedRotationWithConfiguredRegions,
(a, b) -> {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java
index 0a690b90410..8d0232afa58 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.zone.ZoneId;
import java.time.Instant;
import java.util.Objects;
@@ -17,12 +18,14 @@ import java.util.Objects;
public class NodeVersion {
private final HostName hostname;
+ private final ZoneId zone;
private final Version currentVersion;
private final Version wantedVersion;
private final Instant changedAt;
- public NodeVersion(HostName hostname, Version currentVersion, Version wantedVersion, Instant changedAt) {
+ public NodeVersion(HostName hostname, ZoneId zone, Version currentVersion, Version wantedVersion, Instant changedAt) {
this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null");
+ this.zone = Objects.requireNonNull(zone, "zone must be non-null");
this.currentVersion = Objects.requireNonNull(currentVersion, "version must be non-null");
this.wantedVersion = Objects.requireNonNull(wantedVersion, "wantedVersion must be non-null");
this.changedAt = Objects.requireNonNull(changedAt, "changedAt must be non-null");
@@ -33,6 +36,11 @@ public class NodeVersion {
return hostname;
}
+ /** Zone of this */
+ public ZoneId zone() {
+ return zone;
+ }
+
/** Current version of this */
public Version currentVersion() {
return currentVersion;
@@ -56,18 +64,18 @@ public class NodeVersion {
/** Returns a copy of this with current version set to given version */
public NodeVersion withCurrentVersion(Version version, Instant changedAt) {
if (currentVersion.equals(version)) return this;
- return new NodeVersion(hostname, version, wantedVersion, changedAt);
+ return new NodeVersion(hostname, zone, version, wantedVersion, changedAt);
}
/** Returns a copy of this with wanted version set to given version */
public NodeVersion withWantedVersion(Version version) {
if (wantedVersion.equals(version)) return this;
- return new NodeVersion(hostname, currentVersion, version, changedAt);
+ return new NodeVersion(hostname, zone, currentVersion, version, changedAt);
}
@Override
public String toString() {
- return hostname + ": " + currentVersion + " -> " + wantedVersion + " [changedAt=" + changedAt + "]";
+ return hostname + ": " + currentVersion + " -> " + wantedVersion + " [zone=" + zone + ", changedAt=" + changedAt + "]";
}
@Override
@@ -76,6 +84,7 @@ public class NodeVersion {
if (o == null || getClass() != o.getClass()) return false;
NodeVersion that = (NodeVersion) o;
return hostname.equals(that.hostname) &&
+ zone.equals(that.zone) &&
currentVersion.equals(that.currentVersion) &&
wantedVersion.equals(that.wantedVersion) &&
changedAt.equals(that.changedAt);
@@ -83,11 +92,11 @@ public class NodeVersion {
@Override
public int hashCode() {
- return Objects.hash(hostname, currentVersion, wantedVersion, changedAt);
+ return Objects.hash(hostname, zone, currentVersion, wantedVersion, changedAt);
}
public static NodeVersion empty(HostName hostname) {
- return new NodeVersion(hostname, Version.emptyVersion, Version.emptyVersion, Instant.EPOCH);
+ return new NodeVersion(hostname, ZoneId.defaultId(), Version.emptyVersion, Version.emptyVersion, Instant.EPOCH);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
index a73a20198f0..d5e83d99cdd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
@@ -4,9 +4,7 @@ package com.yahoo.vespa.hosted.controller.versions;
import com.google.common.collect.ImmutableMap;
import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
-import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
@@ -14,11 +12,11 @@ import com.yahoo.vespa.hosted.controller.maintenance.OsUpgrader;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -29,25 +27,25 @@ import java.util.stream.Collectors;
*/
public class OsVersionStatus {
- public static final OsVersionStatus empty = new OsVersionStatus(Collections.emptyMap());
+ public static final OsVersionStatus empty = new OsVersionStatus(ImmutableMap.of());
- private final Map<OsVersion, List<Node>> versions;
+ private final Map<OsVersion, NodeVersions> versions;
/** Public for serialization purpose only. Use {@link OsVersionStatus#compute(Controller)} for an up-to-date status */
- public OsVersionStatus(Map<OsVersion, List<Node>> versions) {
+ public OsVersionStatus(ImmutableMap<OsVersion, NodeVersions> versions) {
this.versions = ImmutableMap.copyOf(Objects.requireNonNull(versions, "versions must be non-null"));
}
/** All known OS versions and their nodes */
- public Map<OsVersion, List<Node>> versions() {
+ public Map<OsVersion, NodeVersions> versions() {
return versions;
}
/** Returns nodes eligible for OS upgrades that exist in given cloud */
- public List<Node> nodesIn(CloudName cloud) {
+ public List<NodeVersion> nodesIn(CloudName cloud) {
return versions.entrySet().stream()
.filter(entry -> entry.getKey().cloud().equals(cloud))
- .flatMap(entry -> entry.getValue().stream())
+ .flatMap(entry -> entry.getValue().asMap().values().stream())
.collect(Collectors.toUnmodifiableList());
}
@@ -61,28 +59,52 @@ public class OsVersionStatus {
/** Compute the current OS versions in this system. This is expensive and should be called infrequently */
public static OsVersionStatus compute(Controller controller) {
- Map<OsVersion, List<Node>> versions = new HashMap<>();
-
- // Always include all target versions
- controller.osVersions().forEach(osVersion -> versions.put(osVersion, new ArrayList<>()));
-
- for (SystemApplication application : SystemApplication.all()) {
- if (!application.isEligibleForOsUpgrades()) {
- continue; // Avoid querying applications that are not eligible for OS upgrades
- }
- for (ZoneApi zone : zonesToUpgrade(controller)) {
- controller.serviceRegistry().configServer().nodeRepository().list(zone.getId(), application.id()).stream()
+ var osVersionStatus = controller.osVersionStatus();
+ var osVersions = new HashMap<OsVersion, List<NodeVersion>>();
+ var now = controller.clock().instant();
+ controller.osVersions().forEach(osVersion -> osVersions.put(osVersion, new ArrayList<>()));
+
+ for (var application : SystemApplication.all()) {
+ if (!application.isEligibleForOsUpgrades()) continue;
+ for (var zone : zonesToUpgrade(controller)) {
+ var targetOsVersion = controller.serviceRegistry().configServer().nodeRepository()
+ .targetVersionsOf(zone.getId())
+ .osVersion(application.nodeType())
+ .orElse(Version.emptyVersion);
+ controller.serviceRegistry().configServer().nodeRepository()
+ .list(zone.getId(), application.id()).stream()
.filter(node -> OsUpgrader.eligibleForUpgrade(node, application))
- .map(node -> new Node(node.hostname(), node.currentOsVersion(), zone.getEnvironment(), zone.getRegionName()))
- .forEach(node -> {
- var version = new OsVersion(node.version(), zone.getCloudName());
- versions.putIfAbsent(version, new ArrayList<>());
- versions.get(version).add(node);
+ .map(node -> new NodeVersion(node.hostname(), zone.getId(), node.currentOsVersion(), targetOsVersion, now))
+ .forEach(nodeVersion -> {
+ var newNodeVersion = osVersionStatus.of(nodeVersion.hostname())
+ .map(nv -> nv.withCurrentVersion(nodeVersion.currentVersion(), now)
+ .withWantedVersion(nodeVersion.wantedVersion()))
+ .orElse(nodeVersion);
+ var version = new OsVersion(newNodeVersion.currentVersion(), zone.getCloudName());
+ osVersions.putIfAbsent(version, new ArrayList<>());
+ osVersions.get(version).add(newNodeVersion);
});
}
}
- return new OsVersionStatus(versions);
+ var newOsVersions = ImmutableMap.<OsVersion, NodeVersions>builder();
+ for (var osVersion : osVersions.entrySet()) {
+ var nodeVersions = ImmutableMap.<HostName, NodeVersion>builder();
+ for (var nodeVersion : osVersion.getValue()) {
+ nodeVersions.put(nodeVersion.hostname(), nodeVersion);
+ }
+ newOsVersions.put(osVersion.getKey(), new NodeVersions(nodeVersions.build()));
+ }
+ return new OsVersionStatus(newOsVersions.build());
+ }
+
+ /** Returns version of node identified by given host name */
+ private Optional<NodeVersion> of(HostName hostname) {
+ return versions.values().stream()
+ .map(nodeVersions -> nodeVersions.asMap().get(hostname))
+ .map(Optional::ofNullable)
+ .flatMap(Optional::stream)
+ .findFirst();
}
private static List<ZoneApi> zonesToUpgrade(Controller controller) {
@@ -92,52 +114,4 @@ public class OsVersionStatus {
.collect(Collectors.toUnmodifiableList());
}
- /** A node in this system and its current OS version */
- public static class Node {
-
- private final HostName hostname;
- private final Version version;
- private final Environment environment;
- private final RegionName region;
-
- public Node(HostName hostname, Version version, Environment environment, RegionName region) {
- this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null");
- this.version = Objects.requireNonNull(version, "version must be non-null");
- this.environment = Objects.requireNonNull(environment, "environment must be non-null");
- this.region = Objects.requireNonNull(region, "region must be non-null");
- }
-
- public HostName hostname() {
- return hostname;
- }
-
- public Version version() {
- return version;
- }
-
- public Environment environment() {
- return environment;
- }
-
- public RegionName region() {
- return region;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Node node = (Node) o;
- return Objects.equals(hostname, node.hostname) &&
- Objects.equals(version, node.version) &&
- environment == node.environment &&
- Objects.equals(region, node.region);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(hostname, version, environment, region);
- }
- }
-
}
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 bb43ec20234..ab445de5a7f 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
@@ -172,7 +172,7 @@ public class VersionStatus {
for (var node : nodes) {
// Only use current node version if config has converged
Version version = configConverged ? node.currentVersion() : controller.systemVersion();
- newNodeVersions.add(new NodeVersion(node.hostname(), version, node.wantedVersion(), now));
+ newNodeVersions.add(new NodeVersion(node.hostname(), zone.getId(), version, node.wantedVersion(), now));
}
}
}
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 ebf80eb9daa..e3682a78b7d 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
@@ -72,7 +72,6 @@ public class ControllerTest {
@Test
public void testDeployment() {
// Setup system
- ApplicationController applications = tester.controller().applications();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-west-1")
@@ -753,7 +752,7 @@ public class ControllerTest {
tester.deployCompletely(application, applicationPackage);
fail("Expected exception");
} catch (IllegalArgumentException e) {
- assertEquals("Endpoint 'default' cannot contain regions in different clouds: [aws-us-east-1, us-west-1]", e.getMessage());
+ assertEquals("Endpoint 'default' in instance 'default' cannot contain regions in different clouds: [aws-us-east-1, us-west-1]", e.getMessage());
}
var applicationPackage2 = new ApplicationPackageBuilder()
@@ -766,7 +765,7 @@ public class ControllerTest {
tester.deployCompletely(application, applicationPackage2);
fail("Expected exception");
} catch (IllegalArgumentException e) {
- assertEquals("Endpoint 'foo' cannot contain regions in different clouds: [aws-us-east-1, us-west-1]", e.getMessage());
+ assertEquals("Endpoint 'foo' in instance 'default' cannot contain regions in different clouds: [aws-us-east-1, us-west-1]", e.getMessage());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
index 25e562ed046..9449f2b0854 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
@@ -47,6 +47,7 @@ public class ApplicationPackageBuilder {
private final List<X509Certificate> trustedCertificates = new ArrayList<>();
private OptionalInt majorVersion = OptionalInt.empty();
+ private String instances = "default";
private String upgradePolicy = null;
private Environment environment = Environment.prod;
private String globalServiceId = null;
@@ -58,6 +59,11 @@ public class ApplicationPackageBuilder {
return this;
}
+ public ApplicationPackageBuilder instances(String instances) {
+ this.instances = instances;
+ return this;
+ }
+
public ApplicationPackageBuilder upgradePolicy(String upgradePolicy) {
this.upgradePolicy = upgradePolicy;
return this;
@@ -90,7 +96,7 @@ public class ApplicationPackageBuilder {
}
public ApplicationPackageBuilder region(String regionName) {
- environmentBody.append(" <region active='true'>");
+ environmentBody.append(" <region active='true'>");
environmentBody.append(regionName);
environmentBody.append("</region>\n");
return this;
@@ -112,7 +118,7 @@ public class ApplicationPackageBuilder {
public ApplicationPackageBuilder blockChange(boolean revision, boolean version, String daySpec, String hourSpec,
String zoneSpec) {
- blockChange.append(" <block-change");
+ blockChange.append(" <block-change");
blockChange.append(" revision='").append(revision).append("'");
blockChange.append(" version='").append(version).append("'");
blockChange.append(" days='").append(daySpec).append("'");
@@ -166,14 +172,15 @@ public class ApplicationPackageBuilder {
xml.append(athenzIdentityAttributes);
}
xml.append(">\n");
+ xml.append(" <instance id='").append(instances).append("'>\n");
if (upgradePolicy != null) {
- xml.append("<upgrade policy='");
+ xml.append(" <upgrade policy='");
xml.append(upgradePolicy);
xml.append("'/>\n");
}
xml.append(notifications);
xml.append(blockChange);
- xml.append(" <");
+ xml.append(" <");
xml.append(environment.value());
if (globalServiceId != null) {
xml.append(" global-service-id='");
@@ -182,13 +189,14 @@ public class ApplicationPackageBuilder {
}
xml.append(">\n");
xml.append(environmentBody);
- xml.append(" </");
+ xml.append(" </");
xml.append(environment.value());
xml.append(">\n");
- xml.append(" <endpoints>\n");
+ xml.append(" <endpoints>\n");
xml.append(endpointsBody);
- xml.append(" </endpoints>\n");
- xml.append("</deployment>");
+ xml.append(" </endpoints>\n");
+ xml.append(" </instance>\n");
+ xml.append("</deployment>\n");
return xml.toString().getBytes(UTF_8);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
index 6da77a967f1..6e7a50b5f81 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
@@ -151,15 +151,44 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
/** Set version for an application in a given zone */
public void setVersion(ApplicationId application, ZoneId zone, Version version) {
- setVersion(application, zone, version, -1);
+ setVersion(application, zone, version, -1, false);
}
/** Set version for nodeCount number of nodes in application in a given zone */
public void setVersion(ApplicationId application, ZoneId zone, Version version, int nodeCount) {
+ setVersion(application, zone, version, nodeCount, false);
+ }
+
+ /** Set OS version for an application in a given zone */
+ public void setOsVersion(ApplicationId application, ZoneId zone, Version version) {
+ setOsVersion(application, zone, version, -1);
+ }
+
+ /** Set OS version for an application in a given zone */
+ public void setOsVersion(ApplicationId application, ZoneId zone, Version version, int nodeCount) {
+ setVersion(application, zone, version, nodeCount, true);
+ }
+
+ private void setVersion(ApplicationId application, ZoneId zone, Version version, int nodeCount, boolean osVersion) {
int n = 0;
for (Node node : nodeRepository().list(zone, application)) {
- nodeRepository().putByHostname(zone, new Node(node.hostname(), node.state(), node.type(), node.owner(),
- version, version));
+ Node newNode;
+ if (osVersion) {
+ newNode = new Node(node.hostname(), node.state(), node.type(), node.owner(), node.currentVersion(),
+ node.wantedVersion(), version, version, node.serviceState(),
+ node.restartGeneration(), node.wantedRestartGeneration(), node.rebootGeneration(),
+ node.wantedRebootGeneration(), node.vcpu(), node.memoryGb(), node.diskGb(),
+ node.bandwidthGbps(), node.fastDisk(), node.cost(), node.canonicalFlavor(),
+ node.clusterId(), node.clusterType());
+ } else {
+ newNode = new Node(node.hostname(), node.state(), node.type(), node.owner(), version,
+ version, node.currentOsVersion(), node.wantedOsVersion(), node.serviceState(),
+ node.restartGeneration(), node.wantedRestartGeneration(), node.rebootGeneration(),
+ node.wantedRebootGeneration(), node.vcpu(), node.memoryGb(), node.diskGb(),
+ node.bandwidthGbps(), node.fastDisk(), node.cost(), node.canonicalFlavor(),
+ node.clusterId(), node.clusterType());
+ }
+ nodeRepository().putByHostname(zone, newNode);
if (++n == nodeCount) break;
}
}
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 9cb40d60677..44785407874 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
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.zone.UpgradePolicy;
import com.yahoo.config.provision.zone.ZoneId;
@@ -262,6 +263,57 @@ public class MetricsReporterTest {
}
}
+ @Test
+ public void test_nodes_failing_os_upgrade() {
+ var tester = new DeploymentTester();
+ var reporter = createReporter(tester.controller());
+ var zone = ZoneApiMock.fromId("prod.eu-west-1");
+ var cloud = CloudName.defaultName();
+ tester.controllerTester().zoneRegistry().setOsUpgradePolicy(cloud, UpgradePolicy.create().upgrade(zone));
+ var osUpgrader = new OsUpgrader(tester.controller(), Duration.ofDays(1),
+ new JobControl(tester.controllerTester().curator()), CloudName.defaultName());;
+ var statusUpdater = new OsVersionStatusUpdater(tester.controller(), Duration.ofDays(1),
+ new JobControl(tester.controller().curator()));
+ tester.configServer().bootstrap(List.of(zone.getId()), SystemApplication.tenantHost);
+
+ // All nodes upgrade to initial OS version
+ var version0 = Version.fromString("8.0");
+ tester.controller().upgradeOsIn(cloud, version0, false);
+ osUpgrader.maintain();
+ tester.configServer().setOsVersion(SystemApplication.tenantHost.id(), zone.getId(), version0);
+ statusUpdater.maintain();
+ reporter.maintain();
+ assertEquals(0, getNodesFailingOsUpgrade());
+
+ for (var version : List.of(Version.fromString("8.1"), Version.fromString("8.2"))) {
+ // System starts upgrading to next OS version
+ tester.controller().upgradeOsIn(cloud, version, false);
+ osUpgrader.maintain();
+ statusUpdater.maintain();
+ reporter.maintain();
+ assertEquals(0, getNodesFailingOsUpgrade());
+
+ // 30 minutes pass and nothing happens
+ tester.clock().advance(Duration.ofMinutes(30));
+ statusUpdater.maintain();
+ reporter.maintain();
+ assertEquals(0, getNodesFailingOsUpgrade());
+
+ // 1/3 nodes upgrade within timeout
+ tester.configServer().setOsVersion(SystemApplication.tenantHost.id(), zone.getId(), version, 1);
+ tester.clock().advance(Duration.ofMinutes(30).plus(Duration.ofSeconds(1)));
+ statusUpdater.maintain();
+ reporter.maintain();
+ assertEquals(2, getNodesFailingOsUpgrade());
+
+ // 3/3 nodes upgrade
+ tester.configServer().setOsVersion(SystemApplication.tenantHost.id(), zone.getId(), version);
+ statusUpdater.maintain();
+ reporter.maintain();
+ assertEquals(0, getNodesFailingOsUpgrade());
+ }
+ }
+
private Duration getAverageDeploymentDuration(ApplicationId id) {
return Duration.ofSeconds(getMetric(MetricsReporter.DEPLOYMENT_AVERAGE_DURATION, id).longValue());
}
@@ -278,6 +330,10 @@ public class MetricsReporterTest {
return metrics.getMetric(MetricsReporter.NODES_FAILING_SYSTEM_UPGRADE).intValue();
}
+ private int getNodesFailingOsUpgrade() {
+ return metrics.getMetric(MetricsReporter.NODES_FAILING_OS_UPGRADE).intValue();
+ }
+
private Number getMetric(String name, ApplicationId id) {
return metrics.getMetric((dimensions) -> id.tenant().value().equals(dimensions.get("tenant")) &&
appDimension(id).equals(dimensions.get("app")),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
index 1af5fafbb79..5e92112d465 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
@@ -12,7 +12,7 @@ import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
-import com.yahoo.vespa.hosted.controller.versions.OsVersionStatus;
+import com.yahoo.vespa.hosted.controller.versions.NodeVersion;
import org.junit.Before;
import org.junit.Test;
@@ -111,13 +111,13 @@ public class OsUpgraderTest {
assertWanted(version1, SystemApplication.tenantHost, zone1.getId(), zone2.getId(), zone3.getId(), zone4.getId());
statusUpdater.maintain();
assertTrue("All nodes on target version", tester.controller().osVersionStatus().nodesIn(cloud).stream()
- .allMatch(node -> node.version().equals(version1)));
+ .allMatch(node -> node.currentVersion().equals(version1)));
}
- private List<OsVersionStatus.Node> nodesOn(Version version) {
+ private List<NodeVersion> nodesOn(Version version) {
return tester.controller().osVersionStatus().versions().entrySet().stream()
.filter(entry -> entry.getKey().version().equals(version))
- .flatMap(entry -> entry.getValue().stream())
+ .flatMap(entry -> entry.getValue().asMap().values().stream())
.collect(Collectors.toList());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
index fe7f39fd66d..e51fcff33d1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
@@ -3,18 +3,15 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
+import com.yahoo.config.provision.zone.UpgradePolicy;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.ControllerTester;
-import com.yahoo.config.provision.zone.UpgradePolicy;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import com.yahoo.vespa.hosted.controller.versions.OsVersion;
import com.yahoo.vespa.hosted.controller.versions.OsVersionStatus;
import org.junit.Test;
import java.time.Duration;
-import java.util.List;
-import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -47,10 +44,10 @@ public class OsVersionStatusUpdaterTest {
tester.controller().upgradeOsIn(cloud, version1, false);
statusUpdater.maintain();
- Map<OsVersion, List<OsVersionStatus.Node>> osVersions = tester.controller().osVersionStatus().versions();
+ var osVersions = tester.controller().osVersionStatus().versions();
assertEquals(2, osVersions.size());
- assertFalse("All nodes on unknown version", osVersions.get(new OsVersion(Version.emptyVersion, cloud)).isEmpty());
- assertTrue("No nodes on current target", osVersions.get(new OsVersion(version1, cloud)).isEmpty());
+ assertFalse("All nodes on unknown version", osVersions.get(new OsVersion(Version.emptyVersion, cloud)).asMap().isEmpty());
+ assertTrue("No nodes on current target", osVersions.get(new OsVersion(version1, cloud)).asMap().isEmpty());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
index f28ce83e643..0245e7475f7 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
@@ -11,7 +11,6 @@ import org.junit.Test;
import java.time.Duration;
import java.util.Collection;
-import java.util.List;
import static org.junit.Assert.assertEquals;
@@ -27,20 +26,14 @@ public class ResourceMeterMaintainerTest {
@Test
public void testMaintainer() {
- var awsZone = ZoneApiMock.newBuilder().withId("prod.aws-us-east-1").withCloud("aws").build();
- tester.zoneRegistry().setZones(
- ZoneApiMock.newBuilder().withId("prod.us-east-3").build(),
- ZoneApiMock.newBuilder().withId("prod.us-west-1").build(),
- ZoneApiMock.newBuilder().withId("prod.us-central-1").build(),
- awsZone);
- tester.configServer().nodeRepository().addFixedNodes(awsZone.getId());
+ setUpZones();
ResourceMeterMaintainer resourceMeterMaintainer = new ResourceMeterMaintainer(tester.controller(), Duration.ofMinutes(5), new JobControl(tester.curator()), metrics, snapshotConsumer);
resourceMeterMaintainer.maintain();
Collection<ResourceSnapshot> consumedResources = snapshotConsumer.consumedResources();
// The mocked repository contains two applications, so we should also consume two ResourceSnapshots
- assertEquals(2, consumedResources.size());
+ assertEquals(4, consumedResources.size());
ResourceSnapshot app1 = consumedResources.stream().filter(snapshot -> snapshot.getApplicationId().equals(ApplicationId.from("tenant1", "app1", "default"))).findFirst().orElseThrow();
ResourceSnapshot app2 = consumedResources.stream().filter(snapshot -> snapshot.getApplicationId().equals(ApplicationId.from("tenant2", "app2", "default"))).findFirst().orElseThrow();
@@ -53,7 +46,19 @@ public class ResourceMeterMaintainerTest {
assertEquals(500, app2.getDiskGb(), DELTA);
assertEquals(tester.clock().millis()/1000, metrics.getMetric("metering_last_reported"));
- assertEquals(1112.0d, (Double) metrics.getMetric("metering_total_reported"), DELTA);
+ assertEquals(2224.0d, (Double) metrics.getMetric("metering_total_reported"), DELTA);
}
+ private void setUpZones() {
+ ZoneApiMock nonAwsZone = ZoneApiMock.newBuilder().withId("test.region-1").build();
+ ZoneApiMock awsZone1 = ZoneApiMock.newBuilder().withId("prod.region-2").withCloud("aws").build();
+ ZoneApiMock awsZone2 = ZoneApiMock.newBuilder().withId("test.region-3").withCloud("aws").build();
+ tester.zoneRegistry().setZones(
+ nonAwsZone,
+ awsZone1,
+ awsZone2);
+ tester.configServer().nodeRepository().addFixedNodes(nonAwsZone.getId());
+ tester.configServer().nodeRepository().addFixedNodes(awsZone1.getId());
+ tester.configServer().nodeRepository().addFixedNodes(awsZone2.getId());
+ }
}
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
index 08963b9fec7..447bce0a544 100644
--- 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
@@ -19,7 +19,6 @@ 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;
@@ -92,7 +91,7 @@ public class ApplicationSerializerTest {
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),
+ createClusterInfo(3, 4),
new DeploymentMetrics(2, 3, 4, 5, 6,
Optional.of(Instant.now().truncatedTo(ChronoUnit.MILLIS)),
Map.of(DeploymentMetrics.Warning.all, 3)),
@@ -191,10 +190,6 @@ public class ApplicationSerializerTest {
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(0, serialized.require(id1.instance()).deployments().get(zone2).clusterUtils().size());
-
// 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());
@@ -232,21 +227,6 @@ public class ApplicationSerializerTest {
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"));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java
index 5073f651fd3..ba771d70d26 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java
@@ -1,18 +1,23 @@
// 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.google.common.collect.ImmutableMap;
import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
-import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.hosted.controller.versions.NodeVersion;
+import com.yahoo.vespa.hosted.controller.versions.NodeVersions;
import com.yahoo.vespa.hosted.controller.versions.OsVersion;
import com.yahoo.vespa.hosted.controller.versions.OsVersionStatus;
import org.junit.Test;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Instant;
import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
import static org.junit.Assert.assertEquals;
@@ -25,22 +30,41 @@ public class OsVersionStatusSerializerTest {
public void test_serialization() {
Version version1 = Version.fromString("7.1");
Version version2 = Version.fromString("7.2");
- Map<OsVersion, List<OsVersionStatus.Node>> versions = new TreeMap<>();
+ var versions = ImmutableMap.<OsVersion, NodeVersions>builder();
- versions.put(new OsVersion(version1, CloudName.defaultName()), List.of(
- new OsVersionStatus.Node(HostName.from("node1"), version1, Environment.prod, RegionName.from("us-west")),
- new OsVersionStatus.Node(HostName.from("node2"), version1, Environment.prod, RegionName.from("us-east"))
- ));
- versions.put(new OsVersion(version2, CloudName.defaultName()), List.of(
- new OsVersionStatus.Node(HostName.from("node3"), version2, Environment.prod, RegionName.from("us-west")),
- new OsVersionStatus.Node(HostName.from("node4"), version2, Environment.prod, RegionName.from("us-east"))
+ versions.put(new OsVersion(version1, CloudName.defaultName()), NodeVersions.EMPTY.with(List.of(
+ new NodeVersion(HostName.from("node1"), ZoneId.from("prod", "us-west"), version1, version2, Instant.ofEpochMilli(1)),
+ new NodeVersion(HostName.from("node2"), ZoneId.from("prod", "us-east"), version1, version2, Instant.ofEpochMilli(2))
+ )));
+ versions.put(new OsVersion(version2, CloudName.defaultName()), NodeVersions.EMPTY.with(List.of(
+ new NodeVersion(HostName.from("node3"), ZoneId.from("prod", "us-west"), version2, version2, Instant.ofEpochMilli(3)),
+ new NodeVersion(HostName.from("node4"), ZoneId.from("prod", "us-east"), version2, version2, Instant.ofEpochMilli(4))
+ )));
- ));
-
- OsVersionStatusSerializer serializer = new OsVersionStatusSerializer(new OsVersionSerializer());
- OsVersionStatus status = new OsVersionStatus(versions);
+ OsVersionStatusSerializer serializer = new OsVersionStatusSerializer(new OsVersionSerializer(), new NodeVersionSerializer());
+ OsVersionStatus status = new OsVersionStatus(versions.build());
OsVersionStatus serialized = serializer.fromSlime(serializer.toSlime(status));
assertEquals(status.versions(), serialized.versions());
}
+ @Test
+ public void testLegacySerialization() throws Exception {
+ var data = Files.readAllBytes(Paths.get("src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/os-version-status-legacy-format.json"));
+ var serializer = new OsVersionStatusSerializer(new OsVersionSerializer(), new NodeVersionSerializer());
+ var versions = ImmutableMap.of(
+ new OsVersion(Version.fromString("7.42"), CloudName.from("yahoo")),
+ NodeVersions.EMPTY.with(List.of(new NodeVersion(HostName.from("node1"), ZoneId.from("prod", "us-north-1"),
+ Version.fromString("7.42"), Version.emptyVersion, Instant.EPOCH),
+ new NodeVersion(HostName.from("node2"), ZoneId.from("test", "us-north-2"),
+ Version.fromString("7.42"), Version.emptyVersion, Instant.EPOCH))));
+
+ var deserialized = serializer.fromSlime(SlimeUtils.jsonToSlime(data));
+ assertEquals(versions, deserialized.versions());
+
+
+ var serialized = new String(SlimeUtils.toJsonBytes(serializer.toSlime(new OsVersionStatus(versions))), StandardCharsets.UTF_8);
+ assertEquals("{\"versions\":[{\"version\":\"7.42.0\",\"cloud\":\"yahoo\",\"nodeVersions\":[{\"hostname\":\"node1\",\"zone\":\"prod.us-north-1\",\"wantedVersion\":\"0.0.0\",\"changedAt\":0},{\"hostname\":\"node2\",\"zone\":\"test.us-north-2\",\"wantedVersion\":\"0.0.0\",\"changedAt\":0}],\"nodes\":[{\"hostname\":\"node1\",\"version\":\"7.42.0\",\"region\":\"us-north-1\",\"environment\":\"prod\"},{\"hostname\":\"node2\",\"version\":\"7.42.0\",\"region\":\"us-north-2\",\"environment\":\"test\"}]}]}",
+ serialized);
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
index 5d65cf0381e..a80dcc118dc 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.versions.DeploymentStatistics;
import com.yahoo.vespa.hosted.controller.versions.NodeVersion;
@@ -45,7 +46,7 @@ public class VersionStatusSerializerTest {
false, nodeVersions(Version.fromString("5.0"), Version.fromString("5.1"),
Instant.ofEpochMilli(456), "cfg1", "cfg2", "cfg3"), VespaVersion.Confidence.normal));
VersionStatus status = new VersionStatus(vespaVersions);
- VersionStatusSerializer serializer = new VersionStatusSerializer();
+ VersionStatusSerializer serializer = new VersionStatusSerializer(new NodeVersionSerializer());
VersionStatus deserialized = serializer.fromSlime(serializer.toSlime(status));
assertEquals(status.versions().size(), deserialized.versions().size());
@@ -67,7 +68,7 @@ public class VersionStatusSerializerTest {
@Test
public void testLegacySerialization() throws Exception {
var data = Files.readAllBytes(Paths.get("src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/version-status-legacy-format.json"));
- var serializer = new VersionStatusSerializer();
+ var serializer = new VersionStatusSerializer(new NodeVersionSerializer());
var deserializedStatus = serializer.fromSlime(SlimeUtils.jsonToSlime(data));
var statistics = new DeploymentStatistics(
@@ -76,11 +77,16 @@ public class VersionStatusSerializerTest {
List.of(),
List.of()
);
+ var nodeVersions = List.of(new NodeVersion(HostName.from("cfg1"), ZoneId.defaultId(), Version.fromString("7.0"),
+ Version.fromString("7.1"), Instant.ofEpochMilli(1111)),
+ new NodeVersion(HostName.from("cfg2"), ZoneId.defaultId(), Version.fromString("7.0"),
+ Version.fromString("7.1"), Instant.ofEpochMilli(2222)),
+ new NodeVersion(HostName.from("cfg3"), ZoneId.defaultId(), Version.fromString("7.0"),
+ Version.fromString("7.1"), Instant.ofEpochMilli(3333)));
var vespaVersion = new VespaVersion(statistics, "badc0ffee",
Instant.ofEpochMilli(123), true,
true, true,
- nodeVersions(Version.emptyVersion, Version.emptyVersion,
- Instant.EPOCH, "cfg1", "cfg2", "cfg3"),
+ NodeVersions.EMPTY.with(nodeVersions),
VespaVersion.Confidence.normal);
VespaVersion deserialized = deserializedStatus.versions().get(0);
@@ -97,7 +103,7 @@ public class VersionStatusSerializerTest {
private static NodeVersions nodeVersions(Version version, Version wantedVersion, Instant changedAt, String... hostnames) {
var nodeVersions = new ArrayList<NodeVersion>();
for (var hostname : hostnames) {
- nodeVersions.add(new NodeVersion(HostName.from(hostname), version, wantedVersion, changedAt));
+ nodeVersions.add(new NodeVersion(HostName.from(hostname), ZoneId.from("prod", "us-north-1"), version, wantedVersion, changedAt));
}
return NodeVersions.EMPTY.with(nodeVersions);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/os-version-status-legacy-format.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/os-version-status-legacy-format.json
new file mode 100644
index 00000000000..5a6a864cbf8
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/os-version-status-legacy-format.json
@@ -0,0 +1,22 @@
+{
+ "versions": [
+ {
+ "version": "7.42",
+ "cloud": "yahoo",
+ "nodes": [
+ {
+ "hostname": "node1",
+ "version": "7.42",
+ "region": "us-north-1",
+ "environment": "prod"
+ },
+ {
+ "hostname": "node2",
+ "version": "7.42",
+ "region": "us-north-2",
+ "environment": "test"
+ }
+ ]
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/version-status-legacy-format.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/version-status-legacy-format.json
index 96ca22e1c1a..08463ed7cb4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/version-status-legacy-format.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/version-status-legacy-format.json
@@ -13,10 +13,22 @@
"deploying": []
},
"confidence": "normal",
- "configServerHostnames": [
- "cfg1",
- "cfg2",
- "cfg3"
+ "nodeVersions": [
+ {
+ "hostname": "cfg1",
+ "wantedVersion": "7.1",
+ "changedAt": 1111
+ },
+ {
+ "hostname": "cfg2",
+ "wantedVersion": "7.1",
+ "changedAt": 2222
+ },
+ {
+ "hostname": "cfg3",
+ "wantedVersion": "7.1",
+ "changedAt": 3333
+ }
]
}
]
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 7cacd91a5c4..9c957785606 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
@@ -45,7 +45,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMeteringClien
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
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;
@@ -65,8 +64,6 @@ import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
-import com.yahoo.vespa.hosted.controller.rotation.RotationState;
-import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.yolean.Exceptions;
@@ -84,7 +81,6 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -117,7 +113,18 @@ public class ApplicationApiTest extends ControllerContainerTest {
"-----END PUBLIC KEY-----\n";
private static final String quotedPemPublicKey = pemPublicKey.replaceAll("\\n", "\\\\n");
- private static final ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ private static final ApplicationPackage applicationPackageDefault = new ApplicationPackageBuilder()
+ .instances("default")
+ .environment(Environment.prod)
+ .globalServiceId("foo")
+ .region("us-central-1")
+ .region("us-east-3")
+ .region("us-west-1")
+ .blockChange(false, true, "mon-fri", "0-8", "UTC")
+ .build();
+
+ private static final ApplicationPackage applicationPackageInstance1 = new ApplicationPackageBuilder()
+ .instances("instance1")
.environment(Environment.prod)
.globalServiceId("foo")
.region("us-central-1")
@@ -225,7 +232,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
// POST (deploy) an application to a zone - manual user deployment (includes a content hash for verification)
- MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
+ MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true);
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1/deploy", POST)
.data(entity)
.header("X-Content-Hash", Base64.getEncoder().encodeToString(Signatures.sha256Digest(entity::data)))
@@ -245,7 +252,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
controllerTester.jobCompletion(JobType.component)
.application(id)
.projectId(screwdriverProjectId)
- .uploadArtifact(applicationPackage)
+ .uploadArtifact(applicationPackageInstance1)
.submit();
// ... systemtest
@@ -309,6 +316,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// POST (create) another application
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .instances("instance1")
.environment(Environment.prod)
.region("us-west-1")
.build();
@@ -354,7 +362,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/key", POST)
.userIdentity(USER_ID)
.data("{\"key\":\"" + pemPublicKey + "\"}"),
- "{\"message\":\"Added deploy key " + quotedPemPublicKey + "\"}");
+ "{\"keys\":[\"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuKVFA8dXk43kVfYKzkUqhEY2rDT9\\nz/4jKSTHwbYR8wdsOSrJGVEUPbS2nguIJ64OJH7gFnxM6sxUVj+Nm2HlXw==\\n-----END PUBLIC KEY-----\\n\"]}");
// PATCH in a pem deploy key at deprecated path
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/instance/default", PATCH)
@@ -377,7 +385,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/key", DELETE)
.userIdentity(USER_ID)
.data("{\"key\":\"" + pemPublicKey + "\"}"),
- "{\"message\":\"Removed deploy key " + quotedPemPublicKey + "\"}");
+ "{\"keys\":[]}");
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", GET)
.userIdentity(USER_ID),
@@ -585,6 +593,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Second attempt has a service under a different domain than the tenant of the application, and fails.
ApplicationPackage packageWithServiceForWrongDomain = new ApplicationPackageBuilder()
+ .instances("instance1")
.environment(Environment.prod)
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from(ATHENZ_TENANT_DOMAIN_2.getName()), AthenzService.from("service"))
.region("us-west-1")
@@ -597,6 +606,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Third attempt finally has a service under the domain of the tenant, and succeeds.
ApplicationPackage packageWithService = new ApplicationPackageBuilder()
+ .instances("instance1")
.environment(Environment.prod)
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from(ATHENZ_TENANT_DOMAIN.getName()), AthenzService.from("service"))
.region("us-west-1")
@@ -710,6 +720,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.computeVersionStatus();
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .instances("instance1")
.globalServiceId("foo")
.region("us-west-1")
.region("us-east-3")
@@ -718,7 +729,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Create tenant and deploy
ApplicationId id = createTenantAndApplication();
long projectId = 1;
- MultiPartStreamer deployData = createApplicationDeployData(Optional.empty(), false);
+ MultiPartStreamer deployData = createApplicationDeployData(Optional.of(applicationPackage), false);
startAndTestChange(controllerTester, id, projectId, applicationPackage, deployData, 100);
// us-west-1
@@ -781,6 +792,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.computeVersionStatus();
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .instances("instance1")
.region("us-west-1")
.region("us-east-3")
.region("eu-west-1")
@@ -857,7 +869,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1"));
// POST (deploy) an application to a prod zone - allowed when project ID is not specified
- MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
+ MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true);
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/deploy", POST)
.data(entity)
.screwdriverIdentity(SCREWDRIVER_ID),
@@ -889,6 +901,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Deploy
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .instances("instance1")
.region("us-east-3")
.build();
ApplicationId id = createTenantAndApplication();
@@ -908,6 +921,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// New zone is added before us-east-3
applicationPackage = new ApplicationPackageBuilder()
+ .instances("instance1")
.globalServiceId("foo")
// These decides the ordering of deploymentJobs and instances in the response
.region("us-west-1")
@@ -953,9 +967,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
ResourceAllocation lastMonth = new ResourceAllocation(24, 48, 2000);
ApplicationId applicationId = ApplicationId.from("doesnotexist", "doesnotexist", "default");
Map<ApplicationId, List<ResourceSnapshot>> snapshotHistory = Map.of(applicationId, List.of(
- new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(123)),
- new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(246)),
- new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(492))));
+ new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(123), ZoneId.defaultId()),
+ new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(246), ZoneId.defaultId()),
+ new ResourceSnapshot(applicationId, 1, 2,3, Instant.ofEpochMilli(492), ZoneId.defaultId())));
mockMeteringClient.setMeteringInfo(new MeteringInfo(thisMonth, lastMonth, currentSnapshot, snapshotHistory));
@@ -1060,7 +1074,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to prepare application", ConfigServerException.ErrorCode.INVALID_APPLICATION_PACKAGE, null));
// POST (deploy) an application with an invalid application package
- MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
+ MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true);
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1/deploy", POST)
.data(entity)
.userIdentity(USER_ID),
@@ -1180,7 +1194,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
200);
// Deploy to an authorized zone by a user tenant is disallowed
- MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
+ MultiPartStreamer entity = createApplicationDeployData(applicationPackageDefault, true);
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST)
.data(entity)
.userIdentity(USER_ID),
@@ -1593,7 +1607,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage,
- Optional<ApplicationVersion> applicationVersion, boolean deployDirectly) {
+ Optional<ApplicationVersion> applicationVersion, boolean deployDirectly) {
MultiPartStreamer streamer = new MultiPartStreamer();
streamer.addJson("deployOptions", deployOptions(deployDirectly, applicationVersion));
applicationPackage.ifPresent(ap -> streamer.addBytes("applicationZip", ap.zippedContent()));
@@ -1745,14 +1759,11 @@ public class ApplicationApiTest extends ControllerContainerTest {
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()));
}
@@ -1771,17 +1782,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
new RotationStatusUpdater(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator())).run();
}
- private RotationStatus rotationStatus(Instance instance) {
- return controllerTester.controller().applications().rotationRepository().getRotation(instance)
- .map(rotation -> {
- var rotationStatus = controllerTester.controller().serviceRegistry().globalRoutingService().getHealthStatus(rotation.name());
- var statusMap = new LinkedHashMap<ZoneId, RotationState>();
- rotationStatus.forEach((zone, status) -> statusMap.put(zone, RotationState.in));
- return RotationStatus.from(Map.of(rotation.id(), statusMap));
- })
- .orElse(RotationStatus.EMPTY);
- }
-
private void updateContactInformation() {
Contact contact = new Contact(URI.create("www.contacts.tld/1234"),
URI.create("www.properties.tld/1234"),
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 0a4d046e318..bb1e6b6256a 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
@@ -76,8 +76,8 @@ public class DeploymentApiTest extends ControllerContainerTest {
version.isControllerVersion(),
version.isSystemVersion(),
version.isReleased(),
- NodeVersions.EMPTY.with(List.of(new NodeVersion(HostName.from("config1.test"), version.versionNumber(), version.versionNumber(), Instant.EPOCH),
- new NodeVersion(HostName.from("config2.test"), version.versionNumber(), version.versionNumber(), Instant.EPOCH))),
+ NodeVersions.EMPTY.with(List.of(new NodeVersion(HostName.from("config1.test"), ZoneId.defaultId(), version.versionNumber(), version.versionNumber(), Instant.EPOCH),
+ new NodeVersion(HostName.from("config2.test"), ZoneId.defaultId(), version.versionNumber(), version.versionNumber(), Instant.EPOCH))),
VespaVersion.confidenceFrom(version.statistics(), controller)
);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
index f2410c47908..b1f5f33b960 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
@@ -143,14 +143,14 @@ public class UserApiTest extends ControllerContainerCloudTest {
tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app/key", POST)
.roles(Set.of(Role.tenantOperator(id.tenant())))
.data("{\"key\":\"" + pemPublicKey + "\"}"),
- "{\"message\":\"Added deploy key " + quotedPemPublicKey + "\"}");
+ new File("first-deploy-key.json"));
// POST a pem developer key
tester.assertResponse(request("/application/v4/tenant/my-tenant/key", POST)
.user("joe@dev")
.roles(Set.of(Role.tenantOperator(id.tenant())))
.data("{\"key\":\"" + pemPublicKey + "\"}"),
- "{\"message\":\"Set developer key " + quotedPemPublicKey + " for joe@dev\"}");
+ new File("first-developer-key.json"));
// POST the same pem developer key for a different user is forbidden
tester.assertResponse(request("/application/v4/tenant/my-tenant/key", POST)
@@ -165,7 +165,7 @@ public class UserApiTest extends ControllerContainerCloudTest {
.user("operator@tenant")
.roles(Set.of(Role.tenantOperator(id.tenant())))
.data("{\"key\":\"" + otherPemPublicKey + "\"}"),
- "{\"message\":\"Set developer key " + otherQuotedPemPublicKey + " for operator@tenant\"}");
+ new File("both-developer-keys.json"));
// GET tenant information with keys
tester.assertResponse(request("/application/v4/tenant/my-tenant/")
@@ -176,7 +176,7 @@ public class UserApiTest extends ControllerContainerCloudTest {
tester.assertResponse(request("/application/v4/tenant/my-tenant/key", DELETE)
.roles(Set.of(Role.tenantOperator(id.tenant())))
.data("{\"key\":\"" + pemPublicKey + "\"}"),
- "{\"message\":\"Removed developer key " + quotedPemPublicKey + " for joe@dev\"}");
+ new File("second-developer-key.json"));
// DELETE an application role is allowed for an application admin.
tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", DELETE)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/both-developer-keys.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/both-developer-keys.json
new file mode 100644
index 00000000000..2ff1c29fe29
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/both-developer-keys.json
@@ -0,0 +1,12 @@
+{
+ "keys": [
+ {
+ "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuKVFA8dXk43kVfYKzkUqhEY2rDT9\nz/4jKSTHwbYR8wdsOSrJGVEUPbS2nguIJ64OJH7gFnxM6sxUVj+Nm2HlXw==\n-----END PUBLIC KEY-----\n",
+ "user": "joe@dev"
+ },
+ {
+ "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFELzPyinTfQ/sZnTmRp5E4Ve/sbE\npDhJeqczkyFcT2PysJ5sZwm7rKPEeXDOhzTPCyRvbUqc2SGdWbKUGGa/Yw==\n-----END PUBLIC KEY-----\n",
+ "user": "operator@tenant"
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/first-deploy-key.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/first-deploy-key.json
new file mode 100644
index 00000000000..1c86877b77d
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/first-deploy-key.json
@@ -0,0 +1,5 @@
+{
+ "keys": [
+ "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuKVFA8dXk43kVfYKzkUqhEY2rDT9\nz/4jKSTHwbYR8wdsOSrJGVEUPbS2nguIJ64OJH7gFnxM6sxUVj+Nm2HlXw==\n-----END PUBLIC KEY-----\n"
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/first-developer-key.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/first-developer-key.json
new file mode 100644
index 00000000000..b7d48f283f3
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/first-developer-key.json
@@ -0,0 +1,9 @@
+{
+ "keys": [
+ {
+ "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuKVFA8dXk43kVfYKzkUqhEY2rDT9\nz/4jKSTHwbYR8wdsOSrJGVEUPbS2nguIJ64OJH7gFnxM6sxUVj+Nm2HlXw==\n-----END PUBLIC KEY-----\n",
+ "user": "joe@dev"
+ }
+ ]
+}
+
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/second-developer-key.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/second-developer-key.json
new file mode 100644
index 00000000000..f7d90f31116
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/second-developer-key.json
@@ -0,0 +1,8 @@
+{
+ "keys": [
+ {
+ "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFELzPyinTfQ/sZnTmRp5E4Ve/sbE\npDhJeqczkyFcT2PysJ5sZwm7rKPEeXDOhzTPCyRvbUqc2SGdWbKUGGa/Yw==\n-----END PUBLIC KEY-----\n",
+ "user": "operator@tenant"
+ }
+ ]
+}