diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-10-29 14:51:18 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2021-11-02 09:01:16 +0100 |
commit | 0aa42f1413bc86650c2bd11aa2790e2338af9ffa (patch) | |
tree | 2ae074622fda327da174731cf91419b880585aaf /config-model-api/src/main/java/com/yahoo/config/application/api/Endpoint.java | |
parent | f179c34f92cc73a74965852d14342ca0148ed788 (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.java | 136 |
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); + } + } } |