aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2021-11-19 09:32:33 +0100
committerMartin Polden <mpolden@mpolden.no>2021-11-19 14:32:56 +0100
commitad30cfaba7b1ba6acacd5e105dc8cbdea653a4ff (patch)
treeaa4862bb91b68d9b4a57116185f446222500b55f
parentada377ef8e8cf29102a2ddb696332dca37f71d57 (diff)
Configure all routing variants through a RoutingContext
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java27
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java107
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java53
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java117
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java154
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveRoutingContext.java41
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedRoutingContext.java48
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java23
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json2
14 files changed, 409 insertions, 224 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java
index 55a7af45fd2..2f1b93158ab 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java
@@ -50,9 +50,10 @@ public class EndpointStatus {
}
/**
- * @return The epoch for when this status became active
+ * @return The epoch for when this status became active, in seconds
*/
public long getEpoch() {
return epoch;
}
+
}
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 df0c727ec24..9e7c614d4e8 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
@@ -351,6 +351,7 @@ public class ApplicationController {
TenantAndApplicationId applicationId = TenantAndApplicationId.from(job.application());
ZoneId zone = job.type().zone(controller.system());
+ DeploymentId deployment = new DeploymentId(job.application(), zone);
try (Lock deploymentLock = lockForDeployment(job.application(), zone)) {
Set<ContainerEndpoint> containerEndpoints;
@@ -364,7 +365,7 @@ public class ApplicationController {
Version platform = run.versions().sourcePlatform().filter(__ -> deploySourceVersions).orElse(run.versions().targetPlatform());
ApplicationVersion revision = run.versions().sourceApplication().filter(__ -> deploySourceVersions).orElse(run.versions().targetApplication());
- ApplicationPackage applicationPackage = new ApplicationPackage(applicationStore.get(new DeploymentId(job.application(), zone), revision));
+ ApplicationPackage applicationPackage = new ApplicationPackage(applicationStore.get(deployment, revision));
try (Lock lock = lock(applicationId)) {
LockedApplication application = new LockedApplication(requireApplication(applicationId), lock);
@@ -376,8 +377,7 @@ public class ApplicationController {
applicationPackage = applicationPackage.withTrustedCertificate(run.testerCertificate().get());
endpointCertificateMetadata = endpointCertificates.getMetadata(instance, zone, applicationPackage.deploymentSpec());
-
- containerEndpoints = controller.routing().containerEndpointsOf(application, job.application().instance(), zone);
+ containerEndpoints = controller.routing().of(deployment).prepare(application);
} // Release application lock while doing the deployment, which is a lengthy task.
@@ -391,7 +391,7 @@ public class ApplicationController {
// For direct deployments use the full deployment ID, but otherwise use just the tenant and application as
// the source since it's the same application, so it should have the same warnings
NotificationSource source = zone.environment().isManuallyDeployed() ?
- NotificationSource.from(new DeploymentId(job.application(), zone)) : NotificationSource.from(applicationId);
+ NotificationSource.from(deployment) : NotificationSource.from(applicationId);
List<String> warnings = Optional.ofNullable(result.prepareResponse().log)
.map(logs -> logs.stream()
.filter(log -> log.applicationPackage)
@@ -476,6 +476,7 @@ public class ApplicationController {
ZoneId zone, Version platform, Set<ContainerEndpoint> endpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
boolean dryRun) {
+ DeploymentId deployment = new DeploymentId(application, zone);
try {
Optional<DockerImage> dockerImageRepo = Optional.ofNullable(
dockerImageRepoFlag
@@ -490,7 +491,7 @@ public class ApplicationController {
.map(tenant -> ((AthenzTenant)tenant).domain());
if (zone.environment().isManuallyDeployed())
- controller.applications().applicationStore().putMeta(new DeploymentId(application, zone),
+ controller.applications().applicationStore().putMeta(deployment,
clock.instant(),
applicationPackage.metaDataZip());
@@ -502,9 +503,9 @@ public class ApplicationController {
.filter(tenant-> tenant instanceof CloudTenant)
.map(tenant -> ((CloudTenant) tenant).tenantSecretStores())
.orElse(List.of());
- List<X509Certificate> operatorCertificates = controller.supportAccess().activeGrantsFor(new DeploymentId(application, zone)).stream()
- .map(SupportAccessGrant::certificate)
- .collect(toList());
+ List<X509Certificate> operatorCertificates = controller.supportAccess().activeGrantsFor(deployment).stream()
+ .map(SupportAccessGrant::certificate)
+ .collect(toList());
ConfigServer.PreparedApplication preparedApplication =
configServer.deploy(new DeploymentData(application, zone, applicationPackage.zippedContent(), platform,
@@ -515,10 +516,10 @@ public class ApplicationController {
return new ActivateResult(new RevisionId(applicationPackage.hash()), preparedApplication.prepareResponse(),
applicationPackage.zippedContent().length);
} finally {
- // Even if prepare fails, a load balancer may have been provisioned. Always refresh routing policies so that
- // any DNS updates can be propagated as early as possible.
- if ( ! application.instance().isTester())
- controller.routing().policies().refresh(application, applicationPackage.deploymentSpec(), zone);
+ // Even if prepare fails, routing configuration may need to be updated
+ if ( ! application.instance().isTester()) {
+ controller.routing().of(deployment).configure(applicationPackage.deploymentSpec());
+ }
}
}
@@ -702,7 +703,7 @@ public class ApplicationController {
try {
configServer.deactivate(id);
} finally {
- controller.routing().policies().refresh(application.get().id().instance(instanceName), application.get().deploymentSpec(), zone);
+ controller.routing().of(id).configure(application.get().deploymentSpec());
if (zone.environment().isManuallyDeployed())
applicationStore.putMetaTombstone(id, clock.instant());
if (!zone.environment().isTest())
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
index c832b6672d0..4772dbeaab1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
@@ -18,7 +18,6 @@ import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.Flags;
-import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
@@ -31,10 +30,16 @@ import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority;
-import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock;
-import com.yahoo.vespa.hosted.controller.routing.rotation.RotationRepository;
import com.yahoo.vespa.hosted.controller.routing.RoutingId;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicies;
+import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext;
+import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext.ExclusiveDeploymentRoutingContext;
+import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext.SharedDeploymentRoutingContext;
+import com.yahoo.vespa.hosted.controller.routing.context.ExclusiveRoutingContext;
+import com.yahoo.vespa.hosted.controller.routing.context.RoutingContext;
+import com.yahoo.vespa.hosted.controller.routing.context.SharedRoutingContext;
+import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock;
+import com.yahoo.vespa.hosted.controller.routing.rotation.RotationRepository;
import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import java.nio.charset.StandardCharsets;
@@ -44,7 +49,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -77,6 +81,25 @@ public class RoutingController {
this.hideSharedRoutingEndpoint = Flags.HIDE_SHARED_ROUTING_ENDPOINT.bindTo(controller.flagSource());
}
+ /** Create a routing context for given deployment */
+ public DeploymentRoutingContext of(DeploymentId deployment) {
+ if (usesSharedRouting(deployment.zoneId())) {
+ return new SharedDeploymentRoutingContext(deployment,
+ this,
+ controller.serviceRegistry().configServer(),
+ controller.clock());
+ }
+ return new ExclusiveDeploymentRoutingContext(deployment, this);
+ }
+
+ /** Create a routing context for given zone */
+ public RoutingContext of(ZoneId zone) {
+ if (usesSharedRouting(zone)) {
+ return new SharedRoutingContext(zone, controller.serviceRegistry().configServer());
+ }
+ return new ExclusiveRoutingContext(zone, routingPolicies);
+ }
+
public RoutingPolicies policies() {
return routingPolicies;
}
@@ -217,43 +240,6 @@ public class RoutingController {
return Collections.unmodifiableList(endpointDnsNames);
}
- /** Change status of all global endpoints for given deployment */
- public void setGlobalRotationStatus(DeploymentId deployment, EndpointStatus status) {
- readDeclaredEndpointsOf(deployment.applicationId()).requiresRotation().primary().ifPresent(endpoint -> {
- try {
- controller.serviceRegistry().configServer().setGlobalRotationStatus(deployment, endpoint.upstreamIdOf(deployment), status);
- } catch (Exception e) {
- throw new RuntimeException("Failed to set rotation status of " + endpoint + " in " + deployment, e);
- }
- });
- }
-
- /** Get global endpoint status for given deployment */
- public Map<Endpoint, EndpointStatus> globalRotationStatus(DeploymentId deployment) {
- var routingEndpoints = new LinkedHashMap<Endpoint, EndpointStatus>();
- readDeclaredEndpointsOf(deployment.applicationId()).requiresRotation().primary().ifPresent(endpoint -> {
- var upstreamName = endpoint.upstreamIdOf(deployment);
- var status = controller.serviceRegistry().configServer().getGlobalRotationStatus(deployment, upstreamName);
- routingEndpoints.put(endpoint, status);
- });
- return Collections.unmodifiableMap(routingEndpoints);
- }
-
- /**
- * Assigns one or more global rotations to given application, if eligible. The given application is implicitly
- * stored, ensuring that the assigned rotation(s) are persisted when this returns.
- */
- private LockedApplication assignRotations(LockedApplication application, InstanceName instanceName) {
- try (RotationLock rotationLock = rotationRepository.lock()) {
- var rotations = rotationRepository.getOrAssignRotations(application.get().deploymentSpec(),
- application.get().require(instanceName),
- rotationLock);
- application = application.with(instanceName, instance -> instance.with(rotations));
- controller.applications().store(application); // store assigned rotation even if deployment fails
- }
- return application;
- }
-
/** Returns the global and application-level endpoints for given deployment, as container endpoints */
public Set<ContainerEndpoint> containerEndpointsOf(LockedApplication application, InstanceName instanceName, ZoneId zone) {
// Assign rotations to application
@@ -355,6 +341,32 @@ public class RoutingController {
Priority.normal));
}
+ /** Returns direct routing endpoints if any exist and feature flag is set for given application */
+ // TODO: Remove this when feature flag is removed, and in-line .direct() filter where relevant
+ public EndpointList directEndpoints(EndpointList endpoints, ApplicationId application) {
+ boolean hideSharedEndpoint = hideSharedRoutingEndpoint.with(FetchVector.Dimension.APPLICATION_ID, application.serializedForm()).value();
+ EndpointList directEndpoints = endpoints.direct();
+ if (hideSharedEndpoint && !directEndpoints.isEmpty()) {
+ return directEndpoints;
+ }
+ return endpoints;
+ }
+
+ /**
+ * Assigns one or more global rotations to given application, if eligible. The given application is implicitly
+ * stored, ensuring that the assigned rotation(s) are persisted when this returns.
+ */
+ private LockedApplication assignRotations(LockedApplication application, InstanceName instanceName) {
+ try (RotationLock rotationLock = rotationRepository.lock()) {
+ var rotations = rotationRepository.getOrAssignRotations(application.get().deploymentSpec(),
+ application.get().require(instanceName),
+ rotationLock);
+ application = application.with(instanceName, instance -> instance.with(rotations));
+ controller.applications().store(application); // store assigned rotation even if deployment fails
+ }
+ return application;
+ }
+
private boolean usesSharedRouting(ZoneId zone) {
return controller.zoneRegistry().routingMethods(zone).stream().anyMatch(RoutingMethod::isShared);
}
@@ -442,23 +454,12 @@ public class RoutingController {
}
/** Create a common name based on a hash of given application. This must be less than 64 characters long. */
- private String commonNameHashOf(ApplicationId application, SystemName system) {
+ private static String commonNameHashOf(ApplicationId application, SystemName system) {
HashCode sha1 = Hashing.sha1().hashString(application.serializedForm(), StandardCharsets.UTF_8);
String base32 = BaseEncoding.base32().omitPadding().lowerCase().encode(sha1.asBytes());
return 'v' + base32 + Endpoint.internalDnsSuffix(system);
}
- /** Returns direct routing endpoints if any exist and feature flag is set for given application */
- // TODO: Remove this when feature flag is removed, and in-line .direct() filter where relevant
- public EndpointList directEndpoints(EndpointList endpoints, ApplicationId application) {
- boolean hideSharedEndpoint = hideSharedRoutingEndpoint.with(FetchVector.Dimension.APPLICATION_ID, application.serializedForm()).value();
- EndpointList directEndpoints = endpoints.direct();
- if (hideSharedEndpoint && !directEndpoints.isEmpty()) {
- return directEndpoints;
- }
- return endpoints;
- }
-
private static String asString(Endpoint.Scope scope) {
switch (scope) {
case application: return "application";
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 94f6cccb3a5..9789bbd4da2 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
@@ -41,15 +41,16 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentFailureMails;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Mail;
import com.yahoo.vespa.hosted.controller.application.ActivateResult;
-import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.config.ControllerConfig;
import com.yahoo.vespa.hosted.controller.maintenance.JobRunner;
import com.yahoo.vespa.hosted.controller.notification.Notification;
import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
-import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext;
import com.yahoo.yolean.Exceptions;
import javax.security.auth.x500.X500Principal;
@@ -477,12 +478,12 @@ public class InternalStepRunner implements StepRunner {
}
private boolean endpointsAvailable(ApplicationId id, ZoneId zone, DualLogger logger) {
- var endpoints = controller.routing().readZoneEndpointsOf(Set.of(new DeploymentId(id, zone)));
+ DeploymentId deployment = new DeploymentId(id, zone);
+ Map<ZoneId, List<Endpoint>> endpoints = controller.routing().readZoneEndpointsOf(Set.of(deployment));
if ( ! endpoints.containsKey(zone)) {
logger.log("Endpoints not yet ready.");
return false;
}
- var policies = controller.routing().policies().get(new DeploymentId(id, zone));
for (var endpoint : endpoints.get(zone)) {
HostName endpointName = HostName.from(endpoint.dnsName());
var ipAddress = controller.jobController().cloud().resolveHostName(endpointName);
@@ -490,10 +491,10 @@ public class InternalStepRunner implements StepRunner {
logger.log(INFO, "DNS lookup yielded no IP address for '" + endpointName + "'.");
return false;
}
- if (endpoint.routingMethod() == RoutingMethod.exclusive) {
- var policy = policies.get(new RoutingPolicyId(id, ClusterSpec.Id.from(endpoint.name()), zone));
- if (policy == null)
- throw new IllegalStateException(endpoint + " has no matching policy in " + policies);
+ DeploymentRoutingContext context = controller.routing().of(deployment);
+ if (context.routingMethod() == RoutingMethod.exclusive) {
+ RoutingPolicy policy = context.routingPolicy(ClusterSpec.Id.from(endpoint.name()))
+ .orElseThrow(() -> new IllegalStateException(endpoint + " has no matching policy"));
var cNameValue = controller.jobController().cloud().resolveCname(endpointName);
if ( ! cNameValue.map(policy.canonicalName()::equals).orElse(false)) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java
index 1d5d444a32c..5acb21917eb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
@@ -25,7 +26,8 @@ public class SystemRoutingPolicyMaintainer extends ControllerMaintainer {
for (var zone : controller().zoneRegistry().zones().reachable().ids()) {
for (var application : SystemApplication.values()) {
if (!application.hasEndpoint()) continue;
- controller().routing().policies().refresh(application.id(), DeploymentSpec.empty, zone);
+ DeploymentId deployment = new DeploymentId(application.id(), zone);
+ controller().routing().of(deployment).configure(DeploymentSpec.empty);
}
}
return 1.0;
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 5d4b45fa82b..19fc155b1ca 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
@@ -44,7 +44,6 @@ import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.NotExistsException;
import com.yahoo.vespa.hosted.controller.api.application.v4.EnvironmentResource;
-import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ProtonMetrics;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.RefeedAction;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.RestartAction;
@@ -101,6 +100,7 @@ import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationState;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationStatus;
import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
+import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext;
import com.yahoo.vespa.hosted.controller.security.AccessControlRequests;
import com.yahoo.vespa.hosted.controller.security.Credentials;
import com.yahoo.vespa.hosted.controller.support.access.SupportAccess;
@@ -1532,49 +1532,32 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (deployment == null) {
throw new NotExistsException(instance + " has no deployment in " + zone);
}
-
- // The order here matters because setGlobalRotationStatus involves an external request that may fail.
- // TODO(mpolden): Set only one of these when only one kind of global endpoints are supported per zone.
- var deploymentId = new DeploymentId(instance.id(), zone);
- setGlobalRotationStatus(deploymentId, inService, request);
- setGlobalEndpointStatus(deploymentId, inService, request);
-
+ DeploymentId deploymentId = new DeploymentId(instance.id(), zone);
+ RoutingStatus.Agent agent = isOperator(request) ? RoutingStatus.Agent.operator : RoutingStatus.Agent.tenant;
+ RoutingStatus.Value status = inService ? RoutingStatus.Value.in : RoutingStatus.Value.out;
+ controller.routing().of(deploymentId).setRoutingStatus(status, agent);
return new MessageResponse(Text.format("Successfully set %s in %s %s service",
instance.id().toShortString(), zone, inService ? "in" : "out of"));
}
- /** Set the global endpoint status for given deployment. This only applies to global endpoints backed by a cloud service */
- private void setGlobalEndpointStatus(DeploymentId deployment, boolean inService, HttpRequest request) {
- var agent = isOperator(request) ? RoutingStatus.Agent.operator : RoutingStatus.Agent.tenant;
- var status = inService ? RoutingStatus.Value.in : RoutingStatus.Value.out;
- controller.routing().policies().setRoutingStatus(deployment, status, agent);
- }
-
- /** Set the global rotation status for given deployment. This only applies to global endpoints backed by a rotation */
- private void setGlobalRotationStatus(DeploymentId deployment, boolean inService, HttpRequest request) {
- var requestData = toSlime(request.getData()).get();
- var reason = mandatory("reason", requestData).asString();
- var agent = isOperator(request) ? RoutingStatus.Agent.operator : RoutingStatus.Agent.tenant;
- long timestamp = controller.clock().instant().getEpochSecond();
- var status = inService ? EndpointStatus.Status.in : EndpointStatus.Status.out;
- var endpointStatus = new EndpointStatus(status, reason, agent.name(), timestamp);
- controller.routing().setGlobalRotationStatus(deployment, endpointStatus);
- }
-
private HttpResponse getGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
requireZone(environment, region));
Slime slime = new Slime();
Cursor array = slime.setObject().setArray("globalrotationoverride");
- controller.routing().globalRotationStatus(deploymentId)
- .forEach((endpoint, status) -> {
- array.addString(endpoint.upstreamIdOf(deploymentId));
- Cursor statusObject = array.addObject();
- statusObject.setString("status", status.getStatus().name());
- statusObject.setString("reason", status.getReason() == null ? "" : status.getReason());
- statusObject.setString("agent", status.getAgent() == null ? "" : status.getAgent());
- statusObject.setLong("timestamp", status.getEpoch());
- });
+ Optional<Endpoint> primaryEndpoint = controller.routing().readDeclaredEndpointsOf(deploymentId.applicationId())
+ .requiresRotation()
+ .primary();
+ if (primaryEndpoint.isPresent()) {
+ DeploymentRoutingContext context = controller.routing().of(deploymentId);
+ RoutingStatus status = context.routingStatus();
+ array.addString(primaryEndpoint.get().upstreamIdOf(deploymentId));
+ Cursor statusObject = array.addObject();
+ statusObject.setString("status", status.value().name());
+ statusObject.setString("reason", "");
+ statusObject.setString("agent", status.agent().name());
+ statusObject.setLong("timestamp", status.changedAt().getEpochSecond());
+ }
return new SlimeJsonResponse(slime);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
index 45abf7f2946..226a7ca9561 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
@@ -19,26 +19,26 @@ import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
-import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.RoleDefinition;
import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
+import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler;
import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
+import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext;
+import com.yahoo.vespa.hosted.controller.routing.context.RoutingContext;
import com.yahoo.yolean.Exceptions;
import java.net.URI;
-import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* This implements the /routing/v1 API, which provides operators and tenants routing control at both zone- (operator
@@ -112,11 +112,8 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
var deploymentsStatus = deployments.stream()
.collect(Collectors.toMap(
deploymentId -> deploymentId,
- deploymentId -> Stream.concat(
- directGlobalRoutingStatus(deploymentId).stream(),
- sharedGlobalRoutingStatus(deploymentId).stream()
- ).collect(Collectors.toList())
- ));
+ deploymentId -> controller.routing().of(deploymentId).routingStatus())
+ );
var slime = new Slime();
var root = slime.setObject();
@@ -125,11 +122,11 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
var endpointRoot = endpointsRoot.addObject();
endpointToSlime(endpointRoot, endpoint);
var zonesRoot = endpointRoot.setArray("zones");
- endpoint.deployments().stream().sorted(Comparator.comparing(d -> d.zoneId().value())).forEach(deployment -> {
- deploymentsStatus.getOrDefault(deployment, List.of()).forEach(status -> {
- deploymentStatusToSlime(zonesRoot.addObject(), deployment, status, endpoint.routingMethod());
- });
- });
+ endpoint.deployments().stream().sorted(Comparator.comparing(d -> d.zoneId().value()))
+ .forEach(deployment -> {
+ RoutingStatus status = deploymentsStatus.get(deployment);
+ deploymentStatusToSlime(zonesRoot.addObject(), deployment, status, endpoint.routingMethod());
+ });
});
return new SlimeJsonResponse(slime);
@@ -211,13 +208,10 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse setZoneStatus(Path path, boolean in) {
- var zone = zoneFrom(path);
- if (exclusiveRoutingIn(zone)) {
- var status = in ? RoutingStatus.Value.in : RoutingStatus.Value.out;
- controller.routing().policies().setRoutingStatus(zone, status);
- } else {
- controller.serviceRegistry().configServer().setGlobalRotationStatus(zone, in);
- }
+ ZoneId zone = zoneFrom(path);
+ RoutingContext context = controller.routing().of(zone);
+ RoutingStatus.Value newStatus = in ? RoutingStatus.Value.in : RoutingStatus.Value.out;
+ context.setRoutingStatus(newStatus, RoutingStatus.Agent.operator);
return new MessageResponse("Set global routing status for deployments in " + zone + " to " +
(in ? "IN" : "OUT"));
}
@@ -231,16 +225,8 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
}
private void toSlime(ZoneId zone, Cursor zoneObject) {
- if (exclusiveRoutingIn(zone)) {
- var zonePolicy = controller.routing().policies().get(zone);
- zoneStatusToSlime(zoneObject, zonePolicy.zone(), zonePolicy.routingStatus(), RoutingMethod.exclusive);
- } else {
- // Rotation status per zone only exposes in/out status, no agent or time of change.
- var in = controller.serviceRegistry().configServer().getGlobalRotationStatus(zone);
- var globalRouting = new RoutingStatus(in ? RoutingStatus.Value.in : RoutingStatus.Value.out,
- RoutingStatus.Agent.operator, Instant.EPOCH);
- zoneStatusToSlime(zoneObject, zone, globalRouting, RoutingMethod.shared);
- }
+ RoutingContext context = controller.routing().of(zone);
+ zoneStatusToSlime(zoneObject, zone, context.routingStatus(), context.routingMethod());
}
private HttpResponse setDeploymentStatus(Path path, boolean in, HttpRequest request) {
@@ -249,18 +235,7 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
var status = in ? RoutingStatus.Value.in : RoutingStatus.Value.out;
var agent = isOperator(request) ? RoutingStatus.Agent.operator : RoutingStatus.Agent.tenant;
requireDeployment(deployment, instance);
-
- if (sharedRoutingIn(deployment.zoneId())) {
- // Set rotation status
- var endpointStatus = new EndpointStatus(in ? EndpointStatus.Status.in : EndpointStatus.Status.out,
- "",
- agent.name(),
- controller.clock().instant().getEpochSecond());
- controller.routing().setGlobalRotationStatus(deployment, endpointStatus);
- } else {
- // Set policy status
- controller.routing().policies().setRoutingStatus(deployment, status, agent);
- }
+ controller.routing().of(deployment).setRoutingStatus(status, agent);
return new MessageResponse("Set global routing status for " + deployment + " to " + (in ? "IN" : "OUT"));
}
@@ -279,66 +254,24 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
var instances = instanceId == null
? application.instances().values()
: List.of(application.instances().get(instanceId.instance()));
+ EndpointList declaredEndpoints = controller.routing().declaredEndpointsOf(application);
for (var instance : instances) {
var zones = zoneId == null
? instance.deployments().keySet().stream().sorted(Comparator.comparing(ZoneId::value))
.collect(Collectors.toList())
: List.of(zoneId);
for (var zone : zones) {
- var deploymentId = requireDeployment(new DeploymentId(instance.id(), zone), instance);
- // Include status from rotation
- sharedGlobalRoutingStatus(deploymentId).ifPresent(status -> {
- deploymentStatusToSlime(deploymentsArray.addObject(), deploymentId, status, RoutingMethod.shared);
- });
-
- // Include status from routing policies
- directGlobalRoutingStatus(deploymentId).forEach(status -> {
- deploymentStatusToSlime(deploymentsArray.addObject(), deploymentId, status, RoutingMethod.exclusive);
- });
- }
- }
- }
-
- }
-
- private Optional<RoutingStatus> sharedGlobalRoutingStatus(DeploymentId deploymentId) {
- if (sharedRoutingIn(deploymentId.zoneId())) {
- var rotationStatus = controller.routing().globalRotationStatus(deploymentId);
- // Status is equal across all global endpoints, as the status is per deployment, not per endpoint.
- var endpointStatus = rotationStatus.values().stream().findFirst();
- if (endpointStatus.isPresent()) {
- var changedAt = Instant.ofEpochSecond(endpointStatus.get().getEpoch());
- RoutingStatus.Agent agent;
- try {
- agent = RoutingStatus.Agent.valueOf(endpointStatus.get().getAgent());
- } catch (IllegalArgumentException e) {
- agent = RoutingStatus.Agent.unknown;
+ DeploymentId deploymentId = requireDeployment(new DeploymentId(instance.id(), zone), instance);
+ DeploymentRoutingContext context = controller.routing().of(deploymentId);
+ if (declaredEndpoints.targets(deploymentId).isEmpty()) continue; // No declared endpoints point to this deployment
+ deploymentStatusToSlime(deploymentsArray.addObject(),
+ deploymentId,
+ context.routingStatus(),
+ context.routingMethod());
}
- var status = endpointStatus.get().getStatus() == EndpointStatus.Status.in
- ? RoutingStatus.Value.in
- : RoutingStatus.Value.out;
- return Optional.of(new RoutingStatus(status, agent, changedAt));
}
}
- return Optional.empty();
- }
-
- private List<RoutingStatus> directGlobalRoutingStatus(DeploymentId deploymentId) {
- return controller.routing().policies().get(deploymentId).values().stream()
- .filter(p -> ! p.instanceEndpoints().isEmpty()) // This policy does not apply to a global endpoint
- .filter(p -> exclusiveRoutingIn(p.id().zone()))
- .map(p -> p.status().routingStatus())
- .collect(Collectors.toList());
- }
-
- /** Returns whether given zone uses exclusive routing */
- private boolean exclusiveRoutingIn(ZoneId zone) {
- return controller.zoneRegistry().routingMethods(zone).contains(RoutingMethod.exclusive);
- }
- /** Returns whether given zone uses shared routing */
- private boolean sharedRoutingIn(ZoneId zone) {
- return controller.zoneRegistry().routingMethods(zone).stream().anyMatch(RoutingMethod::isShared);
}
private static void zoneStatusToSlime(Cursor object, ZoneId zone, RoutingStatus routingStatus, RoutingMethod method) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
new file mode 100644
index 00000000000..28fbeee28f5
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
@@ -0,0 +1,154 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing.context;
+
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.zone.RoutingMethod;
+import com.yahoo.vespa.hosted.controller.LockedApplication;
+import com.yahoo.vespa.hosted.controller.RoutingController;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
+import com.yahoo.vespa.hosted.controller.application.Endpoint;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
+import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * A deployment routing context, which extends {@link RoutingContext} to support routing configuration of a deployment.
+ *
+ * @author mpolden
+ */
+public abstract class DeploymentRoutingContext implements RoutingContext {
+
+ final DeploymentId deployment;
+ final RoutingController controller;
+ final RoutingMethod method;
+
+ public DeploymentRoutingContext(DeploymentId deployment, RoutingMethod method, RoutingController controller) {
+ this.deployment = Objects.requireNonNull(deployment);
+ this.controller = Objects.requireNonNull(controller);
+ this.method = Objects.requireNonNull(method);
+ }
+
+ /**
+ * Prepare routing configuration for the deployment in this context
+ *
+ * @return the container endpoints relevant for this deployment, as declared in deployment spec
+ */
+ public final Set<ContainerEndpoint> prepare(LockedApplication application) {
+ return controller.containerEndpointsOf(application, deployment.applicationId().instance(), deployment.zoneId());
+ }
+
+ /** Configure routing for the deployment in this context, using given deployment spec */
+ public final void configure(DeploymentSpec deploymentSpec) {
+ controller.policies().refresh(deployment.applicationId(), deploymentSpec, deployment.zoneId());
+ }
+
+ /** Routing method of this context */
+ public final RoutingMethod routingMethod() {
+ return method;
+ }
+
+ /** Read the routing policy for given cluster in this deployment */
+ public final Optional<RoutingPolicy> routingPolicy(ClusterSpec.Id cluster) {
+ RoutingPolicyId id = new RoutingPolicyId(deployment.applicationId(), cluster, deployment.zoneId());
+ return Optional.ofNullable(controller.policies().get(deployment).get(id));
+ }
+
+ /**
+ * Extension of a {@link DeploymentRoutingContext} for deployments using either {@link RoutingMethod#shared} or
+ * {@link RoutingMethod#sharedLayer4} routing.
+ */
+ public static class SharedDeploymentRoutingContext extends DeploymentRoutingContext {
+
+ private final Clock clock;
+ private final ConfigServer configServer;
+
+ public SharedDeploymentRoutingContext(DeploymentId deployment, RoutingController controller, ConfigServer configServer, Clock clock) {
+ super(deployment, RoutingMethod.shared, controller);
+ this.clock = Objects.requireNonNull(clock);
+ this.configServer = Objects.requireNonNull(configServer);
+ }
+
+ @Override
+ public void setRoutingStatus(RoutingStatus.Value value, RoutingStatus.Agent agent) {
+ EndpointStatus newStatus = new EndpointStatus(value == RoutingStatus.Value.in
+ ? EndpointStatus.Status.in
+ : EndpointStatus.Status.out,
+ "",
+ agent.name(),
+ clock.instant().getEpochSecond());
+ primaryEndpoint().ifPresent(endpoint -> {
+ try {
+ configServer.setGlobalRotationStatus(deployment, endpoint.upstreamIdOf(deployment), newStatus);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to set rotation status of " + endpoint + " in " + deployment, e);
+ }
+ });
+ }
+
+ @Override
+ public RoutingStatus routingStatus() {
+ Optional<EndpointStatus> status = primaryEndpoint().map(endpoint -> {
+ var upstreamName = endpoint.upstreamIdOf(deployment);
+ return configServer.getGlobalRotationStatus(deployment, upstreamName);
+ });
+ if (status.isEmpty()) return RoutingStatus.DEFAULT;
+ RoutingStatus.Agent agent;
+ try {
+ agent = RoutingStatus.Agent.valueOf(status.get().getAgent().toLowerCase());
+ } catch (IllegalArgumentException e) {
+ agent = RoutingStatus.Agent.unknown;
+ }
+ return new RoutingStatus(status.get().getStatus() == EndpointStatus.Status.in
+ ? RoutingStatus.Value.in
+ : RoutingStatus.Value.out,
+ agent,
+ Instant.ofEpochSecond(status.get().getEpoch()));
+ }
+
+ private Optional<Endpoint> primaryEndpoint() {
+ return controller.readDeclaredEndpointsOf(deployment.applicationId())
+ .requiresRotation()
+ .primary();
+ }
+
+ }
+
+ /**
+ * Implementation of a {@link DeploymentRoutingContext} for deployments using {@link RoutingMethod#exclusive}
+ * routing.
+ */
+ public static class ExclusiveDeploymentRoutingContext extends DeploymentRoutingContext {
+
+ public ExclusiveDeploymentRoutingContext(DeploymentId deployment, RoutingController controller) {
+ super(deployment, RoutingMethod.exclusive, controller);
+ }
+
+ @Override
+ public void setRoutingStatus(RoutingStatus.Value value, RoutingStatus.Agent agent) {
+ controller.policies().setRoutingStatus(deployment, value, agent);
+ }
+
+ @Override
+ public RoutingStatus routingStatus() {
+ // Status for a deployment applies to all clusters within the deployment, so we use the status from the
+ // first matching policy here
+ return controller.policies().get(deployment).values().stream()
+ .findFirst()
+ .map(RoutingPolicy::status)
+ .map(RoutingPolicy.Status::routingStatus)
+ .orElse(RoutingStatus.DEFAULT);
+ }
+
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveRoutingContext.java
new file mode 100644
index 00000000000..e949c45f2fd
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveRoutingContext.java
@@ -0,0 +1,41 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing.context;
+
+import com.yahoo.config.provision.zone.RoutingMethod;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicies;
+import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
+
+import java.util.Objects;
+
+/**
+ * An implementation of {@link RoutingContext} for a zone using {@link RoutingMethod#exclusive} routing.
+ *
+ * @author mpolden
+ */
+public class ExclusiveRoutingContext implements RoutingContext {
+
+ private final RoutingPolicies policies;
+ private final ZoneId zone;
+
+ public ExclusiveRoutingContext(ZoneId zone, RoutingPolicies policies) {
+ this.policies = Objects.requireNonNull(policies);
+ this.zone = Objects.requireNonNull(zone);
+ }
+
+ @Override
+ public void setRoutingStatus(RoutingStatus.Value value, RoutingStatus.Agent agent) {
+ policies.setRoutingStatus(zone, value);
+ }
+
+ @Override
+ public RoutingStatus routingStatus() {
+ return policies.get(zone).routingStatus();
+ }
+
+ @Override
+ public RoutingMethod routingMethod() {
+ return RoutingMethod.exclusive;
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java
new file mode 100644
index 00000000000..6f43416b9b5
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java
@@ -0,0 +1,23 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing.context;
+
+import com.yahoo.config.provision.zone.RoutingMethod;
+import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
+
+/**
+ * Top-level interface for a routing context, which provides control of routing status for a deployment or zone.
+ *
+ * @author mpolden
+ */
+public interface RoutingContext {
+
+ /** Change the routing status for the zone or deployment represented by this context */
+ void setRoutingStatus(RoutingStatus.Value value, RoutingStatus.Agent agent);
+
+ /** Get the current routing status for the zone or deployment represented by this context */
+ RoutingStatus routingStatus();
+
+ /** Routing method used in this context */
+ RoutingMethod routingMethod();
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedRoutingContext.java
new file mode 100644
index 00000000000..e38212d7f80
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedRoutingContext.java
@@ -0,0 +1,48 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing.context;
+
+import com.yahoo.config.provision.zone.RoutingMethod;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
+import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * An implementation of {@link RoutingContext} for a zone, using either {@link RoutingMethod#shared} or
+ * {@link RoutingMethod#sharedLayer4} routing.
+ *
+ * @author mpolden
+ */
+public class SharedRoutingContext implements RoutingContext {
+
+ private final ConfigServer configServer;
+ private final ZoneId zone;
+
+ public SharedRoutingContext(ZoneId zone, ConfigServer configServer) {
+ this.configServer = Objects.requireNonNull(configServer);
+ this.zone = Objects.requireNonNull(zone);
+ }
+
+ @Override
+ public void setRoutingStatus(RoutingStatus.Value value, RoutingStatus.Agent agent) {
+ boolean in = value == RoutingStatus.Value.in;
+ configServer.setGlobalRotationStatus(zone, in);
+ }
+
+ @Override
+ public RoutingStatus routingStatus() {
+ boolean in = configServer.getGlobalRotationStatus(zone);
+ RoutingStatus.Value newValue = in ? RoutingStatus.Value.in : RoutingStatus.Value.out;
+ return new RoutingStatus(newValue,
+ RoutingStatus.Agent.operator,
+ Instant.EPOCH); // API does not support time of change
+ }
+
+ @Override
+ public RoutingMethod routingMethod() {
+ return RoutingMethod.shared;
+ }
+
+}
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 132e6caa3ca..30cdd1b8466 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
@@ -19,7 +19,6 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.path.Path;
-import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
@@ -40,6 +39,8 @@ import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock;
+import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
+import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext;
import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import org.junit.Test;
@@ -214,22 +215,18 @@ public class ControllerTest {
// Check initial rotation status
var deployment1 = context.deploymentIdIn(zone1);
- var status1 = tester.controller().routing().globalRotationStatus(deployment1);
- assertEquals(1, status1.size());
- assertTrue("All upstreams are in", status1.values().stream().allMatch(es -> es.getStatus() == EndpointStatus.Status.in));
+ DeploymentRoutingContext routingContext = tester.controller().routing().of(deployment1);
+ RoutingStatus status1 = routingContext.routingStatus();
+ assertEquals(RoutingStatus.Value.in, status1.value());
// Set the deployment out of service in the global rotation
- var newStatus = new EndpointStatus(EndpointStatus.Status.out, "unit-test", ControllerTest.class.getSimpleName(), tester.clock().instant().getEpochSecond());
- tester.controller().routing().setGlobalRotationStatus(deployment1, newStatus);
- status1 = tester.controller().routing().globalRotationStatus(deployment1);
- assertEquals(1, status1.size());
- assertTrue("All upstreams are out", status1.values().stream().allMatch(es -> es.getStatus() == EndpointStatus.Status.out));
- assertTrue("Reason is set", status1.values().stream().allMatch(es -> es.getReason().equals("unit-test")));
+ routingContext.setRoutingStatus(RoutingStatus.Value.out, RoutingStatus.Agent.operator);
+ RoutingStatus status2 = routingContext.routingStatus();
+ assertEquals(RoutingStatus.Value.out, status2.value());
// Other deployment remains in
- var status2 = tester.controller().routing().globalRotationStatus(context.deploymentIdIn(zone2));
- assertEquals(1, status2.size());
- assertTrue("All upstreams are in", status2.values().stream().allMatch(es -> es.getStatus() == EndpointStatus.Status.in));
+ RoutingStatus status3 = tester.controller().routing().of(context.deploymentIdIn(zone2)).routingStatus();
+ assertEquals(RoutingStatus.Value.in, status3.value());
}
@Test
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 ae6232ae419..afd67824ee8 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
@@ -69,6 +69,7 @@ import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
+import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext;
import com.yahoo.vespa.hosted.controller.security.AthenzCredentials;
import com.yahoo.vespa.hosted.controller.security.AthenzTenantSpec;
import com.yahoo.vespa.hosted.controller.support.access.SupportAccessGrant;
@@ -1868,13 +1869,12 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
private void assertGlobalRouting(DeploymentId deployment, RoutingStatus.Value value, RoutingStatus.Agent agent) {
- var changedAt = tester.controller().clock().instant();
- var westPolicies = tester.controller().routing().policies().get(deployment);
- assertEquals(1, westPolicies.size());
- var westPolicy = westPolicies.values().iterator().next();
- assertEquals(value, westPolicy.status().routingStatus().value());
- assertEquals(agent, westPolicy.status().routingStatus().agent());
- assertEquals(changedAt.truncatedTo(ChronoUnit.MILLIS), westPolicy.status().routingStatus().changedAt());
+ Instant changedAt = tester.controller().clock().instant();
+ DeploymentRoutingContext context = tester.controller().routing().of(deployment);
+ RoutingStatus status = context.routingStatus();
+ assertEquals(value, status.value());
+ assertEquals(agent, status.agent());
+ assertEquals(changedAt.truncatedTo(ChronoUnit.SECONDS), status.changedAt());
}
private static class RequestBuilder implements Supplier<Request> {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json
index 934e0cf43b9..de2266fd197 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json
@@ -4,7 +4,7 @@
{
"status": "in",
"reason": "",
- "agent": "",
+ "agent": "unknown",
"timestamp": 1497618757
}
]