diff options
13 files changed, 317 insertions, 204 deletions
diff --git a/config/src/main/java/com/yahoo/vespa/config/SlimeUtils.java b/config/src/main/java/com/yahoo/vespa/config/SlimeUtils.java index bc1b2c90364..80e93977980 100644 --- a/config/src/main/java/com/yahoo/vespa/config/SlimeUtils.java +++ b/config/src/main/java/com/yahoo/vespa/config/SlimeUtils.java @@ -5,6 +5,7 @@ import com.yahoo.slime.*; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Optional; /** @@ -113,6 +114,10 @@ public class SlimeUtils { return slime; } + public static Slime jsonToSlime(String json) { + return jsonToSlime(json.getBytes(StandardCharsets.UTF_8)); + } + public static Optional<String> optionalString(Inspector inspector) { return Optional.of(inspector.asString()).filter(s -> !s.isEmpty()); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/LoadBalancer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/LoadBalancer.java index b77b63cab8a..9f686570da1 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/LoadBalancer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/LoadBalancer.java @@ -1,13 +1,15 @@ // 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.api.integration.configserver; +import com.google.common.collect.ImmutableSortedSet; +import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; -import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; -import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; +import com.yahoo.config.provision.RotationName; import java.util.Objects; +import java.util.Optional; +import java.util.Set; /** * Represents an exclusive load balancer, assigned to an application's cluster. @@ -17,37 +19,30 @@ import java.util.Objects; public class LoadBalancer { private final String id; - private final TenantId tenant; private final ApplicationId application; - private final InstanceId instance; private final ClusterSpec.Id cluster; private final HostName hostname; + private final Optional<String> dnsZone; + private final Set<RotationName> rotations; - public LoadBalancer(String id, TenantId tenant, ApplicationId application, InstanceId instance, ClusterSpec.Id cluster, HostName hostname) { + public LoadBalancer(String id, ApplicationId application, ClusterSpec.Id cluster, HostName hostname, + Optional<String> dnsZone, Set<RotationName> rotations) { this.id = Objects.requireNonNull(id, "id must be non-null"); - this.tenant = Objects.requireNonNull(tenant, "tenant must be non-null"); this.application = Objects.requireNonNull(application, "application must be non-null"); - this.instance = Objects.requireNonNull(instance, "instance must be non-null"); this.cluster = Objects.requireNonNull(cluster, "cluster must be non-null"); this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null"); + this.dnsZone = Objects.requireNonNull(dnsZone, "dnsZone must be non-null"); + this.rotations = ImmutableSortedSet.copyOf(Objects.requireNonNull(rotations, "rotations must be non-null")); } public String id() { return id; } - public TenantId tenant() { - return tenant; - } - public ApplicationId application() { return application; } - public InstanceId instance() { - return instance; - } - public ClusterSpec.Id cluster() { return cluster; } @@ -56,4 +51,12 @@ public class LoadBalancer { return hostname; } + public Optional<String> dnsZone() { + return dnsZone; + } + + public Set<RotationName> rotations() { + return rotations; + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/LoadBalancerAlias.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java index d8b90d5aa65..41e3a6086d2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/LoadBalancerAlias.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java @@ -2,34 +2,44 @@ package com.yahoo.vespa.hosted.controller.application; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSortedSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.RotationName; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** - * Represents a DNS alias for a load balancer. + * Represents the DNS routing policy for a load balancer. * * @author mortent */ -public class LoadBalancerAlias { +public class RoutingPolicy { private static final String ignoredEndpointPart = "default"; + private final ApplicationId owner; - private final String id; + private final String recordId; private final HostName alias; private final HostName canonicalName; + private final Optional<String> dnsZone; + private final Set<RotationName> rotations; - public LoadBalancerAlias(ApplicationId owner, String id, HostName alias, HostName canonicalName) { + public RoutingPolicy(ApplicationId owner, String recordId, HostName alias, HostName canonicalName, + Optional<String> dnsZone, Set<RotationName> rotations) { this.owner = Objects.requireNonNull(owner, "owner must be non-null"); - this.id = Objects.requireNonNull(id, "id must be non-null"); + this.recordId = Objects.requireNonNull(recordId, "recordId must be non-null"); this.alias = Objects.requireNonNull(alias, "alias must be non-null"); this.canonicalName = Objects.requireNonNull(canonicalName, "canonicalName must be non-null"); + this.dnsZone = Objects.requireNonNull(dnsZone, "dnsZone must be non-null"); + this.rotations = ImmutableSortedSet.copyOf(Objects.requireNonNull(rotations, "rotations must be non-null")); } /** The application owning this */ @@ -37,40 +47,53 @@ public class LoadBalancerAlias { return owner; } - /** The ID of the DNS record represented by this */ - public String id() { - return id; + /** The ID of the DNS record identifying this */ + public String recordId() { + return recordId; } - /** This alias (lhs of the CNAME record) */ + /** This alias (lhs of a CNAME record) */ public HostName alias() { return alias; } - /** The canonical name of this (rhs of the CNAME record) */ + /** The canonical name for this (rhs of a CNAME record) */ public HostName canonicalName() { return canonicalName; } + /** DNS zone for this, if any */ + public Optional<String> dnsZone() { + return dnsZone; + } + + /** The rotations in this policy */ + public Set<RotationName> rotations() { + return rotations; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - LoadBalancerAlias that = (LoadBalancerAlias) o; - return Objects.equals(owner, that.owner) && - Objects.equals(id, that.id) && - Objects.equals(alias, that.alias) && - Objects.equals(canonicalName, that.canonicalName); + RoutingPolicy that = (RoutingPolicy) o; + return owner.equals(that.owner) && + recordId.equals(that.recordId) && + alias.equals(that.alias) && + canonicalName.equals(that.canonicalName) && + dnsZone.equals(that.dnsZone) && + rotations.equals(that.rotations); } @Override public int hashCode() { - return Objects.hash(owner, id, alias, canonicalName); + return Objects.hash(owner, recordId, alias, canonicalName, dnsZone, rotations); } @Override public String toString() { - return String.format("%s: %s -> %s, owned by %s", id, alias, canonicalName, owner.toShortString()); + return String.format("%s: %s -> %s [rotations: %s%s], owned by %s", recordId, alias, canonicalName, rotations, + dnsZone.map(z -> ", DNS zone: " + z).orElse(""), owner.toShortString()); } public static String createAlias(ClusterSpec.Id clusterId, ApplicationId applicationId, ZoneId zoneId) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java index 5045ab0877c..cc9e4020dab 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java @@ -52,7 +52,7 @@ public class ControllerMaintenance extends AbstractComponent { private final JobRunner jobRunner; private final ContactInformationMaintainer contactInformationMaintainer; private final CostReportMaintainer costReportMaintainer; - private final LoadBalancerAliasMaintainer loadBalancerAliasMaintainer; + private final RoutingPolicyMaintainer routingPolicyMaintainer; @SuppressWarnings("unused") // instantiated by Dependency Injection public ControllerMaintenance(MaintainerConfig maintainerConfig, ApiAuthorityConfig apiAuthorityConfig, Controller controller, CuratorDb curator, @@ -82,7 +82,7 @@ public class ControllerMaintenance extends AbstractComponent { osVersionStatusUpdater = new OsVersionStatusUpdater(controller, maintenanceInterval, jobControl); contactInformationMaintainer = new ContactInformationMaintainer(controller, Duration.ofHours(12), jobControl, contactRetriever); costReportMaintainer = new CostReportMaintainer(controller, Duration.ofHours(2), reportConsumer, jobControl, nodeRepositoryClient, Clock.systemUTC(), selfHostedCostConfig); - loadBalancerAliasMaintainer = new LoadBalancerAliasMaintainer(controller, Duration.ofMinutes(5), jobControl, nameService, curator); + routingPolicyMaintainer = new RoutingPolicyMaintainer(controller, Duration.ofMinutes(5), jobControl, nameService, curator); } public Upgrader upgrader() { return upgrader; } @@ -110,7 +110,7 @@ public class ControllerMaintenance extends AbstractComponent { jobRunner.deconstruct(); contactInformationMaintainer.deconstruct(); costReportMaintainer.deconstruct(); - loadBalancerAliasMaintainer.deconstruct(); + routingPolicyMaintainer.deconstruct(); } /** Create one OS upgrader per cloud found in the zone registry of controller */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/LoadBalancerAliasMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainer.java index 407fdfbe466..42e8a572815 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/LoadBalancerAliasMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainer.java @@ -17,7 +17,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordId; import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.Deployment; -import com.yahoo.vespa.hosted.controller.application.LoadBalancerAlias; +import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import java.time.Duration; @@ -33,23 +33,23 @@ import java.util.logging.Logger; import java.util.stream.Collectors; /** - * Maintains DNS aliases for all load balancers in this system. + * Maintains routing policies for all exclusive load balancers in this system. * * @author mortent */ -public class LoadBalancerAliasMaintainer extends Maintainer { +public class RoutingPolicyMaintainer extends Maintainer { - private static final Logger log = Logger.getLogger(LoadBalancerAliasMaintainer.class.getName()); + private static final Logger log = Logger.getLogger(RoutingPolicyMaintainer.class.getName()); private final NameService nameService; private final CuratorDb db; private final ApplicationController applications; - public LoadBalancerAliasMaintainer(Controller controller, - Duration interval, - JobControl jobControl, - NameService nameService, - CuratorDb db) { + public RoutingPolicyMaintainer(Controller controller, + Duration interval, + JobControl jobControl, + NameService nameService, + CuratorDb db) { super(controller, interval, jobControl); this.nameService = nameService; this.db = db; @@ -73,26 +73,26 @@ public class LoadBalancerAliasMaintainer extends Maintainer { applications.store(locked.withLoadBalancersIn(zone, loadBalancers)); }); - try (Lock lock = db.lockLoadBalancerAliases()) { - Set<LoadBalancerAlias> aliases = new LinkedHashSet<>(db.readLoadBalancerAliases(application.id())); + try (Lock lock = db.lockRoutingPolicies()) { + Set<RoutingPolicy> policies = new LinkedHashSet<>(db.readRoutingPolicies(application.id())); for (LoadBalancer loadBalancer : loadBalancers) { try { - aliases.add(registerDnsAlias(application.id(), zone, loadBalancer)); + policies.add(registerDnsAlias(application.id(), zone, loadBalancer)); } catch (Exception e) { log.log(LogLevel.WARNING, "Failed to create or update DNS record for load balancer " + loadBalancer.hostname() + ". Retrying in " + maintenanceInterval(), e); } } - db.writeLoadBalancerAliases(application.id(), aliases); + db.writeRoutingPolicies(application.id(), policies); } } } } /** Register DNS alias for given load balancer */ - private LoadBalancerAlias registerDnsAlias(ApplicationId application, ZoneId zone, LoadBalancer loadBalancer) { - HostName alias = HostName.from(LoadBalancerAlias.createAlias(loadBalancer.cluster(), application, zone)); + private RoutingPolicy registerDnsAlias(ApplicationId application, ZoneId zone, LoadBalancer loadBalancer) { + HostName alias = HostName.from(RoutingPolicy.createAlias(loadBalancer.cluster(), application, zone)); RecordName name = RecordName.from(alias.value()); RecordData data = RecordData.fqdn(loadBalancer.hostname().value()); List<Record> existingRecords = nameService.findRecords(Record.Type.CNAME, name); @@ -107,7 +107,8 @@ public class LoadBalancerAliasMaintainer extends Maintainer { } else { id = nameService.createCname(name, data); } - return new LoadBalancerAlias(application, id.asString(), alias, loadBalancer.hostname()); + return new RoutingPolicy(application, id.asString(), alias, loadBalancer.hostname(), loadBalancer.dnsZone(), + loadBalancer.rotations()); } /** Find all load balancers assigned to application in given zone */ @@ -124,8 +125,8 @@ public class LoadBalancerAliasMaintainer extends Maintainer { /** Remove all DNS records that point to non-existing load balancers */ private void removeObsoleteDnsRecords() { - try (Lock lock = db.lockLoadBalancerAliases()) { - List<LoadBalancerAlias> removalCandidates = new ArrayList<>(db.readLoadBalancerAliases()); + try (Lock lock = db.lockRoutingPolicies()) { + List<RoutingPolicy> removalCandidates = new ArrayList<>(db.readRoutingPolicies()); Set<HostName> activeLoadBalancers = controller().applications().asList().stream() .map(Application::deployments) .map(Map::values) @@ -137,11 +138,11 @@ public class LoadBalancerAliasMaintainer extends Maintainer { // Remove any active load balancers removalCandidates.removeIf(lb -> activeLoadBalancers.contains(lb.canonicalName())); - for (LoadBalancerAlias alias : removalCandidates) { + for (RoutingPolicy policy : removalCandidates) { try { - nameService.removeRecord(new RecordId(alias.id())); + nameService.removeRecord(new RecordId(policy.recordId())); } catch (Exception e) { - log.log(LogLevel.WARNING, "Failed to remove DNS record with ID '" + alias.id() + + log.log(LogLevel.WARNING, "Failed to remove DNS record with ID '" + policy.recordId() + "'. Retrying in " + maintenanceInterval()); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java index 466e00cef2a..e9920c9f1c6 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java @@ -16,9 +16,9 @@ import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; +import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import com.yahoo.vespa.hosted.controller.deployment.Run; import com.yahoo.vespa.hosted.controller.deployment.Step; -import com.yahoo.vespa.hosted.controller.application.LoadBalancerAlias; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; import com.yahoo.vespa.hosted.controller.tenant.UserTenant; @@ -73,7 +73,8 @@ public class CuratorDb { private static final Path applicationRoot = root.append("applications"); private static final Path jobRoot = root.append("jobs"); private static final Path controllerRoot = root.append("controllers"); - private static final Path loadBalancerAliasesRoot = root.append("loadBalancerAliases"); + private static final Path legacyRoutingPoliciesRoot = root.append("loadBalancerAliases"); + private static final Path routingPoliciesRoot = root.append("routingPolicies"); private final StringSetSerializer stringSetSerializer = new StringSetSerializer(); private final VersionStatusSerializer versionStatusSerializer = new VersionStatusSerializer(); @@ -84,7 +85,7 @@ public class CuratorDb { private final RunSerializer runSerializer = new RunSerializer(); private final OsVersionSerializer osVersionSerializer = new OsVersionSerializer(); private final OsVersionStatusSerializer osVersionStatusSerializer = new OsVersionStatusSerializer(osVersionSerializer); - private final LoadBalancerAliasSerializer loadBalancerAliasSerializer = new LoadBalancerAliasSerializer(); + private final RoutingPolicySerializer routingPolicySerializer = new RoutingPolicySerializer(); private final Curator curator; private final Duration tryLockTimeout; @@ -185,8 +186,16 @@ public class CuratorDb { return lock(lockRoot.append("osVersionStatus"), defaultLockTimeout); } - public Lock lockLoadBalancerAliases() { - return lock(lockRoot.append("loadBalancerAliases"), defaultLockTimeout); + public Lock lockRoutingPolicies() { + Set<Version> clusterVersions = cluster().stream() + .map(this::readControllerVersion) + .collect(Collectors.toSet()); + // TODO: Remove this once cluster has completely upgraded once + Path newPath = lockRoot.append("routingPolicies"); + if (clusterVersions.size() > 1 && !curator.exists(newPath)) { + return lock(lockRoot.append("loadBalancerAliases"), defaultLockTimeout); + } + return lock(newPath, defaultLockTimeout); } // -------------- Helpers ------------------------------------------ @@ -467,22 +476,29 @@ public class CuratorDb { curator.set(openStackServerPoolPath(), data); } - // -------------- Load balancer aliases------------------------------------ + // -------------- Routing policies ---------------------------------------- - public void writeLoadBalancerAliases(ApplicationId application, Set<LoadBalancerAlias> aliases) { - curator.set(loadBalancerAliasPath(application), asJson(loadBalancerAliasSerializer.toSlime(aliases))); + public void writeRoutingPolicies(ApplicationId application, Set<RoutingPolicy> policies) { + curator.set(routingPolicyPath(application), asJson(routingPolicySerializer.toSlime(policies))); } - public Set<LoadBalancerAlias> readLoadBalancerAliases() { - return curator.getChildren(loadBalancerAliasesRoot).stream() - .map(ApplicationId::fromSerializedForm) - .flatMap(application -> readLoadBalancerAliases(application).stream()) - .collect(Collectors.toUnmodifiableSet()); + public Set<RoutingPolicy> readRoutingPolicies() { + List<String> children; + if (curator.exists(routingPoliciesRoot)) { + children = curator.getChildren(routingPoliciesRoot); + curator.delete(legacyRoutingPoliciesRoot); + } else { + children = curator.getChildren(legacyRoutingPoliciesRoot); // TODO: Remove after 7.9 has been released + } + return children.stream() + .map(ApplicationId::fromSerializedForm) + .flatMap(application -> readRoutingPolicies(application).stream()) + .collect(Collectors.toUnmodifiableSet()); } - public Set<LoadBalancerAlias> readLoadBalancerAliases(ApplicationId application) { - return readSlime(loadBalancerAliasPath(application)).map(slime -> loadBalancerAliasSerializer.fromSlime(application, slime)) - .orElseGet(Collections::emptySet); + public Set<RoutingPolicy> readRoutingPolicies(ApplicationId application) { + return readSlime(routingPolicyPath(application)).map(slime -> routingPolicySerializer.fromSlime(application, slime)) + .orElseGet(Collections::emptySet); } // -------------- Paths --------------------------------------------------- @@ -560,8 +576,8 @@ public class CuratorDb { return root.append("versionStatus"); } - private static Path loadBalancerAliasPath(ApplicationId application) { - return loadBalancerAliasesRoot.append(application.serializedForm()); + private static Path routingPolicyPath(ApplicationId application) { + return routingPoliciesRoot.append(application.serializedForm()); } private static Path provisionStatePath() { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LoadBalancerAliasSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LoadBalancerAliasSerializer.java deleted file mode 100644 index 0e7682eaf96..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LoadBalancerAliasSerializer.java +++ /dev/null @@ -1,52 +0,0 @@ -// 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.persistence; - -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.HostName; -import com.yahoo.slime.ArrayTraverser; -import com.yahoo.slime.Cursor; -import com.yahoo.slime.Slime; -import com.yahoo.vespa.hosted.controller.application.LoadBalancerAlias; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * Serializer and deserializer for a {@link LoadBalancerAlias}. - * - * @author mortent - */ -public class LoadBalancerAliasSerializer { - - private static final String aliasesField = "aliases"; - private static final String idField = "id"; - private static final String aliasField = "alias"; - private static final String canonicalNameField = "canonicalName"; - - public Slime toSlime(Set<LoadBalancerAlias> aliases) { - Slime slime = new Slime(); - Cursor root = slime.setObject(); - Cursor aliasArray = root.setArray(aliasesField); - aliases.forEach(alias -> { - Cursor nameObject = aliasArray.addObject(); - nameObject.setString(idField, alias.id()); - nameObject.setString(aliasField, alias.alias().value()); - nameObject.setString(canonicalNameField, alias.canonicalName().value()); - }); - return slime; - } - - public Set<LoadBalancerAlias> fromSlime(ApplicationId owner, Slime slime) { - Set<LoadBalancerAlias> names = new LinkedHashSet<>(); - slime.get().field(aliasesField).traverse((ArrayTraverser) (i, inspect) -> { - names.add(new LoadBalancerAlias(owner, - inspect.field(idField).asString(), - HostName.from(inspect.field(aliasField).asString()), - HostName.from(inspect.field(canonicalNameField).asString()))); - }); - - return Collections.unmodifiableSet(names); - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java new file mode 100644 index 00000000000..479d87cbe71 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java @@ -0,0 +1,81 @@ +// 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.persistence; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.RotationName; +import com.yahoo.slime.ArrayTraverser; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Inspector; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +/** + * Serializer and deserializer for a {@link RoutingPolicy}. + * + * @author mortent + */ +public class RoutingPolicySerializer { + + private static final String routingPoliciesField = "routingPolicies"; + private static final String aliasesField = "aliases"; + private static final String idField = "id"; + private static final String recordIdField = "recordId"; + private static final String aliasField = "alias"; + private static final String canonicalNameField = "canonicalName"; + private static final String dnsZoneField = "dnsZone"; + private static final String rotationsField = "rotations"; + + public Slime toSlime(Set<RoutingPolicy> routingPolicies) { + Slime slime = new Slime(); + Cursor root = slime.setObject(); + Cursor policyArray = root.setArray(routingPoliciesField); + routingPolicies.forEach(policy -> { + Cursor policyObject = policyArray.addObject(); + policyObject.setString(recordIdField, policy.recordId()); + policyObject.setString(aliasField, policy.alias().value()); + policyObject.setString(canonicalNameField, policy.canonicalName().value()); + policy.dnsZone().ifPresent(dnsZone -> policyObject.setString(dnsZoneField, dnsZone)); + Cursor rotationArray = policyObject.setArray(rotationsField); + policy.rotations().forEach(rotation -> { + rotationArray.addString(rotation.value()); + }); + }); + return slime; + } + + public Set<RoutingPolicy> fromSlime(ApplicationId owner, Slime slime) { + Set<RoutingPolicy> policies = new LinkedHashSet<>(); + Cursor root = slime.get(); + Cursor field = root.field(routingPoliciesField); + if (!field.valid()) { + field = root.field(aliasesField); // TODO: Remove after 7.9 has been released + } + field.traverse((ArrayTraverser) (i, inspect) -> { + Set<RotationName> rotations = new LinkedHashSet<>(); + inspect.field(rotationsField).traverse((ArrayTraverser) (j, rotation) -> rotations.add(RotationName.from(rotation.asString()))); + Inspector recordId = inspect.field(recordIdField); + if (!recordId.valid()) { + recordId = inspect.field(idField); // TODO: Remove after 7.9 has been released + } + policies.add(new RoutingPolicy(owner, + recordId.asString(), + HostName.from(inspect.field(aliasField).asString()), + HostName.from(inspect.field(canonicalNameField).asString()), + optionalField(inspect.field(dnsZoneField), Function.identity()), + rotations)); + }); + return Collections.unmodifiableSet(policies); + } + + private static <T> Optional<T> optionalField(Inspector field, Function<String, T> fieldMapper) { + return Optional.of(field).filter(Inspector::valid).map(Inspector::asString).map(fieldMapper); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/LoadBalancerAliasTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicyTest.java index a331ee07239..2e2ab3d9f67 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/LoadBalancerAliasTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicyTest.java @@ -6,13 +6,13 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import org.junit.Test; -import static com.yahoo.vespa.hosted.controller.application.LoadBalancerAlias.createAlias; +import static com.yahoo.vespa.hosted.controller.application.RoutingPolicy.createAlias; import static org.junit.Assert.*; /** * @author mpolden */ -public class LoadBalancerAliasTest { +public class RoutingPolicyTest { @Test public void test_endpoint_names() { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/LoadBalancerAliasMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainerTest.java index fa3fe505f3c..f1c571da451 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/LoadBalancerAliasMaintainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainerTest.java @@ -5,18 +5,17 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.RotationName; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; -import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; 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.RecordId; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; -import com.yahoo.vespa.hosted.controller.application.LoadBalancerAlias; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; import org.junit.Test; @@ -25,23 +24,25 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import static org.junit.Assert.assertEquals; /** * @author mortent */ -public class LoadBalancerAliasMaintainerTest { +public class RoutingPolicyMaintainerTest { @Test - public void maintains_load_balancer_records_correctly() { + public void maintains_routing_policies() { DeploymentTester tester = new DeploymentTester(); Application application = tester.createApplication("app1", "tenant1", 1, 1L); - LoadBalancerAliasMaintainer maintainer = new LoadBalancerAliasMaintainer(tester.controller(), Duration.ofHours(12), - new JobControl(new MockCuratorDb()), - tester.controllerTester().nameService(), - tester.controllerTester().curator()); + RoutingPolicyMaintainer maintainer = new RoutingPolicyMaintainer(tester.controller(), Duration.ofHours(12), + new JobControl(new MockCuratorDb()), + tester.controllerTester().nameService(), + tester.controllerTester().curator()); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .environment(Environment.prod) @@ -57,18 +58,17 @@ public class LoadBalancerAliasMaintainerTest { maintainer.maintain(); Map<RecordId, Record> records = tester.controllerTester().nameService().records(); - long recordCount = records.entrySet().stream().filter(entry -> entry.getValue().data().asString().contains("loadbalancer")).count(); - assertEquals(4, recordCount); + Supplier<Long> recordCount = () -> records.entrySet().stream().filter(entry -> entry.getValue().data().asString().contains("loadbalancer")).count(); + assertEquals(4, (long) recordCount.get()); - Set<LoadBalancerAlias> loadBalancerAliases = tester.controller().curator().readLoadBalancerAliases(application.id()); - assertEquals(4, loadBalancerAliases.size()); + Set<RoutingPolicy> policies = tester.controller().curator().readRoutingPolicies(application.id()); + assertEquals(4, policies.size()); // no update maintainer.maintain(); Map<RecordId, Record> records2 = tester.controllerTester().nameService().records(); - long recordCount2 = records2.entrySet().stream().filter(entry -> entry.getValue().data().asString().contains("loadbalancer")).count(); - assertEquals(recordCount, recordCount2); + assertEquals(4, (long) recordCount.get()); assertEquals(records, records2); @@ -76,12 +76,10 @@ public class LoadBalancerAliasMaintainerTest { setupClustersWithLoadBalancers(tester, application, numberOfClustersPerZone + 1); maintainer.maintain(); - Map<RecordId, Record> records3 = tester.controllerTester().nameService().records(); - long recordCount3 = records3.entrySet().stream().filter(entry -> entry.getValue().data().asString().contains("loadbalancer")).count(); - assertEquals(6,recordCount3); + assertEquals(6, (long) recordCount.get()); - Set<LoadBalancerAlias> aliases3 = tester.controller().curator().readLoadBalancerAliases(application.id()); - assertEquals(6, aliases3.size()); + Set<RoutingPolicy> policies2 = tester.controller().curator().readRoutingPolicies(application.id()); + assertEquals(6, policies2.size()); // Add application @@ -90,11 +88,9 @@ public class LoadBalancerAliasMaintainerTest { setupClustersWithLoadBalancers(tester, application2, numberOfClustersPerZone); maintainer.maintain(); - Map<RecordId, Record> records4 = tester.controllerTester().nameService().records(); - long recordCount4 = records4.entrySet().stream().filter(entry -> entry.getValue().data().asString().contains("loadbalancer")).count(); - assertEquals(10,recordCount4); + assertEquals(10, (long) recordCount.get()); - Set<LoadBalancerAlias> aliases4 = tester.controller().curator().readLoadBalancerAliases(application2.id()); + Set<RoutingPolicy> aliases4 = tester.controller().curator().readRoutingPolicies(application2.id()); assertEquals(4, aliases4.size()); @@ -102,9 +98,7 @@ public class LoadBalancerAliasMaintainerTest { setupClustersWithLoadBalancers(tester, application, numberOfClustersPerZone); maintainer.maintain(); - Map<RecordId, Record> records5 = tester.controllerTester().nameService().records(); - long recordCount5 = records5.entrySet().stream().filter(entry -> entry.getValue().data().asString().contains("loadbalancer")).count(); - assertEquals(8, recordCount5); + assertEquals(8, (long) recordCount.get()); // Remove application app2 tester.controller().applications().get(application2.id()) @@ -113,9 +107,7 @@ public class LoadBalancerAliasMaintainerTest { .forEach(zone -> tester.controller().applications().deactivate(application2.id(), zone)); maintainer.maintain(); - Map<RecordId, Record> records6 = tester.controllerTester().nameService().records(); - long recordCount6 = records6.entrySet().stream().filter(entry -> entry.getValue().data().asString().contains("loadbalancer")).count(); - assertEquals(4, recordCount6); + assertEquals(4, (long) recordCount.get()); } private void setupClustersWithLoadBalancers(DeploymentTester tester, Application application, int numberOfClustersPerZone) { @@ -128,17 +120,18 @@ public class LoadBalancerAliasMaintainerTest { } - private List<LoadBalancer> makeLoadBalancers(ZoneId zone, ApplicationId applicationId, int count) { + private List<LoadBalancer> makeLoadBalancers(ZoneId zone, ApplicationId application, int count) { List<LoadBalancer> loadBalancers = new ArrayList<>(); + Set<RotationName> rotations = Collections.singleton(RotationName.from("r1")); for (int i = 0; i < count; i++) { loadBalancers.add( new LoadBalancer("LB-" + i + "-Z-" + zone.value(), - new TenantId(applicationId.tenant().value()), - new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(applicationId.application().value()), - new InstanceId(applicationId.instance().value()), + application, ClusterSpec.Id.from("cluster-" + i), - HostName.from("loadbalancer-" + i + "-" + applicationId.serializedForm() + "-zone-" + zone.value()) - )); + HostName.from("loadbalancer-" + i + "-" + application.serializedForm() + + "-zone-" + zone.value()), + Optional.of("dns-zone-1"), + rotations)); } return loadBalancers; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/LoadBalancerAliasSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/LoadBalancerAliasSerializerTest.java deleted file mode 100644 index 6ef15be775e..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/LoadBalancerAliasSerializerTest.java +++ /dev/null @@ -1,35 +0,0 @@ -// 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.persistence; - -import com.google.common.collect.ImmutableSet; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.HostName; -import com.yahoo.vespa.hosted.controller.application.LoadBalancerAlias; -import org.junit.Test; - -import java.util.Set; - -import static org.junit.Assert.assertEquals; - -/** - * @author mortent - */ -public class LoadBalancerAliasSerializerTest { - - @Test - public void test_serialization() { - LoadBalancerAliasSerializer serializer = new LoadBalancerAliasSerializer(); - ApplicationId owner = ApplicationId.defaultId(); - Set<LoadBalancerAlias> names = ImmutableSet.of(new LoadBalancerAlias(owner, - "record-id-1", - HostName.from("my-pretty-alias"), - HostName.from("long-and-ugly-name")), - new LoadBalancerAlias(owner, - "record-id-2", - HostName.from("my-pretty-alias-2"), - HostName.from("long-and-ugly-name-2"))); - Set<LoadBalancerAlias> serialized = serializer.fromSlime(owner, serializer.toSlime(names)); - assertEquals(names, serialized); - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java new file mode 100644 index 00000000000..7dc31581d3b --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java @@ -0,0 +1,78 @@ +// 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.persistence; + +import com.google.common.collect.ImmutableSet; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.RotationName; +import com.yahoo.vespa.config.SlimeUtils; +import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; +import org.junit.Test; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +/** + * @author mortent + */ +public class RoutingPolicySerializerTest { + + @Test + public void test_serialization() { + RoutingPolicySerializer serializer = new RoutingPolicySerializer(); + ApplicationId owner = ApplicationId.defaultId(); + Set<RotationName> rotations = Set.of(RotationName.from("r1"), RotationName.from("r2")); + Set<RoutingPolicy> loadBalancers = ImmutableSet.of(new RoutingPolicy(owner, + "record-id-1", + HostName.from("my-pretty-alias"), + HostName.from("long-and-ugly-name"), + Optional.of("zone1"), + rotations), + new RoutingPolicy(owner, + "record-id-2", + HostName.from("my-pretty-alias-2"), + HostName.from("long-and-ugly-name-2"), + Optional.empty(), + rotations)); + Set<RoutingPolicy> serialized = serializer.fromSlime(owner, serializer.toSlime(loadBalancers)); + assertEquals(loadBalancers, serialized); + } + + // TODO: Remove after 7.9 has been released + @Test + public void test_serialization_old_format() { + String json = "{\n" + + " \"aliases\": [\n" + + " {\n" + + " \"id\": \"record-id-1\",\n" + + " \"alias\": \"my-pretty-alias\",\n" + + " \"canonicalName\": \"long-and-ugly-name\"" + + " },\n" + + " {\n" + + " \"id\": \"record-id-2\",\n" + + " \"alias\": \"my-pretty-alias-2\",\n" + + " \"canonicalName\": \"long-and-ugly-name-2\"" + + " }\n" + + " ]\n" + + "}\n"; + ApplicationId owner = ApplicationId.defaultId(); + Set<RoutingPolicy> loadBalancers = ImmutableSet.of(new RoutingPolicy(owner, + "record-id-1", + HostName.from("my-pretty-alias"), + HostName.from("long-and-ugly-name"), + Optional.empty(), + Collections.emptySet()), + new RoutingPolicy(owner, + "record-id-2", + HostName.from("my-pretty-alias-2"), + HostName.from("long-and-ugly-name-2"), + Optional.empty(), + Collections.emptySet())); + RoutingPolicySerializer serializer = new RoutingPolicySerializer(); + assertEquals(loadBalancers, serializer.fromSlime(owner, SlimeUtils.jsonToSlime(json))); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json index 169c86fabbe..dd64d480453 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json @@ -16,7 +16,7 @@ "name": "CostReportMaintainer" }, { - "name":"DefaultOsUpgrader" + "name": "DefaultOsUpgrader" }, { "name": "DeploymentExpirer" @@ -34,9 +34,6 @@ "name": "JobRunner" }, { - "name": "LoadBalancerAliasMaintainer" - }, - { "name": "MetricsReporter" }, { @@ -49,6 +46,9 @@ "name": "ReadyJobsTrigger" }, { + "name": "RoutingPolicyMaintainer" + }, + { "name": "SystemUpgrader" }, { |