diff options
Diffstat (limited to 'controller-api/src/main/java/com/yahoo')
11 files changed, 244 insertions, 7 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java index a35d01f6891..d3331c3cfd4 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java @@ -5,6 +5,7 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.DockerImage; +import com.yahoo.config.provision.Tags; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota; @@ -22,12 +23,14 @@ import static java.util.Objects.requireNonNull; /** * Data pertaining to a deployment to be done on a config server. + * Accessor names must match the names in com.yahoo.vespa.config.server.session.PrepareParams. * * @author jonmv */ public class DeploymentData { private final ApplicationId instance; + private final Tags tags; private final ZoneId zone; private final byte[] applicationPackage; private final Version platform; @@ -41,7 +44,7 @@ public class DeploymentData { private final Optional<CloudAccount> cloudAccount; private final boolean dryRun; - public DeploymentData(ApplicationId instance, ZoneId zone, byte[] applicationPackage, Version platform, + public DeploymentData(ApplicationId instance, Tags tags, ZoneId zone, byte[] applicationPackage, Version platform, Set<ContainerEndpoint> containerEndpoints, Optional<EndpointCertificateMetadata> endpointCertificateMetadata, Optional<DockerImage> dockerImageRepo, @@ -51,6 +54,7 @@ public class DeploymentData { List<X509Certificate> operatorCertificates, Optional<CloudAccount> cloudAccount, boolean dryRun) { this.instance = requireNonNull(instance); + this.tags = requireNonNull(tags); this.zone = requireNonNull(zone); this.applicationPackage = requireNonNull(applicationPackage); this.platform = requireNonNull(platform); @@ -69,6 +73,8 @@ public class DeploymentData { return instance; } + public Tags tags() { return tags; } + public ZoneId zone() { return zone; } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java index 4e5f2ab64cb..28bb9182c6a 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java @@ -133,6 +133,11 @@ public class ZmsClientMock implements ZmsClient { return new ArrayList<>(athenz.domains.keySet()); } + public List<AthenzDomain> getDomainListByAccount(String id) { + log("getDomainListById()"); + return new ArrayList<>(); + } + @Override public boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity) { log("hasAccess(resource=%s, action=%s, identity=%s)", resource, action, identity); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java index 1659a87acb3..7ccbcf2a954 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java @@ -59,8 +59,8 @@ public sealed abstract class AliasTarget permits LatencyAliasTarget, WeightedAli public static AliasTarget unpack(RecordData data) { String[] parts = data.asString().split("/"); switch (parts[0]) { - case "latency": return LatencyAliasTarget.unpack(data); - case "weighted": return WeightedAliasTarget.unpack(data); + case LatencyAliasTarget.TARGET_TYPE: return LatencyAliasTarget.unpack(data); + case WeightedAliasTarget.TARGET_TYPE: return WeightedAliasTarget.unpack(data); } throw new IllegalArgumentException("Unknown alias type '" + parts[0] + "'"); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTarget.java new file mode 100644 index 00000000000..c3cedf93841 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTarget.java @@ -0,0 +1,57 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.dns; + +import java.util.Objects; + +/** + * Same as {@link AliasTarget}, except for targets outside AWS (cannot be targeted with ALIAS record). + * + * @author freva + */ +public sealed abstract class DirectTarget permits LatencyDirectTarget, WeightedDirectTarget { + + private final RecordData recordData; + private final String id; + + protected DirectTarget(RecordData recordData, String id) { + this.recordData = Objects.requireNonNull(recordData, "recordData must be non-null"); + this.id = Objects.requireNonNull(id, "id must be non-null"); + } + + /** A unique identifier of this record within the record group */ + public String id() { + return id; + } + + /** Data in this, e.g. IP address for records of type A */ + public RecordData recordData() { + return recordData; + } + + /** Returns the fields in this encoded as record data */ + public abstract RecordData pack(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DirectTarget that = (DirectTarget) o; + return recordData.equals(that.recordData) && id.equals(that.id); + } + + @Override + public int hashCode() { + return Objects.hash(recordData, id); + } + + /** Unpack target from given record data */ + public static DirectTarget unpack(RecordData data) { + String[] parts = data.asString().split("/"); + return switch (parts[0]) { + case LatencyDirectTarget.TARGET_TYPE -> LatencyDirectTarget.unpack(data); + case WeightedDirectTarget.TARGET_TYPE -> WeightedDirectTarget.unpack(data); + default -> throw new IllegalArgumentException("Unknown alias type '" + parts[0] + "'"); + }; + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java index 70c89b05f09..00e5218dead 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java @@ -13,6 +13,8 @@ import java.util.Objects; */ public final class LatencyAliasTarget extends AliasTarget { + static final String TARGET_TYPE = "latency"; + private final ZoneId zone; public LatencyAliasTarget(DomainName name, String dnsZone, ZoneId zone) { @@ -27,7 +29,7 @@ public final class LatencyAliasTarget extends AliasTarget { @Override public RecordData pack() { - return RecordData.from("latency/" + name().value() + "/" + dnsZone() + "/" + id()); + return RecordData.from(String.join("/", TARGET_TYPE, name().value(), dnsZone(), id())); } @Override @@ -56,7 +58,7 @@ public final class LatencyAliasTarget extends AliasTarget { throw new IllegalArgumentException("Expected data to be on format type/name/DNS-zone/zone-id, but got " + data.asString()); } - if (!"latency".equals(parts[0])) { + if (!TARGET_TYPE.equals(parts[0])) { throw new IllegalArgumentException("Unexpected type '" + parts[0] + "'"); } return new LatencyAliasTarget(DomainName.of(parts[1]), parts[2], ZoneId.from(parts[3])); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyDirectTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyDirectTarget.java new file mode 100644 index 00000000000..09795ae08a7 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyDirectTarget.java @@ -0,0 +1,66 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.dns; + +import com.yahoo.config.provision.zone.ZoneId; + +import java.util.Objects; + +/** + * An implementation of {@link DirectTarget} that uses latency-based routing. + * + * @author freva + */ +public final class LatencyDirectTarget extends DirectTarget { + + static final String TARGET_TYPE = "latency"; + + private final ZoneId zone; + + public LatencyDirectTarget(RecordData recordData, ZoneId zone) { + super(recordData, zone.value()); + this.zone = Objects.requireNonNull(zone); + } + + /** The zone this record points to */ + public ZoneId zone() { + return zone; + } + + @Override + public RecordData pack() { + return RecordData.from(String.join("/", TARGET_TYPE, recordData().asString(), id())); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + LatencyDirectTarget that = (LatencyDirectTarget) o; + return zone.equals(that.zone); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), zone); + } + + @Override + public String toString() { + return "latency target for " + recordData() + " [id=" + id() + "]"; + } + + /** Unpack latency alias from given record data */ + public static LatencyDirectTarget unpack(RecordData data) { + var parts = data.asString().split("/"); + if (parts.length != 3) { + throw new IllegalArgumentException("Expected data to be on format target-type/record-data/zone-id, but got " + + data.asString()); + } + if (!TARGET_TYPE.equals(parts[0])) { + throw new IllegalArgumentException("Unexpected type '" + parts[0] + "'"); + } + return new LatencyDirectTarget(RecordData.from(parts[1]), ZoneId.from(parts[2])); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java index 18d7bc53035..9a9270bdf7f 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java @@ -54,6 +54,20 @@ public class MemoryNameService implements NameService { } @Override + public List<Record> createDirect(RecordName name, Set<DirectTarget> targets) { + var records = targets.stream() + .sorted((a, b) -> Comparator.comparing((DirectTarget target) -> target.recordData().asString()).compare(a, b)) + .map(d -> new Record(Record.Type.DIRECT, name, d.pack())) + .collect(Collectors.toList()); + // Satisfy idempotency contract of interface + for (var r1 : records) { + this.records.removeIf(r2 -> conflicts(r1, r2)); + } + this.records.addAll(records); + return records; + } + + @Override public List<Record> createTxtRecords(RecordName name, List<RecordData> txtData) { var records = txtData.stream() .map(data -> new Record(Record.Type.TXT, name, data)) @@ -122,6 +136,11 @@ public class MemoryNameService implements NameService { AliasTarget t2 = AliasTarget.unpack(r2.data()); return t1.name().equals(t2.name()); // ALIAS records require distinct targets } + if (r1.type() == Record.Type.DIRECT && r1.type() == r2.type()) { + DirectTarget t1 = DirectTarget.unpack(r1.data()); + DirectTarget t2 = DirectTarget.unpack(r2.data()); + return t1.id().equals(t2.id()); // DIRECT records require distinct IDs + } return true; // Anything else is considered a conflict } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java index 505ff3850ab..72e983680d9 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java @@ -31,6 +31,15 @@ public interface NameService { List<Record> createAlias(RecordName name, Set<AliasTarget> targets); /** + * Create a non-standard record pointing to given targets. Implementations of this are expected to be + * idempotent + * + * @param targets Targets that should be resolved by this name. + * @return The created records. One per target. + */ + List<Record> createDirect(RecordName name, Set<DirectTarget> targets); + + /** * Create a new TXT record containing the provided data. * @param name Name of the created record * @param txtRecords TXT data values for the record, each consisting of one or more space-separated double-quoted diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java index 2f9312b2f89..e76445faa60 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java @@ -55,6 +55,7 @@ public record Record(Type type, AAAA, ALIAS, CNAME, + DIRECT, MX, NS, PTR, diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java index 6a61b62f3a4..ca01c713e93 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java @@ -16,6 +16,8 @@ import java.util.Objects; */ public final class WeightedAliasTarget extends AliasTarget { + static final String TARGET_TYPE = "weighted"; + private final long weight; public WeightedAliasTarget(DomainName name, String dnsZone, ZoneId zone, long weight) { @@ -31,7 +33,7 @@ public final class WeightedAliasTarget extends AliasTarget { @Override public RecordData pack() { - return RecordData.from("weighted/" + name().value() + "/" + dnsZone() + "/" + id() + "/" + weight); + return RecordData.from(String.join("/", TARGET_TYPE, name().value(), dnsZone(), id(), Long.toString(weight))); } @Override @@ -60,7 +62,7 @@ public final class WeightedAliasTarget extends AliasTarget { throw new IllegalArgumentException("Expected data to be on format type/name/DNS-zone/zone-id/weight, " + "but got " + data.asString()); } - if (!"weighted".equals(parts[0])) { + if (!TARGET_TYPE.equals(parts[0])) { throw new IllegalArgumentException("Unexpected type '" + parts[0] + "'"); } return new WeightedAliasTarget(DomainName.of(parts[1]), parts[2], ZoneId.from(parts[3]), diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedDirectTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedDirectTarget.java new file mode 100644 index 00000000000..b899cb57b60 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedDirectTarget.java @@ -0,0 +1,70 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.dns; + +import com.yahoo.config.provision.zone.ZoneId; + +import java.util.Objects; + +/** + * An implementation of {@link DirectTarget} where is requests are answered based on the weight assigned to the + * record, as a proportion of the total weight for all records having the same DNS name. + * + * The portion of received traffic is calculated as follows: (record weight / sum of the weights of all records). + * + * @author freva + */ +public final class WeightedDirectTarget extends DirectTarget { + + static final String TARGET_TYPE = "weighted"; + + private final long weight; + + public WeightedDirectTarget(RecordData recordData, ZoneId zone, long weight) { + super(recordData, zone.value()); + this.weight = weight; + if (weight < 0) throw new IllegalArgumentException("Weight cannot be negative"); + } + + /** The weight of this target */ + public long weight() { + return weight; + } + + @Override + public RecordData pack() { + return RecordData.from(String.join("/", TARGET_TYPE, recordData().asString(), id(), Long.toString(weight))); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + WeightedDirectTarget that = (WeightedDirectTarget) o; + return weight == that.weight; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), weight); + } + + @Override + public String toString() { + return "weighted target for " + recordData() + "[id=" + id() + ",weight=" + weight + "]"; + } + + /** Unpack weighted alias from given record data */ + public static WeightedDirectTarget unpack(RecordData data) { + var parts = data.asString().split("/"); + if (parts.length != 4) { + throw new IllegalArgumentException("Expected data to be on format target-type/record-data/zone-id/weight, " + + "but got " + data.asString()); + } + if (!TARGET_TYPE.equals(parts[0])) { + throw new IllegalArgumentException("Unexpected type '" + parts[0] + "'"); + } + return new WeightedDirectTarget(RecordData.from(parts[1]), ZoneId.from(parts[2]), Long.parseLong(parts[3])); + } + +} |