aboutsummaryrefslogtreecommitdiffstats
path: root/config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2021-10-29 14:51:18 +0200
committerMartin Polden <mpolden@mpolden.no>2021-11-02 09:01:16 +0100
commit0aa42f1413bc86650c2bd11aa2790e2338af9ffa (patch)
tree2ae074622fda327da174731cf91419b880585aaf /config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java
parentf179c34f92cc73a74965852d14342ca0148ed788 (diff)
Support application-level endpoints in DeploymentSpec
Diffstat (limited to 'config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java')
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java136
1 files changed, 115 insertions, 21 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java b/config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java
index b3d2aa5e2a1..78bf9fc68c4 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java
@@ -1,21 +1,22 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.application.api;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import java.util.List;
import java.util.Objects;
-import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
- * Represents a (global) endpoint in 'deployments.xml'. It contains the name of the
- * endpoint (endpointId) and the name of the container cluster that the endpoint
- * should point to.
+ * Represents an application- or instance-level endpoint in deployments.xml.
*
- * If the endpoint is not set it will default to the string "default".
+ * - An instance-level endpoint is global and can span multiple regions within a single instance.
+ * - An application-level endpoint points can span multiple instances within a single region.
*
* @author ogronnesby
+ * @author mpolden
*/
public class Endpoint {
@@ -34,35 +35,67 @@ public class Endpoint {
private final String endpointId;
private final String containerId;
- private final Set<RegionName> regions;
+ private final Level level;
+ private final List<Target> targets;
- public Endpoint(String endpointId, String containerId, Set<String> regions) {
+ public Endpoint(String endpointId, String containerId, Level level, List<Target> targets) {
this.endpointId = Objects.requireNonNull(endpointId, "endpointId must be non-null");
this.containerId = Objects.requireNonNull(containerId, "containerId must be non-null");
- this.regions = Set.copyOf(
- Objects.requireNonNull(
- regions.stream().map(RegionName::from).collect(Collectors.toList()),
- "Missing 'regions' parameter"));
-
+ this.level = Objects.requireNonNull(level, "level must be non-null");
+ this.targets = List.copyOf(Objects.requireNonNull(targets, "targets must be non-null"));
if (endpointId().length() > endpointMaxLength || !endpointPattern.matcher(endpointId()).matches()) {
throw new IllegalArgumentException("Invalid endpoint ID: '" + endpointId() + "'");
}
+ if (targets.isEmpty()) throw new IllegalArgumentException("targets must be non-empty");
+ for (int i = 0; i < targets.size(); i++) {
+ for (int j = 0; j < i; j++) {
+ Target a = targets.get(i);
+ Target b = targets.get(j);
+ if (level == Level.application) {
+ // - All instance names must be distinct
+ // - All region names must be equal
+ if (a.instance().equals(b.instance())) throw new IllegalArgumentException("Instance '" + a.instance +
+ "' declared multiple times, but allowed at most once");
+ if (!a.region().equals(b.region())) throw new IllegalArgumentException("Instance '" + a.instance + "' declares a region different from instance '" +
+ b.instance() + "': '" + a.region() + "'");
+ }
+ if (level == Level.instance && a.region.equals(b.region)) {
+ // - Instance name is implicit
+ // - All regions must be distinct
+ throw new IllegalArgumentException("Region '" + a.region + "' declared multiple times, but allowed at most once");
+ }
+ }
+ }
}
+ /** The unique identifer of this */
public String endpointId() {
return endpointId;
}
+ /** The container cluster this points to */
public String containerId() {
return containerId;
}
- public Set<RegionName> regions() {
- return regions;
+ /** The regions of this points to */
+ public List<RegionName> regions() {
+ return targets.stream().map(Target::region).collect(Collectors.toUnmodifiableList());
+ }
+
+ /** The level of targets in this */
+ public Level level() {
+ return level;
}
- public Endpoint withRegions(Set<String> regions) {
- return new Endpoint(endpointId, containerId, regions);
+ /** The targets this points to */
+ public List<Target> targets() {
+ return targets;
+ }
+
+ /** Returns a copy of this with targets set to given targets */
+ public Endpoint withTargets(List<Target> targets) {
+ return new Endpoint(endpointId, containerId, level, targets);
}
@Override
@@ -70,20 +103,81 @@ public class Endpoint {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Endpoint endpoint = (Endpoint) o;
- return Objects.equals(endpointId, endpoint.endpointId) &&
- Objects.equals(containerId, endpoint.containerId) &&
- Objects.equals(regions, endpoint.regions);
+ return endpointId.equals(endpoint.endpointId) && containerId.equals(endpoint.containerId) && level == endpoint.level && targets.equals(endpoint.targets);
}
@Override
public int hashCode() {
- return Objects.hash(endpointId, containerId, regions);
+ return Objects.hash(endpointId, containerId, level, targets);
}
@Override
public String toString() {
+ if (level == Level.application) {
+ return "endpoint '" + endpointId() + "' (cluster " + containerId + ") -> " +
+ targets.stream().map(Target::toString).sorted()
+ .collect(Collectors.joining(", "));
+ }
return "endpoint '" + endpointId() + "' (cluster " + containerId + ") -> " +
- regions.stream().map(RegionName::value).sorted().collect(Collectors.joining(", "));
+ targets.stream().map(Target::region).map(RegionName::value).sorted()
+ .collect(Collectors.joining(", "));
+ }
+
+ /** The level of targets in an endpoint */
+ public enum Level {
+ application,
+ instance,
+ }
+
+ /** A target of an endpoint */
+ public static class Target {
+
+ private final RegionName region;
+ private final InstanceName instance;
+ private final int weight;
+
+ public Target(RegionName region, InstanceName instance, int weight) {
+ this.region = Objects.requireNonNull(region);
+ this.instance = Objects.requireNonNull(instance);
+ this.weight = weight;
+ if (weight < 0 || weight > 100) {
+ throw new IllegalArgumentException("Target must have weight in range [0, 100], got " + weight);
+ }
+ }
+
+ /** The region this points to */
+ public RegionName region() {
+ return region;
+ }
+
+ /** The instance this points to */
+ public InstanceName instance() {
+ return instance;
+ }
+
+ /** The routing weight of this target */
+ public int weight() {
+ return weight;
+ }
+
+ @Override
+ public String toString() {
+ return "region=" + region + ",instance=" + instance + ",weight=" + weight;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Target target = (Target) o;
+ return weight == target.weight && region.equals(target.region) && instance.equals(target.instance);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(region, instance, weight);
+ }
+
}
}