summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java84
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java45
4 files changed, 109 insertions, 52 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
index f37c9d5a394..79d6e10e1dd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
@@ -10,6 +10,7 @@ import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.yolean.Exceptions;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -24,6 +25,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -61,8 +63,8 @@ public class ApplicationPackage {
this.deploymentSpec = files.getAsReader("deployment.xml").map(DeploymentSpec::fromXml).orElse(DeploymentSpec.empty);
this.validationOverrides = files.getAsReader("validation-overrides.xml").map(ValidationOverrides::fromXml).orElse(ValidationOverrides.empty);
Optional<Inspector> buildMetaObject = files.get("build-meta.json").map(SlimeUtils::jsonToSlime).map(Slime::get);
- this.compileVersion = buildMetaObject.map(object -> Version.fromString(object.field("compileVersion").asString()));
- this.buildTime = buildMetaObject.map(object -> Instant.ofEpochMilli(object.field("buildTime").asLong()));
+ this.compileVersion = buildMetaObject.flatMap(object -> parse(object, "compileVersion", field -> Version.fromString(field.asString())));
+ this.buildTime = buildMetaObject.flatMap(object -> parse(object, "buildTime", field -> Instant.ofEpochMilli(field.asLong())));
this.trustedCertificates = files.get(trustedCertificatesFile).map(bytes -> X509CertificateUtils.certificateListFromPem(new String(bytes, UTF_8))).orElse(List.of());
}
@@ -106,6 +108,16 @@ public class ApplicationPackage {
return trustedCertificates;
}
+ private static <Type> Optional<Type> parse(Inspector buildMetaObject, String fieldName, Function<Inspector, Type> mapper) {
+ try {
+ return buildMetaObject.field(fieldName).valid() ? Optional.of(mapper.apply(buildMetaObject.field(fieldName)))
+ : Optional.empty();
+ }
+ catch (RuntimeException e) {
+ throw new IllegalArgumentException("Failed parsing \"" + fieldName + "\" in 'build-meta.json': " + Exceptions.toMessageString(e));
+ }
+ }
+
private static class Files {
/** Max size of each extracted file */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
index f139001aa28..930a0f257f9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
@@ -71,7 +71,7 @@ public class DeploymentIssueReporter extends Maintainer {
for (Application application : applications)
if (failingApplications.contains(application.id()))
- fileDeploymentIssueFor(application.id());
+ fileDeploymentIssueFor(application);
else
store(application.id(), null);
}
@@ -116,18 +116,18 @@ public class DeploymentIssueReporter extends Maintainer {
}
/** File an issue for applicationId, if it doesn't already have an open issue associated with it. */
- private void fileDeploymentIssueFor(TenantAndApplicationId applicationId) {
+ private void fileDeploymentIssueFor(Application application) {
try {
- Tenant tenant = ownerOf(applicationId);
+ Tenant tenant = ownerOf(application.id());
tenant.contact().ifPresent(contact -> {
- User assignee = tenant.type() == Tenant.Type.user ? userFor(tenant) : null;
- Optional<IssueId> ourIssueId = controller().applications().requireApplication(applicationId).deploymentIssueId();
- IssueId issueId = deploymentIssues.fileUnlessOpen(ourIssueId, applicationId.defaultInstance(), assignee, contact);
- store(applicationId, issueId);
+ User assignee = tenant.type() == Tenant.Type.user ? userFor(tenant) : application.owner().orElse(null);
+ Optional<IssueId> ourIssueId = application.deploymentIssueId();
+ IssueId issueId = deploymentIssues.fileUnlessOpen(ourIssueId, application.id().defaultInstance(), assignee, contact);
+ store(application.id(), issueId);
});
}
catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout.
- log.log(Level.INFO, "Exception caught when attempting to file an issue for '" + applicationId + "': " + Exceptions.toMessageString(e));
+ log.log(Level.INFO, "Exception caught when attempting to file an issue for '" + application.id() + "': " + Exceptions.toMessageString(e));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java
index 98483763a0d..70134d5a86d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java
@@ -1,7 +1,6 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
-import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
@@ -75,10 +74,11 @@ public class RoutingPolicies {
*/
public void refresh(ApplicationId application, DeploymentSpec deploymentSpec, ZoneId zone) {
if (!controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) return;
- var lbs = new AllocatedLoadBalancers(application, zone, controller.serviceRegistry().configServer().getLoadBalancers(application, zone));
+ var lbs = new AllocatedLoadBalancers(application, zone, controller.serviceRegistry().configServer().getLoadBalancers(application, zone),
+ deploymentSpec);
try (var lock = db.lockRoutingPolicies()) {
- removeObsoleteEndpointsFromDns(lbs, deploymentSpec, lock);
- storePoliciesOf(lbs, deploymentSpec, lock);
+ removeObsoleteEndpointsFromDns(lbs, lock);
+ storePoliciesOf(lbs, lock);
removeObsoletePolicies(lbs, lock);
registerEndpointsInDns(lbs, lock);
}
@@ -104,27 +104,25 @@ public class RoutingPolicies {
}
/** Store routing policies for given route */
- private void storePoliciesOf(AllocatedLoadBalancers loadBalancers, DeploymentSpec spec, @SuppressWarnings("unused") Lock lock) {
- Set<RoutingPolicy> policies = new LinkedHashSet<>(get(loadBalancers.application));
+ private void storePoliciesOf(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
+ var policies = new LinkedHashSet<>(get(loadBalancers.application));
for (LoadBalancer loadBalancer : loadBalancers.list) {
- spec.instance(loadBalancer.application().instance()).ifPresent(instanceSpec -> {
- RoutingPolicy policy = createPolicy(loadBalancers.application, instanceSpec, loadBalancers.zone, loadBalancer);
- if (!policies.add(policy)) {
- policies.remove(policy);
- policies.add(policy);
- }
- });
+ var endpointIds = loadBalancers.endpointIdsOf(loadBalancer);
+ var policy = createPolicy(loadBalancers.application, loadBalancers.zone, loadBalancer, endpointIds);
+ if (!policies.add(policy)) {
+ // Update existing policy
+ policies.remove(policy);
+ policies.add(policy);
+ }
}
db.writeRoutingPolicies(loadBalancers.application, policies);
}
/** Create a policy for given load balancer and register a CNAME for it */
- private RoutingPolicy createPolicy(ApplicationId application, DeploymentInstanceSpec instanceSpec, ZoneId zone,
- LoadBalancer loadBalancer) {
- var endpoints = endpointIdsOf(loadBalancer, zone, instanceSpec);
- var routingPolicy = new RoutingPolicy(application, loadBalancer.cluster(), zone,
- loadBalancer.hostname(), loadBalancer.dnsZone(),
- endpoints);
+ private RoutingPolicy createPolicy(ApplicationId application, ZoneId zone, LoadBalancer loadBalancer,
+ Set<EndpointId> endpointIds) {
+ var routingPolicy = new RoutingPolicy(application, loadBalancer.cluster(), zone, loadBalancer.hostname(),
+ loadBalancer.dnsZone(), endpointIds);
var name = RecordName.from(routingPolicy.endpointIn(controller.system()).dnsName());
var data = RecordData.fqdn(loadBalancer.hostname().value());
controller.nameServiceForwarder().createCname(name, data, Priority.normal);
@@ -150,26 +148,24 @@ public class RoutingPolicies {
}
/** Remove unreferenced global endpoints for given route from DNS */
- private void removeObsoleteEndpointsFromDns(AllocatedLoadBalancers loadBalancers, DeploymentSpec deploymentSpec, @SuppressWarnings("unused") Lock lock) {
+ private void removeObsoleteEndpointsFromDns(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
var zonePolicies = get(loadBalancers.application, loadBalancers.zone);
var removalCandidates = routingTableFrom(zonePolicies).keySet();
- var activeRoutingIds = routingIdsFrom(loadBalancers, deploymentSpec);
+ var activeRoutingIds = routingIdsFrom(loadBalancers);
removalCandidates.removeAll(activeRoutingIds);
for (var id : removalCandidates) {
- Endpoint endpoint = RoutingPolicy.endpointOf(id.application(), id.endpointId(), controller.system());
+ var endpoint = RoutingPolicy.endpointOf(id.application(), id.endpointId(), controller.system());
controller.nameServiceForwarder().removeRecords(Record.Type.ALIAS, RecordName.from(endpoint.dnsName()), Priority.normal);
}
}
/** Compute routing IDs from given load balancers */
- private static Set<RoutingId> routingIdsFrom(AllocatedLoadBalancers loadBalancers, DeploymentSpec spec) {
+ private static Set<RoutingId> routingIdsFrom(AllocatedLoadBalancers loadBalancers) {
Set<RoutingId> routingIds = new LinkedHashSet<>();
for (var loadBalancer : loadBalancers.list) {
- spec.instance(loadBalancer.application().instance()).ifPresent(instanceSpec -> {
- for (var endpointId : endpointIdsOf(loadBalancer, loadBalancers.zone, instanceSpec)) {
- routingIds.add(new RoutingId(loadBalancer.application(), endpointId));
- }
- });
+ for (var endpointId : loadBalancers.endpointIdsOf(loadBalancer)) {
+ routingIds.add(new RoutingId(loadBalancer.application(), endpointId));
+ }
}
return Collections.unmodifiableSet(routingIds);
}
@@ -187,29 +183,39 @@ public class RoutingPolicies {
return routingTable;
}
- /** Compute all endpoint IDs of given load balancer */
- private static Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer, ZoneId zone, DeploymentInstanceSpec spec) {
- return spec.endpoints().stream()
- .filter(endpoint -> endpoint.containerId().equals(loadBalancer.cluster().value()))
- .filter(endpoint -> endpoint.regions().contains(zone.region()))
- .map(com.yahoo.config.application.api.Endpoint::endpointId)
- .map(EndpointId::of)
- .collect(Collectors.toSet());
- }
-
/** Load balancers allocated to a deployment */
private static class AllocatedLoadBalancers {
private final ApplicationId application;
private final ZoneId zone;
private final List<LoadBalancer> list;
+ private final DeploymentSpec deploymentSpec;
- private AllocatedLoadBalancers(ApplicationId application, ZoneId zone, List<LoadBalancer> loadBalancers) {
+ private AllocatedLoadBalancers(ApplicationId application, ZoneId zone, List<LoadBalancer> loadBalancers,
+ DeploymentSpec deploymentSpec) {
this.application = application;
this.zone = zone;
this.list = loadBalancers.stream()
.filter(AllocatedLoadBalancers::shouldUpdatePolicy)
.collect(Collectors.toUnmodifiableList());
+ this.deploymentSpec = deploymentSpec;
+ }
+
+ /** Compute all endpoint IDs for given load balancer */
+ private Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer) {
+ if (zone.environment().isManuallyDeployed()) { // Manual deployments do not have any configurable endpoints
+ return Set.of();
+ }
+ var instanceSpec = deploymentSpec.instance(loadBalancer.application().instance());
+ if (instanceSpec.isEmpty()) {
+ return Set.of();
+ }
+ return instanceSpec.get().endpoints().stream()
+ .filter(endpoint -> endpoint.containerId().equals(loadBalancer.cluster().value()))
+ .filter(endpoint -> endpoint.regions().contains(zone.region()))
+ .map(com.yahoo.config.application.api.Endpoint::endpointId)
+ .map(EndpointId::of)
+ .collect(Collectors.toSet());
}
private static boolean shouldUpdatePolicy(LoadBalancer loadBalancer) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
index bc184715589..c9ec03bd1af 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
@@ -1,12 +1,14 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
+import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
@@ -16,6 +18,7 @@ import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import org.junit.Test;
import java.net.URI;
@@ -51,7 +54,7 @@ public class RoutingPoliciesTest {
.build();
@Test
- public void maintains_global_routing_policies() {
+ public void global_routing_policies() {
int clustersPerZone = 2;
int numberOfDeployments = 2;
var applicationPackage = new ApplicationPackageBuilder()
@@ -137,10 +140,9 @@ public class RoutingPoliciesTest {
}
@Test
- public void maintains_routing_policies_per_zone() {
+ public void zone_routing_policies() {
// Deploy application
int clustersPerZone = 2;
- int buildNumber = 42;
provisionLoadBalancers(clustersPerZone, context1.instanceId(), zone1, zone2);
context1.submit(applicationPackage).deploy();
@@ -238,6 +240,43 @@ public class RoutingPoliciesTest {
tester.controller().applications().clusterEndpoints(context1.deploymentIdIn(zone1)));
}
+ @Test
+ public void manual_deployment_creates_routing_policy() {
+ // Empty application package is valid in manually deployed environments
+ var emptyApplicationPackage = new ApplicationPackageBuilder().build();
+ var zone = ZoneId.from("dev", "us-east-1");
+ tester.controllerTester().serviceRegistry().zoneRegistry().setZones(ZoneApiMock.from(zone.environment(), zone.region()));
+ provisionLoadBalancers(1, context1.instanceId(), zone);
+
+ // Deploy to dev
+ tester.controller().applications().deploy(context1.instanceId(), zone, Optional.of(emptyApplicationPackage), DeployOptions.none());
+ assertEquals("DeploymentSpec is not persisted", DeploymentSpec.empty, context1.application().deploymentSpec());
+ context1.flushDnsUpdates();
+
+ // Routing policy is created and DNS is updated
+ assertEquals(1, policies(context1.instance()).size());
+ assertEquals(Set.of("c0.app1.tenant1.us-east-1.dev.vespa.oath.cloud"), recordNames());
+ }
+
+ @Test
+ public void manual_deployment_creates_routing_policy_with_non_empty_spec() {
+ // Initial deployment
+ context1.submit(applicationPackage).deploy();
+ var zone = ZoneId.from("dev", "us-east-1");
+ tester.controllerTester().serviceRegistry().zoneRegistry().setZones(ZoneApiMock.from(zone.environment(), zone.region()));
+
+ // Deploy to dev under different instance
+ var devInstance = context1.application().id().instance("user");
+ provisionLoadBalancers(1, devInstance, zone);
+ tester.controller().applications().deploy(devInstance, zone, Optional.of(applicationPackage), DeployOptions.none());
+ assertEquals("DeploymentSpec is persisted", applicationPackage.deploymentSpec(), context1.application().deploymentSpec());
+ context1.flushDnsUpdates();
+
+ // Routing policy is created and DNS is updated
+ assertEquals(1, policies(tester.instance(devInstance)).size());
+ assertEquals(Set.of("c0.user.app1.tenant1.us-east-1.dev.vespa.oath.cloud"), recordNames());
+ }
+
private Set<RoutingPolicy> policies(Instance instance) {
return tester.controller().curator().readRoutingPolicies(instance.id());
}