aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2019-08-22 13:49:44 +0200
committerMartin Polden <mpolden@mpolden.no>2019-08-23 11:27:23 +0200
commit9f647e0078e150d2825779bf1cc2f0479ca802d7 (patch)
tree7432711f4cd9e6b6d2d3c4d1f0a81cba704dfdb9 /controller-server/src
parent944b956083270a7204396a909621756b7d7c38ca (diff)
Retrieve rotation status from GlobalRoutingService
Diffstat (limited to 'controller-server/src')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java30
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java97
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationStatus.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java19
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java48
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java104
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json3
12 files changed, 243 insertions, 100 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index d76f120dddd..8ea4ddeb631 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -265,7 +265,7 @@ public class LockedApplication {
metrics, pemDeployKey, assignedRotations, rotationStatus, applicationCertificate);
}
- public LockedApplication withRotationStatus(RotationStatus rotationStatus) {
+ public LockedApplication with(RotationStatus rotationStatus) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
index dec1847e751..56e77f3254b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
@@ -165,6 +165,11 @@ public class ApplicationList {
return listOf(list.stream().filter(a -> a.deploymentSpec().canUpgradeAt(instant)));
}
+ /** Returns the subset of applications that have at least one assigned rotation */
+ public ApplicationList hasRotation() {
+ return listOf(list.stream().filter(a -> !a.rotations().isEmpty()));
+ }
+
/**
* Returns the subset of applications that hasn't pinned to an an earlier major version than the given one.
*
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
index 4cadefc35b5..ec73ea62465 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
@@ -54,6 +54,7 @@ public class ControllerMaintenance extends AbstractComponent {
private final NameServiceDispatcher nameServiceDispatcher;
private final BillingMaintainer billingMaintainer;
private final AwsEventReporterMaintainer awsEventReporterMaintainer;
+ private final RotationStatusUpdater rotationStatusUpdater;
@SuppressWarnings("unused") // instantiated by Dependency Injection
public ControllerMaintenance(MaintainerConfig maintainerConfig, ApiAuthorityConfig apiAuthorityConfig, Controller controller, CuratorDb curator,
@@ -90,6 +91,7 @@ public class ControllerMaintenance extends AbstractComponent {
nameServiceDispatcher = new NameServiceDispatcher(controller, Duration.ofSeconds(10), jobControl, nameService);
billingMaintainer = new BillingMaintainer(controller, Duration.ofDays(3), jobControl, billing);
awsEventReporterMaintainer = new AwsEventReporterMaintainer(controller, Duration.ofDays(1), jobControl, issueHandler, awsEventFetcher);
+ rotationStatusUpdater = new RotationStatusUpdater(controller, maintenanceInterval, jobControl);
}
public Upgrader upgrader() { return upgrader; }
@@ -120,6 +122,7 @@ public class ControllerMaintenance extends AbstractComponent {
nameServiceDispatcher.deconstruct();
billingMaintainer.deconstruct();
awsEventReporterMaintainer.deconstruct();
+ rotationStatusUpdater.deconstruct();
}
/** Create one OS upgrader per cloud found in the zone registry of controller */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
index debf07ea127..74bb11f575d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
@@ -2,21 +2,16 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
-import com.yahoo.vespa.hosted.controller.rotation.RotationState;
-import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
import java.time.Duration;
import java.time.Instant;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -58,9 +53,6 @@ public class DeploymentMetricsMaintainer extends Maintainer {
applications.lockIfPresent(application.id(), locked ->
applications.store(locked.with(controller().metricsService().getApplicationMetrics(application.id()))));
- applications.lockIfPresent(application.id(), locked ->
- applications.store(locked.withRotationStatus(rotationStatus(application))));
-
for (Deployment deployment : application.deployments().values()) {
MetricsService.DeploymentMetrics collectedMetrics = controller().metricsService()
.getDeploymentMetrics(application.id(), deployment.zone());
@@ -101,26 +93,8 @@ public class DeploymentMetricsMaintainer extends Maintainer {
}
}
- /** Get global rotation status for application */
- // TODO(mpolden): Stop fetching rotation status from MetricsService and remove this
- private RotationStatus rotationStatus(Application application) {
- return applications.rotationRepository().getRotation(application)
- .map(rotation -> {
- var rotationStatus = controller().metricsService().getRotationStatus(rotation.name());
- var statusMap = new LinkedHashMap<ZoneId, RotationState>();
- rotationStatus.forEach((hostname, zoneStatus) -> statusMap.put(ZoneId.from("prod", hostname.value()), from(zoneStatus)));
- return new RotationStatus(Map.of(rotation.id(), statusMap));
- })
- .orElse(RotationStatus.EMPTY);
- }
-
- private static RotationState from(com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus status) {
- switch (status) {
- case IN: return RotationState.in;
- case OUT: return RotationState.out;
- case UNKNOWN: return RotationState.unknown;
- default: throw new IllegalArgumentException("Unknown API value for rotation status: " + status);
- }
+ private static DeploymentMetricsMaintainer maintainer(Controller controller) {
+ return new DeploymentMetricsMaintainer(controller, Duration.ofDays(1), new JobControl(controller.curator()));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java
new file mode 100644
index 00000000000..0bb98d7837c
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java
@@ -0,0 +1,97 @@
+// 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.maintenance;
+
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.ApplicationController;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingService;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
+import com.yahoo.vespa.hosted.controller.rotation.RotationState;
+import com.yahoo.vespa.hosted.controller.rotation.RotationStatus;
+
+import java.time.Duration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+/**
+ * Periodically updates the status of assigned global rotations.
+ *
+ * @author mpolden
+ */
+public class RotationStatusUpdater extends Maintainer {
+
+ private static final int applicationsToUpdateInParallel = 10;
+
+ private final GlobalRoutingService service;
+ private final ApplicationController applications;
+
+ public RotationStatusUpdater(Controller controller, Duration interval, JobControl jobControl) {
+ super(controller, interval, jobControl);
+ this.service = controller.globalRoutingService();
+ this.applications = controller.applications();
+ }
+
+ @Override
+ protected void maintain() {
+ var failures = new AtomicInteger(0);
+ var lastException = new AtomicReference<Exception>(null);
+ var applicationList = ApplicationList.from(applications.asList()).hasRotation();
+
+ // Run parallel stream inside a custom ForkJoinPool so that we can control the number of threads used
+ var pool = new ForkJoinPool(applicationsToUpdateInParallel);
+
+ pool.submit(() -> {
+ applicationList.asList().parallelStream().forEach(application -> {
+ try {
+ applications.lockIfPresent(application.id(), (app) -> applications.store(app.with(getStatus(app.get()))));
+ } catch (Exception e) {
+ failures.incrementAndGet();
+ lastException.set(e);
+ }
+ });
+ });
+ pool.shutdown();
+ try {
+ pool.awaitTermination(30, TimeUnit.SECONDS);
+ if (lastException.get() != null) {
+ log.log(LogLevel.WARNING, String.format("Failed to get global routing status of %d/%d applications. Retrying in %s. Last error: ",
+ failures.get(),
+ applicationList.size(),
+ maintenanceInterval()),
+ lastException.get());
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private RotationStatus getStatus(Application application) {
+ var statusMap = new LinkedHashMap<RotationId, Map<ZoneId, RotationState>>();
+ for (var assignedRotation : application.rotations()) {
+ var rotation = applications.rotationRepository().getRotation(assignedRotation.rotationId());
+ if (rotation.isEmpty()) continue;
+ var rotationStatus = service.getHealthStatus(rotation.get().name()).entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, (kv) -> from(kv.getValue())));
+ statusMap.put(assignedRotation.rotationId(), rotationStatus);
+ }
+ return new RotationStatus(statusMap);
+ }
+
+ private static RotationState from(com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus status) {
+ switch (status) {
+ case IN: return RotationState.in;
+ case OUT: return RotationState.out;
+ case UNKNOWN: return RotationState.unknown;
+ default: throw new IllegalArgumentException("Unknown API value for rotation status: " + status);
+ }
+ }
+
+}
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 40fdd193be9..7db75d73ea4 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
@@ -551,7 +551,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Cursor deploymentObject = instancesArray.addObject();
if (!application.rotations().isEmpty() && deployment.zone().environment() == Environment.prod) {
- toSlime(application.rotationStatus().of(deployment), deploymentObject);
+ // TODO(mpolden): Support retrieving status for multiple rotations
+ toSlime(application.rotationStatus().of(application.rotations().get(0).rotationId(), deployment), deploymentObject);
}
if (recurseOverDeployments(request)) // List full deployment information when recursive.
@@ -776,7 +777,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Slime slime = new Slime();
Cursor response = slime.setObject();
- toSlime(application.rotationStatus().of(deployment), response);
+ // TODO(mpolden): Support retrieving status for multiple rotations
+ toSlime(application.rotationStatus().of(application.rotations().get(0).rotationId(), deployment), response);
return new SlimeJsonResponse(slime);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationStatus.java
index 101c224b172..62750a5b608 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationStatus.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller.rotation;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.Deployment;
-import java.util.Collection;
import java.util.Map;
import java.util.Objects;
@@ -19,7 +18,6 @@ public class RotationStatus {
private final Map<RotationId, Map<ZoneId, RotationState>> status;
- /** DO NOT USE. Public for serialization purposes */
public RotationStatus(Map<RotationId, Map<ZoneId, RotationState>> status) {
this.status = Map.copyOf(Objects.requireNonNull(status));
}
@@ -33,16 +31,14 @@ public class RotationStatus {
return status.getOrDefault(rotation, Map.of());
}
- /** Get status of given deployment, if any */
- public RotationState of(Deployment deployment) {
- return status.values().stream()
- .map(Map::entrySet)
- .flatMap(Collection::stream)
- // TODO(mpolden): Change to exact comparison after September 2019
- .filter(kv -> kv.getKey().value().contains(deployment.zone().value()))
- .map(Map.Entry::getValue)
- .findFirst()
- .orElse(RotationState.unknown);
+ /** Get status of deployment in given rotation, if any */
+ public RotationState of(RotationId rotation, Deployment deployment) {
+ return of(rotation).entrySet().stream()
+ // TODO(mpolden): Change to exact comparison after September 2019
+ .filter(kv -> kv.getKey().value().contains(deployment.zone().value()))
+ .map(Map.Entry::getValue)
+ .findFirst()
+ .orElse(RotationState.unknown);
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index b79c0dc4ac2..3ce3ac5eecb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -90,6 +90,7 @@ public final class ControllerTester {
private final MetricsServiceMock metricsService;
private final RoutingGeneratorMock routingGenerator;
private final MockContactRetriever contactRetriever;
+ private final MemoryGlobalRoutingService globalRoutingService;
private Controller controller;
@@ -98,7 +99,7 @@ public final class ControllerTester {
this(new AthenzDbMock(), clock, new ConfigServerMock(new ZoneRegistryMock()),
new ZoneRegistryMock(), curatorDb, rotationsConfig,
new MemoryNameService(), new ArtifactRepositoryMock(), new ApplicationStoreMock(), new MockBuildService(),
- metricsService, new RoutingGeneratorMock(), new MockContactRetriever());
+ metricsService, new RoutingGeneratorMock(), new MockContactRetriever(), new MemoryGlobalRoutingService());
}
public ControllerTester(ManualClock clock) {
@@ -123,7 +124,7 @@ public final class ControllerTester {
MemoryNameService nameService, ArtifactRepositoryMock artifactRepository,
ApplicationStoreMock appStoreMock, MockBuildService buildService,
MetricsServiceMock metricsService, RoutingGeneratorMock routingGenerator,
- MockContactRetriever contactRetriever) {
+ MockContactRetriever contactRetriever, MemoryGlobalRoutingService globalRoutingService) {
this.athenzDb = athenzDb;
this.clock = clock;
this.configServer = configServer;
@@ -137,9 +138,10 @@ public final class ControllerTester {
this.metricsService = metricsService;
this.routingGenerator = routingGenerator;
this.contactRetriever = contactRetriever;
+ this.globalRoutingService = globalRoutingService;
this.controller = createController(curator, rotationsConfig, configServer, clock, zoneRegistry,
athenzDb, artifactRepository, appStoreMock, buildService,
- metricsService, routingGenerator);
+ metricsService, routingGenerator, globalRoutingService);
// Make root logger use time from manual clock
configureDefaultLogHandler(handler -> handler.setFilter(
@@ -189,6 +191,10 @@ public final class ControllerTester {
return contactRetriever;
}
+ public MemoryGlobalRoutingService globalRoutingService() {
+ return globalRoutingService;
+ }
+
public Optional<Record> findCname(String name) {
return nameService.findRecords(Record.Type.CNAME, RecordName.from(name)).stream().findFirst();
}
@@ -197,7 +203,7 @@ public final class ControllerTester {
public final void createNewController() {
controller = createController(curator, rotationsConfig, configServer, clock, zoneRegistry, athenzDb,
artifactRepository, applicationStore, buildService, metricsService,
- routingGenerator);
+ routingGenerator, globalRoutingService);
}
/** Creates the given tenant and application and deploys it */
@@ -331,7 +337,8 @@ public final class ControllerTester {
AthenzDbMock athensDb,
ArtifactRepository artifactRepository, ApplicationStore applicationStore,
BuildService buildService, MetricsServiceMock metricsService,
- RoutingGenerator routingGenerator) {
+ RoutingGenerator routingGenerator,
+ MemoryGlobalRoutingService globalRoutingService) {
Controller controller = new Controller(curator,
rotationsConfig,
zoneRegistryMock,
@@ -351,7 +358,7 @@ public final class ControllerTester {
new MockMavenRepository(),
new ApplicationCertificateMock(),
new MockMeteringClient(),
- new MemoryGlobalRoutingService());
+ globalRoutingService);
// Calculate initial versions
controller.updateVersionStatus(VersionStatus.compute(controller));
return controller;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
index ccafab622d0..60ac01bdf57 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
@@ -6,13 +6,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.ControllerTester;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Deployment;
-import com.yahoo.vespa.hosted.controller.rotation.RotationState;
-import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
-import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
-import com.yahoo.vespa.hosted.controller.integration.MetricsServiceMock;
import org.junit.Test;
import java.time.Duration;
@@ -90,48 +84,6 @@ public class DeploymentMetricsMaintainerTest {
assertEquals(5, deployment.get().activity().lastWritesPerSecond().getAsDouble(), Double.MIN_VALUE);
}
- @Test
- public void updates_rotation_status() {
- DeploymentTester tester = new DeploymentTester();
- MetricsServiceMock metricsService = tester.controllerTester().metricsService();
- DeploymentMetricsMaintainer maintainer = maintainer(tester.controller());
- Application application = tester.createApplication("app1", "tenant1", 1, 1L);
- ZoneId zone1 = ZoneId.from("prod", "us-west-1");
- ZoneId zone2 = ZoneId.from("prod", "us-east-3");
-
- // Deploy application with global rotation
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .environment(Environment.prod)
- .globalServiceId("foo")
- .region(zone1.region().value())
- .region(zone2.region().value())
- .build();
- tester.deployCompletely(application, applicationPackage);
-
- Supplier<Application> app = () -> tester.application(application.id());
- Supplier<Deployment> deployment1 = () -> app.get().deployments().get(zone1);
- Supplier<Deployment> deployment2 = () -> app.get().deployments().get(zone2);
- String assignedRotation = "rotation-fqdn-01";
- tester.controllerTester().metricsService().addRotation(assignedRotation);
-
- // No status gathered yet
- assertEquals(RotationState.unknown, app.get().rotationStatus().of(deployment1.get()));
- assertEquals(RotationState.unknown, app.get().rotationStatus().of(deployment2.get()));
-
- // One rotation out, one in
- metricsService.setZoneIn(assignedRotation, "proxy.prod.us-west-1.vip.test");
- metricsService.setZoneOut(assignedRotation,"proxy.prod.us-east-3.vip.test");
- maintainer.maintain();
- assertEquals(RotationState.in, app.get().rotationStatus().of(deployment1.get()));
- assertEquals(RotationState.out, app.get().rotationStatus().of(deployment2.get()));
-
- // All rotations in
- metricsService.setZoneIn(assignedRotation,"proxy.prod.us-east-3.vip.test");
- maintainer.maintain();
- assertEquals(RotationState.in, app.get().rotationStatus().of(deployment1.get()));
- assertEquals(RotationState.in, app.get().rotationStatus().of(deployment2.get()));
- }
-
private static DeploymentMetricsMaintainer maintainer(Controller controller) {
return new DeploymentMetricsMaintainer(controller, Duration.ofDays(1), new JobControl(controller.curator()));
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java
new file mode 100644
index 00000000000..4ef77573f93
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdaterTest.java
@@ -0,0 +1,104 @@
+// 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.maintenance;
+
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.flags.InMemoryFlagSource;
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus;
+import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
+import com.yahoo.vespa.hosted.controller.deployment.BuildJob;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.rotation.RotationState;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author mpolden
+ */
+public class RotationStatusUpdaterTest {
+
+ @Test
+ public void updates_rotation_status() {
+ var tester = new DeploymentTester();
+ ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.MULTIPLE_GLOBAL_ENDPOINTS.id(), true);
+ var globalRotationService = tester.controllerTester().globalRoutingService();
+ var updater = new RotationStatusUpdater(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()));
+
+ var application = tester.createApplication("app1", "tenant1", 1, 1L);
+ var zone1 = ZoneId.from("prod", "us-west-1");
+ var zone2 = ZoneId.from("prod", "us-east-3");
+ var zone3 = ZoneId.from("prod", "eu-west-1");
+
+ // Deploy application with global rotation
+ var applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .globalServiceId("foo")
+ .region(zone1.region().value())
+ .region(zone2.region().value())
+ .build();
+ tester.deployCompletely(application, applicationPackage);
+
+ Supplier<Application> app = () -> tester.application(application.id());
+ Supplier<Deployment> deployment1 = () -> app.get().deployments().get(zone1);
+ Supplier<Deployment> deployment2 = () -> app.get().deployments().get(zone2);
+ Supplier<Deployment> deployment3 = () -> app.get().deployments().get(zone3);
+
+ // No status gathered yet
+ var rotation1 = app.get().rotations().get(0).rotationId();
+ assertEquals(RotationState.unknown, app.get().rotationStatus().of(rotation1, deployment1.get()));
+ assertEquals(RotationState.unknown, app.get().rotationStatus().of(rotation1, deployment2.get()));
+
+ // First rotation: One zone out, one in
+ var rotationName1 = "rotation-fqdn-01";
+ globalRotationService.setStatus(rotationName1, zone1, RotationStatus.IN)
+ .setStatus(rotationName1, zone2, RotationStatus.OUT);
+ updater.maintain();
+ assertEquals(RotationState.in, app.get().rotationStatus().of(rotation1, deployment1.get()));
+ assertEquals(RotationState.out, app.get().rotationStatus().of(rotation1, deployment2.get()));
+
+ // First rotation: All zones in
+ globalRotationService.setStatus(rotationName1, zone2, RotationStatus.IN);
+ updater.maintain();
+ assertEquals(RotationState.in, app.get().rotationStatus().of(rotation1, deployment1.get()));
+ assertEquals(RotationState.in, app.get().rotationStatus().of(rotation1, deployment2.get()));
+
+ // Another rotation is assigned
+ applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .region(zone1.region().value())
+ .region(zone2.region().value())
+ .region(zone3.region().value())
+ .endpoint("default", "default", "us-east-3", "us-west-1")
+ .endpoint("eu", "default", "eu-west-1")
+ .build();
+ tester.deployCompletely(application, applicationPackage, BuildJob.defaultBuildNumber + 1);
+ assertEquals(2, app.get().rotations().size());
+
+ // Second rotation: No status gathered yet
+ var rotation2 = app.get().rotations().get(1).rotationId();
+ updater.maintain();
+ assertEquals(RotationState.unknown, app.get().rotationStatus().of(rotation2, deployment3.get()));
+
+ // Status of third zone is retrieved via second rotation
+ var rotationName2 = "rotation-fqdn-02";
+ globalRotationService.setStatus(rotationName2, zone3, RotationStatus.IN);
+ updater.maintain();
+ assertEquals(RotationState.in, app.get().rotationStatus().of(rotation2, deployment3.get()));
+
+ // Each rotation only has status for their configured zones
+ assertEquals("Rotation " + rotation1 + " does not know about " + deployment3.get(), RotationState.unknown,
+ app.get().rotationStatus().of(rotation1, deployment3.get()));
+ assertEquals("Rotation " + rotation2 + " does not know about " + deployment1.get(), RotationState.unknown,
+ app.get().rotationStatus().of(rotation2, deployment1.get()));
+ assertEquals("Rotation " + rotation2 + " does not know about " + deployment2.get(), RotationState.unknown,
+ app.get().rotationStatus().of(rotation2, deployment2.get()));
+ }
+
+}
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 9bc5c879961..e93513ec088 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
@@ -1677,16 +1677,16 @@ public class ApplicationApiTest extends ControllerContainerTest {
List<Application> applicationList = applicationController.asList();
applicationList.forEach(application -> {
applicationController.lockIfPresent(application.id(), locked ->
- applicationController.store(locked.withRotationStatus(rotationStatus(application))));
+ applicationController.store(locked.with(rotationStatus(application))));
});
}
private RotationStatus rotationStatus(Application application) {
return controllerTester.controller().applications().rotationRepository().getRotation(application)
.map(rotation -> {
- var rotationStatus = controllerTester.controller().metricsService().getRotationStatus(rotation.name());
+ var rotationStatus = controllerTester.controller().globalRoutingService().getHealthStatus(rotation.name());
var statusMap = new LinkedHashMap<ZoneId, RotationState>();
- rotationStatus.forEach((hostname, status) -> statusMap.put(ZoneId.from("prod", hostname.value()), RotationState.in));
+ rotationStatus.forEach((zone, status) -> statusMap.put(zone, RotationState.in));
return new RotationStatus(Map.of(rotation.id(), statusMap));
})
.orElse(RotationStatus.EMPTY);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
index 1b5b679cb8a..c8ab8a92583 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
@@ -55,6 +55,9 @@
"name": "ResourceMeterMaintainer"
},
{
+ "name": "RotationStatusUpdater"
+ },
+ {
"name": "SystemUpgrader"
},
{