aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2021-03-24 15:19:54 +0100
committerMartin Polden <mpolden@mpolden.no>2021-03-25 08:51:10 +0100
commit0e5126f9d1baf9310185c7690955ea2263956ade (patch)
treec48abc655b8073266e660e490171379b8697c3d9 /controller-server
parent15fe46b572ed734767bc0dd0dbf43136114090b1 (diff)
Compute global endpoints from deployment spec
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java127
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java25
4 files changed, 73 insertions, 87 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index 4351ac17001..dddb9ff0b7b 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
@@ -358,7 +358,7 @@ public class ApplicationController {
ZoneId zone = job.type().zone(controller.system());
try (Lock deploymentLock = lockForDeployment(job.application(), zone)) {
- Set<ContainerEndpoint> endpoints;
+ Set<ContainerEndpoint> containerEndpoints;
Optional<EndpointCertificateMetadata> endpointCertificateMetadata;
Optional<TenantRoles> tenantRoles = Optional.empty();
@@ -383,12 +383,12 @@ public class ApplicationController {
endpointCertificateMetadata = endpointCertificateManager.getEndpointCertificateMetadata(instance, zone, applicationPackage.deploymentSpec().instance(instance.name()));
- endpoints = controller.routing().registerEndpointsInDns(application.get(), job.application().instance(), zone);
+ containerEndpoints = controller.routing().containerEndpointsOf(application.get(), job.application().instance(), zone);
} // Release application lock while doing the deployment, which is a lengthy task.
// Carry out deployment without holding the application lock.
- ActivateResult result = deploy(job.application(), applicationPackage, zone, platform, endpoints, endpointCertificateMetadata, tenantRoles);
+ ActivateResult result = deploy(job.application(), applicationPackage, zone, platform, containerEndpoints, endpointCertificateMetadata, tenantRoles);
// Record the quota usage for this application
var quotaUsage = deploymentQuotaUsage(zone, job.application());
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 ab97757e958..7f7b8452579 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
@@ -1,6 +1,7 @@
// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
+import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
@@ -22,6 +23,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.Endpoint.Port;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
@@ -43,6 +45,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
@@ -83,10 +86,11 @@ public class RoutingController {
/** Returns endpoints for given deployment */
public EndpointList endpointsOf(DeploymentId deployment) {
- var endpoints = new LinkedHashSet<Endpoint>();
+ Set<Endpoint> endpoints = new LinkedHashSet<>();
boolean isSystemApplication = SystemApplication.matching(deployment.applicationId()).isPresent();
// Avoid reading application more than once per call to this
- var application = Suppliers.memoize(() -> controller.applications().requireApplication(TenantAndApplicationId.from(deployment.applicationId())));
+ Supplier<Application> application = Suppliers.memoize(() -> controller.applications().requireApplication(TenantAndApplicationId.from(deployment.applicationId())));
+ // To discover the cluster name for a zone-scoped endpoint, we need to read routing policies
for (var policy : routingPolicies.get(deployment).values()) {
if (!policy.status().isActive()) continue;
for (var routingMethod : controller.zoneRegistry().routingMethods(policy.id().zone())) {
@@ -100,37 +104,34 @@ public class RoutingController {
/** Returns global-scoped endpoints for given instance */
public EndpointList endpointsOf(ApplicationId instance) {
- if (SystemApplication.matching(instance).isPresent()) return EndpointList.copyOf(List.of());
+ if (SystemApplication.matching(instance).isPresent()) return EndpointList.EMPTY;
return endpointsOf(controller.applications().requireApplication(TenantAndApplicationId.from(instance)),
instance.instance());
}
/** Returns global-scoped endpoints for given instance */
public EndpointList endpointsOf(Application application, InstanceName instanceName) {
- var endpoints = new LinkedHashSet<Endpoint>();
- // Add global endpoints provided by rotations
- var instance = application.require(instanceName);
- for (var rotation : instance.rotations()) {
- var deployments = rotation.regions().stream()
- .map(region -> new DeploymentId(instance.id(), ZoneId.from(Environment.prod, region)))
- .collect(Collectors.toList());
- computeGlobalEndpoints(RoutingId.of(instance.id(), rotation.endpointId()), rotation.clusterId(),
- application, deployments).requiresRotation()
- .forEach(endpoints::add);
- }
- // Add global endpoints provided by routing policies
- var deploymentsByEndpointKey = new LinkedHashMap<EndpointKey, List<DeploymentId>>();
- for (var policy : routingPolicies.get(instance.id()).values()) {
- if (!policy.status().isActive()) continue;
- for (var endpointId : policy.endpoints()) {
- var endpointKey = new EndpointKey(RoutingId.of(instance.id(), endpointId), policy.id().cluster());
- deploymentsByEndpointKey.computeIfAbsent(endpointKey, (k) -> new ArrayList<>())
- .add(new DeploymentId(instance.id(), policy.id().zone()));
- }
- }
- deploymentsByEndpointKey.forEach((endpointKey, deployments) -> {
- computeGlobalEndpoints(endpointKey.routingId, endpointKey.cluster, application,
- deployments).not().requiresRotation().forEach(endpoints::add);
+ Set<Endpoint> endpoints = new LinkedHashSet<>();
+ Instance instance = application.require(instanceName);
+ Optional<DeploymentInstanceSpec> spec = application.deploymentSpec().instance(instanceName);
+ if (spec.isEmpty()) return EndpointList.EMPTY;
+ // Add endpoint declared with legacy syntax
+ spec.get().globalServiceId().ifPresent(clusterId -> {
+ List<DeploymentId> deployments = spec.get().zones().stream()
+ .filter(zone -> zone.concerns(Environment.prod))
+ .map(zone -> new DeploymentId(instance.id(), ZoneId.from(Environment.prod, zone.region().get())))
+ .collect(Collectors.toList());
+ RoutingId routingId = RoutingId.of(instance.id(), EndpointId.defaultId());
+ endpoints.addAll(computeGlobalEndpoints(routingId, ClusterSpec.Id.from(clusterId), application, deployments));
+ });
+ // Add endpoints declared with current syntax
+ spec.get().endpoints().forEach(declaredEndpoint -> {
+ RoutingId routingId = RoutingId.of(instance.id(), EndpointId.of(declaredEndpoint.endpointId()));
+ List<DeploymentId> deployments = declaredEndpoint.regions().stream()
+ .map(region -> new DeploymentId(instance.id(),
+ ZoneId.from(Environment.prod, region)))
+ .collect(Collectors.toList());
+ endpoints.addAll(computeGlobalEndpoints(routingId, ClusterSpec.Id.from(declaredEndpoint.containerId()), application, deployments));
});
return EndpointList.copyOf(endpoints);
}
@@ -185,21 +186,19 @@ public class RoutingController {
return application;
}
- /**
- * Register endpoints for rotations assigned to given application and zone in DNS.
- *
- * @return the registered endpoints
- */
- public Set<ContainerEndpoint> registerEndpointsInDns(Application application, InstanceName instanceName, ZoneId zone) {
- var instance = application.require(instanceName);
- var containerEndpoints = new HashSet<ContainerEndpoint>();
+ /** Returns the global endpoints for given deployment as container endpoints */
+ public Set<ContainerEndpoint> containerEndpointsOf(Application application, InstanceName instanceName, ZoneId zone) {
+ Instance instance = application.require(instanceName);
boolean registerLegacyNames = application.deploymentSpec().instance(instanceName)
.flatMap(DeploymentInstanceSpec::globalServiceId)
.isPresent();
+ Set<ContainerEndpoint> containerEndpoints = new HashSet<>();
+ EndpointList endpoints = endpointsOf(application, instanceName);
+ // Add endpoints backed by a rotation, and register them in DNS if necessary
for (var assignedRotation : instance.rotations()) {
var names = new ArrayList<String>();
- var endpoints = endpointsOf(application, instanceName).named(assignedRotation.endpointId())
- .requiresRotation();
+ EndpointList rotationEndpoints = endpoints.named(assignedRotation.endpointId())
+ .requiresRotation();
// Skip rotations which do not apply to this zone. Legacy names always point to all zones
if (!registerLegacyNames && !assignedRotation.regions().contains(zone.region())) {
@@ -208,13 +207,13 @@ public class RoutingController {
// Omit legacy DNS names when assigning rotations using <endpoints/> syntax
if (!registerLegacyNames) {
- endpoints = endpoints.not().legacy();
+ rotationEndpoints = rotationEndpoints.not().legacy();
}
// Register names in DNS
var rotation = rotationRepository.getRotation(assignedRotation.rotationId());
if (rotation.isPresent()) {
- endpoints.forEach(endpoint -> {
+ rotationEndpoints.forEach(endpoint -> {
controller.nameServiceForwarder().createCname(RecordName.from(endpoint.dnsName()),
RecordData.fqdn(rotation.get().name()),
Priority.normal);
@@ -231,11 +230,21 @@ public class RoutingController {
/** Remove endpoints in DNS for all rotations assigned to given instance */
public void removeEndpointsInDns(Application application, InstanceName instanceName) {
- endpointsOf(application, instanceName).requiresRotation()
- .forEach(endpoint -> controller.nameServiceForwarder()
- .removeRecords(Record.Type.CNAME,
- RecordName.from(endpoint.dnsName()),
- Priority.normal));
+ Set<Endpoint> endpointsToRemove = new LinkedHashSet<>();
+ Instance instance = application.require(instanceName);
+ // Compute endpoints from rotations. When removing DNS records for rotation-based endpoints we cannot use the
+ // deployment spec, because submitting an empty deployment spec is the first step of removing an application
+ for (var rotation : instance.rotations()) {
+ var deployments = rotation.regions().stream()
+ .map(region -> new DeploymentId(instance.id(), ZoneId.from(Environment.prod, region)))
+ .collect(Collectors.toList());
+ endpointsToRemove.addAll(computeGlobalEndpoints(RoutingId.of(instance.id(), rotation.endpointId()),
+ rotation.clusterId(), application, deployments));
+ }
+ endpointsToRemove.forEach(endpoint -> controller.nameServiceForwarder()
+ .removeRecords(Record.Type.CNAME,
+ RecordName.from(endpoint.dnsName()),
+ Priority.normal));
}
/** Returns the routing methods that are available across all given deployments */
@@ -283,7 +292,7 @@ public class RoutingController {
}
/** Compute global endpoints for given routing ID, application and deployments */
- private EndpointList computeGlobalEndpoints(RoutingId routingId, ClusterSpec.Id cluster, Application application, List<DeploymentId> deployments) {
+ private List<Endpoint> computeGlobalEndpoints(RoutingId routingId, ClusterSpec.Id cluster, Application application, List<DeploymentId> deployments) {
var endpoints = new ArrayList<Endpoint>();
var directMethods = 0;
var zones = deployments.stream().map(DeploymentId::zoneId).collect(Collectors.toList());
@@ -315,33 +324,7 @@ public class RoutingController {
.in(controller.system()));
}
}
- return EndpointList.copyOf(endpoints);
- }
-
- private static class EndpointKey {
-
- private final RoutingId routingId;
- private final ClusterSpec.Id cluster;
-
- public EndpointKey(RoutingId routingId, ClusterSpec.Id cluster) {
- this.routingId = routingId;
- this.cluster = cluster;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- EndpointKey that = (EndpointKey) o;
- return routingId.equals(that.routingId) &&
- cluster.equals(that.cluster);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(routingId, cluster);
- }
-
+ return endpoints;
}
/** Returns direct routing endpoints if any exist and feature flag is set for given application */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
index 358086d453e..7a56329e16a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
@@ -17,6 +17,8 @@ import java.util.Optional;
*/
public class EndpointList extends AbstractFilteringList<Endpoint, EndpointList> {
+ public static final EndpointList EMPTY = EndpointList.copyOf(List.of());
+
private EndpointList(Collection<? extends Endpoint> endpoints, boolean negate) {
super(endpoints, negate, EndpointList::new);
if (endpoints.stream().distinct().count() != endpoints.size()) {
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 362c980a906..988acc5b456 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
@@ -503,10 +503,12 @@ public class ControllerTest {
context.submit(applicationPackage).deploy();
assertEquals(1, tester.controllerTester().nameService().records().size());
- Optional<Record> record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
- assertTrue(record.isPresent());
- assertEquals("app1--tenant1.global.vespa.oath.cloud", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
+ {
+ Optional<Record> record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
+ assertTrue(record.isPresent());
+ assertEquals("app1--tenant1.global.vespa.oath.cloud", record.get().name().asString());
+ assertEquals("rotation-fqdn-01.", record.get().data().asString());
+ }
// Application is deleted and rotation is unassigned
applicationPackage = new ApplicationPackageBuilder()
@@ -524,14 +526,13 @@ public class ControllerTest {
context.flushDnsUpdates();
// Records are removed
- record = tester.controllerTester().findCname("app1--tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isEmpty());
-
- record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
- assertTrue(record.isEmpty());
-
- record = tester.controllerTester().findCname("app1.tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isEmpty());
+ List<String> removed = List.of("app1--tenant1.global.vespa.yahooapis.com",
+ "app1--tenant1.global.vespa.oath.cloud",
+ "app1.tenant1.global.vespa.yahooapis.com");
+ for (var name : removed) {
+ Optional<Record> record = tester.controllerTester().findCname(name);
+ assertTrue(name + " is removed", record.isEmpty());
+ }
}
// Application 2 is deployed and assigned same rotation as application 1 had before deletion