diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-11-19 09:32:33 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2021-11-19 14:32:56 +0100 |
commit | ad30cfaba7b1ba6acacd5e105dc8cbdea653a4ff (patch) | |
tree | aa4862bb91b68d9b4a57116185f446222500b55f | |
parent | ada377ef8e8cf29102a2ddb696332dca37f71d57 (diff) |
Configure all routing variants through a RoutingContext
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 } ] |