diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2023-01-18 18:32:50 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-18 18:32:50 +0100 |
commit | ce73e7681cf25865bf6f417f176eea1c85f5efba (patch) | |
tree | bd83cefec54d52788217abddcac020dbc59ba0df | |
parent | e0191b4d49048f9398395dc8c1c60dfcb383f705 (diff) |
Revert "Jonmv/private endpoints"
46 files changed, 317 insertions, 1154 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java index b36c1409459..fdde4c38fb8 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java @@ -3,23 +3,17 @@ package com.yahoo.config.application.api; import com.yahoo.config.provision.AthenzService; import com.yahoo.config.provision.CloudAccount; -import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.Tags; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.zone.ZoneId; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -32,6 +26,7 @@ import static ai.vespa.validation.Validation.requireInRange; import static com.yahoo.config.application.api.DeploymentSpec.RevisionChange.whenClear; import static com.yahoo.config.application.api.DeploymentSpec.RevisionTarget.next; import static com.yahoo.config.provision.Environment.prod; +import static java.util.stream.Collectors.toList; /** * The deployment spec for an application instance @@ -60,7 +55,6 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps { private final Optional<CloudAccount> cloudAccount; private final Notifications notifications; private final List<Endpoint> endpoints; - private final Map<ClusterSpec.Id, Map<ZoneId, ZoneEndpoint>> zoneEndpoints; public DeploymentInstanceSpec(InstanceName name, Tags tags, @@ -76,7 +70,6 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps { Optional<CloudAccount> cloudAccount, Notifications notifications, List<Endpoint> endpoints, - Map<ClusterSpec.Id, Map<ZoneId, ZoneEndpoint>> zoneEndpoints, Instant now) { super(steps); this.name = Objects.requireNonNull(name); @@ -98,9 +91,6 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps { this.cloudAccount = Objects.requireNonNull(cloudAccount); this.notifications = Objects.requireNonNull(notifications); this.endpoints = List.copyOf(Objects.requireNonNull(endpoints)); - Map<ClusterSpec.Id, Map<ZoneId, ZoneEndpoint>> zoneEndpointsCopy = new HashMap<>(); - for (var entry : zoneEndpoints.entrySet()) zoneEndpointsCopy.put(entry.getKey(), Collections.unmodifiableMap(new HashMap<>(entry.getValue()))); - this.zoneEndpoints = Collections.unmodifiableMap(zoneEndpointsCopy); validateZones(new HashSet<>(), new HashSet<>(), this); validateEndpoints(steps(), globalServiceId, this.endpoints); validateChangeBlockers(changeBlockers, now); @@ -261,25 +251,6 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps { return zones().stream().anyMatch(zone -> zone.concerns(environment, Optional.of(region))); } - /** Returns the zone endpoint specified for the given region, or empty. */ - Optional<ZoneEndpoint> zoneEndpoint(ZoneId zone, ClusterSpec.Id cluster) { - return Optional.ofNullable(zoneEndpoints.get(cluster)) - .filter(__ -> deploysTo(zone.environment(), zone.region())) - .map(zoneEndpoints -> zoneEndpoints.get(zoneEndpoints.containsKey(zone) ? zone : null)); - } - - /** Returns the zone endpoint data for this instance. */ - Map<ClusterSpec.Id, Map<ZoneId, ZoneEndpoint>> zoneEndpoints() { - return zoneEndpoints; - } - - /** The zone endpoints in the given zone, possibly default values. */ - public Map<ClusterSpec.Id, ZoneEndpoint> zoneEndpoints(ZoneId zone) { - return zoneEndpoints.keySet().stream() - .collect(Collectors.toMap(cluster -> cluster, - cluster -> zoneEndpoint(zone, cluster).orElse(ZoneEndpoint.defaultEndpoint))); - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java index d731e09d4e4..6c519a4656e 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java @@ -6,21 +6,16 @@ import com.yahoo.config.application.api.xml.DeploymentSpecXmlReader; import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.AthenzService; import com.yahoo.config.provision.CloudAccount; -import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.zone.ZoneId; import java.io.Reader; import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -52,7 +47,7 @@ public class DeploymentSpec { private final List<Step> steps; - // Attributes which can be set on the root tag and which must be available outside any particular instance + // Attributes which can be set on the root tag and which must be available outside of any particular instance private final Optional<Integer> majorVersion; private final Optional<AthenzDomain> athenzDomain; private final Optional<AthenzService> athenzService; @@ -150,10 +145,6 @@ public class DeploymentSpec { illegal(prefix + "targets undeclared region '" + target.region() + "' in instance '" + target.instance() + "'"); } - if (instance.get().zoneEndpoint(ZoneId.from(Environment.prod, target.region()), ClusterSpec.Id.from(endpoint.containerId())) - .map(zoneEndpoint -> ! zoneEndpoint.isPublicEndpoint()).orElse(false)) - illegal(prefix + "targets '" + target.region().value() + "' in '" + target.instance().value() + - "', but its zone endpoint has 'enabled' set to 'false'"); } } } @@ -184,21 +175,6 @@ public class DeploymentSpec { /** Cloud account set on the deployment root; see discussion for {@link #athenzService}. */ public Optional<CloudAccount> cloudAccount() { return cloudAccount; } - /** - * Returns the most specific zone endpoint, where specificity is given, in decreasing order: - * 1. The given instance has declared a zone endpoint for the cluster, for the given region. - * 2. The given instance has declared a universal zone endpoint for the cluster. - * 3. The application has declared a zone endpoint for the cluster, for the given region. - * 4. The application has declared a universal zone endpoint for the cluster. - * 5. None of the above apply, and the default of a publicly visible endpoint is used. - */ - public ZoneEndpoint zoneEndpoint(InstanceName instance, ZoneId zone, ClusterSpec.Id cluster) { - // TODO: look up endpoints from <dev> tag, or so, if we're to support non-prod settings. - if (zone.environment() != Environment.prod) return ZoneEndpoint.defaultEndpoint; - return instance(instance).flatMap(spec -> spec.zoneEndpoint(zone, cluster)) - .orElse(ZoneEndpoint.defaultEndpoint); - } - /** Returns the XML form of this spec, or null if it was not created by fromXml, nor is empty */ public String xmlForm() { return xmlForm; } diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java index b4be99ad20b..571cc3c7d5c 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java @@ -24,7 +24,6 @@ public enum ValidationId { skipOldConfigModels("skip-old-config-models"), // Internal use accessControl("access-control"), // Internal use, used in zones where there should be no access-control globalEndpointChange("global-endpoint-change"), // Changing global endpoints - zoneEndpointChange("zone-endpoint-change"), // Changing zone (possibly private) endpoint settings redundancyIncrease("redundancy-increase"), // Increasing redundancy - may easily cause feed blocked redundancyOne("redundancy-one"), // redundancy=1 requires a validation override on first deployment pagedSettingRemoval("paged-setting-removal"), // May cause content nodes to run out of memory diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java index fb6d834f783..8182e697e7e 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java @@ -15,8 +15,6 @@ import com.yahoo.config.application.api.DeploymentSpec.Steps; import com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy; import com.yahoo.config.application.api.DeploymentSpec.UpgradeRollout; import com.yahoo.config.application.api.Endpoint; -import com.yahoo.config.application.api.Endpoint.Level; -import com.yahoo.config.application.api.Endpoint.Target; import com.yahoo.config.application.api.Notifications; import com.yahoo.config.application.api.Notifications.Role; import com.yahoo.config.application.api.Notifications.When; @@ -24,15 +22,10 @@ import com.yahoo.config.application.api.TimeWindow; import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.AthenzService; import com.yahoo.config.provision.CloudAccount; -import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.Tags; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; -import com.yahoo.config.provision.ZoneEndpoint.AccessType; -import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.io.IOUtils; import com.yahoo.text.XML; import org.w3c.dom.Element; @@ -46,20 +39,16 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.Comparator.comparingInt; - /** * @author bratseth */ @@ -134,7 +123,7 @@ public class DeploymentSpecXmlReader { return DeploymentSpec.empty; List<Step> steps = new ArrayList<>(); - List<Endpoint> applicationEndpoints = new ArrayList<>(); + List<Endpoint> applicationEndpoints = List.of(); if ( ! containsTag(instanceTag, root)) { // deployment spec skipping explicit instance -> "default" instance steps.addAll(readInstanceContent("default", root, new HashMap<>(), root)); } @@ -152,7 +141,7 @@ public class DeploymentSpecXmlReader { steps.addAll(readNonInstanceSteps(child, new HashMap<>(), root)); // (No global service id here) } } - readEndpoints(root, Optional.empty(), steps, applicationEndpoints, Map.of()); + applicationEndpoints = readEndpoints(root, Optional.empty(), steps); } return new DeploymentSpec(steps, @@ -201,13 +190,11 @@ public class DeploymentSpecXmlReader { Notifications notifications = readNotifications(instanceElement, parentTag); // Values where there is no default - Tags tags = XML.attribute(tagsTag, instanceElement).map(Tags::fromString).orElse(Tags.empty()); + Tags tags = XML.attribute(tagsTag, instanceElement).map(value -> Tags.fromString(value)).orElse(Tags.empty()); List<Step> steps = new ArrayList<>(); for (Element instanceChild : XML.getChildren(instanceElement)) steps.addAll(readNonInstanceSteps(instanceChild, prodAttributes, instanceChild)); - List<Endpoint> endpoints = new ArrayList<>(); - Map<ClusterSpec.Id, Map<ZoneId, ZoneEndpoint>> zoneEndpoints = new LinkedHashMap<>(); - readEndpoints(instanceElement, Optional.of(instanceNameString), steps, endpoints, zoneEndpoints); + List<Endpoint> endpoints = readEndpoints(instanceElement, Optional.of(instanceNameString), steps); // Build and return instances with these values Instant now = clock.instant(); @@ -227,7 +214,6 @@ public class DeploymentSpecXmlReader { cloudAccount, notifications, endpoints, - zoneEndpoints, now)) .toList(); } @@ -320,54 +306,19 @@ public class DeploymentSpecXmlReader { return Notifications.of(emailAddresses, emailRoles); } - private void readEndpoints(Element parent, Optional<String> instance, List<Step> steps, List<Endpoint> endpoints, - Map<ClusterSpec.Id, Map<ZoneId, ZoneEndpoint>> zoneEndpoints) { + private List<Endpoint> readEndpoints(Element parent, Optional<String> instance, List<Step> steps) { var endpointsElement = XML.getChild(parent, endpointsTag); - if (endpointsElement == null) return; + if (endpointsElement == null) return List.of(); Endpoint.Level level = instance.isEmpty() ? Endpoint.Level.application : Endpoint.Level.instance; - Map<String, Endpoint> endpointsById = new LinkedHashMap<>(); - Map<String, Map<RegionName, List<ZoneEndpoint>>> endpointsByZone = new LinkedHashMap<>(); - XML.getChildren(endpointsElement, endpointTag).stream() // Read zone settings first. - .sorted(comparingInt(endpoint -> getZoneEndpointType(endpoint, level).isPresent() ? 0 : 1)) - .forEach(endpointElement -> { + Map<String, Endpoint> endpoints = new LinkedHashMap<>(); + for (var endpointElement : XML.getChildren(endpointsElement, endpointTag)) { + String endpointId = stringAttribute("id", endpointElement).orElse(Endpoint.DEFAULT_ID); String containerId = requireStringAttribute("container-id", endpointElement); - Optional<String> endpointId = stringAttribute("id", endpointElement); - Optional<String> zoneEndpointType = getZoneEndpointType(endpointElement, level); String msgPrefix = (level == Endpoint.Level.application ? "Application-level" : "Instance-level") + - " endpoint '" + endpointId.orElse(Endpoint.DEFAULT_ID) + "': "; - - if (zoneEndpointType.isPresent() && endpointId.isPresent()) - illegal(msgPrefix + "cannot declare 'id' with type 'zone' or 'private'"); - + " endpoint '" + endpointId + "': "; String invalidChild = level == Endpoint.Level.application ? "region" : "instance"; - if ( ! XML.getChildren(endpointElement, invalidChild).isEmpty()) - illegal(msgPrefix + "invalid element '" + invalidChild + "'"); - - boolean enabled = XML.attribute("enabled", endpointElement) - .map(value -> { - if (zoneEndpointType.isEmpty() || ! zoneEndpointType.get().equals("zone")) - illegal(msgPrefix + "only endpoints of type 'zone' can specify 'enabled'"); - - return switch (value) { - case "true" -> true; - case "false" -> false; - default -> throw new IllegalArgumentException(msgPrefix + "invalid 'enabled' value; must be 'true' or 'false'"); - }; - }).orElse(true); - - List<AllowedUrn> allowedUrns = new ArrayList<>(); - for (var allow : XML.getChildren(endpointElement, "allow")) { - if (zoneEndpointType.isEmpty() || ! zoneEndpointType.get().equals("private")) - illegal(msgPrefix + "only endpoints of type 'private' can specify 'allow' children"); - - switch (requireStringAttribute("with", allow)) { - case "aws-private-link" -> allowedUrns.add(new AllowedUrn(AccessType.awsPrivateLink, requireStringAttribute("arn", allow))); - case "gcp-service-connect" -> allowedUrns.add(new AllowedUrn(AccessType.gcpServiceConnect, requireStringAttribute("project", allow))); - default -> illegal("Private endpoint for container-id '" + containerId + "': " + - "invalid attribute 'with': '" + requireStringAttribute("with", allow) + "'"); - } - } + if (!XML.getChildren(endpointElement, invalidChild).isEmpty()) illegal(msgPrefix + "invalid element '" + invalidChild + "'"); List<Endpoint.Target> targets = new ArrayList<>(); if (level == Endpoint.Level.application) { @@ -394,132 +345,33 @@ public class DeploymentSpecXmlReader { if (weightSum == 0) illegal(msgPrefix + "sum of all weights must be positive, got " + weightSum); } else { if (stringAttribute("region", endpointElement).isPresent()) illegal(msgPrefix + "invalid 'region' attribute"); - Set<RegionName> regions = new LinkedHashSet<>(); for (var regionElement : XML.getChildren(endpointElement, "region")) { String region = regionElement.getTextContent(); - if (region == null || region.isBlank()) - illegal(msgPrefix + "empty 'region' element"); - if ( zoneEndpointType.isEmpty() - && Stream.of(RegionName.from(region), null) - .map(endpointsByZone.getOrDefault(containerId, new HashMap<>())::get) - .flatMap(maybeEndpoints -> maybeEndpoints == null ? Stream.empty() : maybeEndpoints.stream()) - .anyMatch(endpoint -> ! endpoint.isPublicEndpoint())) - illegal(msgPrefix + "targets zone endpoint in '" + region + "' with 'enabled' set to 'false'"); - if ( ! regions.add(RegionName.from(region))) - illegal(msgPrefix + "duplicate 'region' element: '" + region + "'"); - } - - if (zoneEndpointType.isPresent()) { - if (regions.isEmpty()) regions.add(null); - ZoneEndpoint endpoint = switch (zoneEndpointType.get()) { - case "zone" -> new ZoneEndpoint(enabled, false, List.of()); - case "private" -> new ZoneEndpoint(true, true, allowedUrns); // Doesn't turn off public visibility. - default -> throw new IllegalArgumentException("unsupported zone endpoint type '" + zoneEndpointType.get() + "'"); - }; - for (RegionName region : regions) endpointsByZone.computeIfAbsent(containerId, __ -> new LinkedHashMap<>()) - .computeIfAbsent(region, __ -> new ArrayList<>()) - .add(endpoint); - } - else { - if (regions.isEmpty()) { - // No explicit targets given for instance level endpoint. Include all declared, enabled regions by default - List<RegionName> declared = - steps.stream() - .filter(step -> step.concerns(Environment.prod)) - .flatMap(step -> step.zones().stream()) - .flatMap(zone -> zone.region().stream()) - .toList(); - if (declared.isEmpty()) illegal(msgPrefix + "no declared regions to target"); - - declared.stream().filter(region -> Stream.of(region, null) - .map(endpointsByZone.getOrDefault(containerId, new HashMap<>())::get) - .flatMap(maybeEndpoints -> maybeEndpoints == null ? Stream.empty() : maybeEndpoints.stream()) - .allMatch(ZoneEndpoint::isPublicEndpoint)) - .forEach(regions::add); - } - if (regions.isEmpty()) illegal(msgPrefix + "all eligible zone endpoints have 'enabled' set to 'false'"); - InstanceName instanceName = instance.map(InstanceName::from).get(); - for (RegionName region : regions) targets.add(new Target(region, instanceName, 1)); - } - } - - if (zoneEndpointType.isEmpty()) { - Endpoint endpoint = new Endpoint(endpointId.orElse(Endpoint.DEFAULT_ID), containerId, level, targets); - if (endpointsById.containsKey(endpoint.endpointId())) { - illegal("Endpoint ID '" + endpoint.endpointId() + "' is specified multiple times"); + if (region == null || region.isBlank()) illegal(msgPrefix + "empty 'region' element"); + targets.add(new Endpoint.Target(RegionName.from(region), + InstanceName.from(instance.get()), + 1)); } - endpointsById.put(endpoint.endpointId(), endpoint); } - }); - endpoints.addAll(endpointsById.values()); - validateAndConsolidate(endpointsByZone, zoneEndpoints); - } - - static void validateAndConsolidate(Map<String, Map<RegionName, List<ZoneEndpoint>>> in, Map<ClusterSpec.Id, Map<ZoneId, ZoneEndpoint>> out) { - in.forEach((cluster, regions) -> { - List<ZoneEndpoint> wildcards = regions.remove(null); - ZoneEndpoint wildcardZoneEndpoint = null; - ZoneEndpoint wildcardPrivateEndpoint = null; - if (wildcards != null) { - for (ZoneEndpoint endpoint : wildcards) { - if (endpoint.isPrivateEndpoint()) { - if (wildcardPrivateEndpoint != null) illegal("Multiple private endpoints (for all regions) declared for " + - "container id '" + cluster + "'"); - wildcardPrivateEndpoint = endpoint; - } - else { - if (wildcardZoneEndpoint != null) illegal("Multiple zone endpoints (for all regions) declared " + - "for container id '" + cluster + "'"); - wildcardZoneEndpoint = endpoint; - } - } + if (targets.isEmpty() && level == Endpoint.Level.instance) { + // No explicit targets given for instance level endpoint. Include all declared regions by default + InstanceName instanceName = instance.map(InstanceName::from).get(); + steps.stream() + .filter(step -> step.concerns(Environment.prod)) + .flatMap(step -> step.zones().stream()) + .flatMap(zone -> zone.region().stream()) + .distinct() + .map(region -> new Endpoint.Target(region, instanceName, 1)) + .forEach(targets::add); } - for (RegionName region : regions.keySet()) { - ZoneEndpoint zoneEndpoint = null; - ZoneEndpoint privateEndpoint = null; - for (ZoneEndpoint endpoint : regions.getOrDefault(region, List.of())) { - if (endpoint.isPrivateEndpoint()) { - if (privateEndpoint != null) illegal("Multiple private endpoints declared for " + - "container id '" + cluster + "' in region '" + region + "'"); - privateEndpoint = endpoint; - } - else { - if (zoneEndpoint != null) illegal("Multiple zone endpoints (without regions) declared " + - "for container id '" + cluster + "' in region '" + region + "'"); - zoneEndpoint = endpoint; - } - } - if (wildcardZoneEndpoint != null && zoneEndpoint != null) illegal("Zone endpoint for container id '" + cluster + "' declared " + - "both with region '" + region + "', and for all regions."); - if (wildcardPrivateEndpoint != null && privateEndpoint != null) illegal("Private endpoint for container id '" + cluster + "' declared " + - "both with region '" + region + "', and for all regions."); - - if (zoneEndpoint == null) zoneEndpoint = wildcardZoneEndpoint; - if (privateEndpoint == null) privateEndpoint = wildcardPrivateEndpoint; - - // Gosh, we made it here! Now we'll combine the settings for zone and private types into one ZoneEndpoint to rule them all. - out.computeIfAbsent(ClusterSpec.Id.from(cluster), __ -> new LinkedHashMap<>()) - .put(ZoneId.from(Environment.prod, region), new ZoneEndpoint(zoneEndpoint == null || zoneEndpoint.isPublicEndpoint(), - privateEndpoint != null, - privateEndpoint != null ? privateEndpoint.allowedUrns() : List.of())); + + Endpoint endpoint = new Endpoint(endpointId, containerId, level, targets); + if (endpoints.containsKey(endpoint.endpointId())) { + illegal("Endpoint ID '" + endpoint.endpointId() + "' is specified multiple times"); } - out.computeIfAbsent(ClusterSpec.Id.from(cluster), __ -> new LinkedHashMap<>()) - .put(null, new ZoneEndpoint(wildcardZoneEndpoint == null || wildcardZoneEndpoint.isPublicEndpoint(), - wildcardPrivateEndpoint != null, - wildcardPrivateEndpoint != null ? wildcardPrivateEndpoint.allowedUrns() : List.of())); - }); - } - - /** Returns endpoint type if a private or zone type endpoint, throws if invalid, or otherwise returns empty (global, application). */ - static Optional<String> getZoneEndpointType(Element endpoint, Level level) { - Optional<String> type = XML.attribute("type", endpoint); - if (type.isPresent() && ! List.of("zone", "private", "global", "application").contains(type.get())) - illegal("Illegal endpoint type '" + type.get() + "'"); - - String implied = switch (level) { case instance -> "global"; case application -> "application"; }; - if (type.isEmpty() || type.get().equals(implied)) return Optional.empty(); - if (level == Level.instance && (type.get().equals("zone") || type.get().equals("private"))) return type; - throw new IllegalArgumentException("Endpoints at " + level + " level cannot be of type '" + type.get() + "'"); + endpoints.put(endpoint.endpointId(), endpoint); + } + return List.copyOf(endpoints.values()); } /** diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java index b7969267328..99bf768f67e 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java @@ -26,7 +26,7 @@ public class ApplicationClusterEndpoint { ", routingMethod=" + routingMethod + ", weight=" + weight + ", hostNames=" + hostNames + - ", clusterId='" + clusterId + "'" + + ", clusterId='" + clusterId + '\'' + '}'; } diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java index 355ce651c34..96cd4810ec4 100644 --- a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java +++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java @@ -6,14 +6,10 @@ import com.yahoo.config.application.api.Endpoint.Level; import com.yahoo.config.application.api.Endpoint.Target; import com.yahoo.config.application.api.xml.DeploymentSpecXmlReader; import com.yahoo.config.provision.CloudAccount; -import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.Tags; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; -import com.yahoo.config.provision.ZoneEndpoint.AccessType; import com.yahoo.test.ManualClock; import org.junit.Test; @@ -23,7 +19,6 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -32,12 +27,9 @@ import java.util.stream.Collectors; import static com.yahoo.config.application.api.Notifications.Role.author; import static com.yahoo.config.application.api.Notifications.When.failing; import static com.yahoo.config.application.api.Notifications.When.failingCommit; -import static com.yahoo.config.provision.zone.ZoneId.defaultId; -import static com.yahoo.config.provision.zone.ZoneId.from; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1191,16 +1183,15 @@ public class DeploymentSpecTest { @Test public void customTesterFlavor() { - DeploymentSpec spec = DeploymentSpec.fromXml(""" - <deployment> - <instance id='default'> - <test tester-flavor="d-1-4-20" /> - <staging /> - <prod tester-flavor="d-2-8-50"> - <region active="false">us-north-7</region> - </prod> - </instance> - </deployment>"""); + DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>" + + " <instance id='default'>" + + " <test tester-flavor=\"d-1-4-20\" />" + + " <staging />" + + " <prod tester-flavor=\"d-2-8-50\">" + + " <region active=\"false\">us-north-7</region>" + + " </prod>" + + " </instance>" + + "</deployment>"); assertEquals(Optional.of("d-1-4-20"), spec.requireInstance("default").steps().get(0).zones().get(0).testerFlavor()); assertEquals(Optional.empty(), spec.requireInstance("default").steps().get(1).zones().get(0).testerFlavor()); assertEquals(Optional.of("d-2-8-50"), spec.requireInstance("default").steps().get(2).zones().get(0).testerFlavor()); @@ -1208,55 +1199,39 @@ public class DeploymentSpecTest { @Test public void noEndpoints() { - DeploymentSpec spec = DeploymentSpec.fromXml(""" - <deployment> - <instance id='default'/> - </deployment> - """); - assertEquals(Collections.emptyList(), spec.requireInstance("default").endpoints()); - assertEquals(ZoneEndpoint.defaultEndpoint, spec.zoneEndpoint(InstanceName.defaultName(), - defaultId(), - ClusterSpec.Id.from("cluster"))); + assertEquals(Collections.emptyList(), + DeploymentSpec.fromXml("<deployment>" + + " <instance id='default'/>" + + "</deployment>").requireInstance("default").endpoints()); } @Test public void emptyEndpoints() { - var spec = DeploymentSpec.fromXml(""" - <deployment> - <instance id='default'> - <endpoints/> - </instance> - </deployment>"""); + var spec = DeploymentSpec.fromXml("<deployment>" + + " <instance id='default'>" + + " <endpoints/>" + + " </instance>" + + "</deployment>"); assertEquals(List.of(), spec.requireInstance("default").endpoints()); - assertEquals(ZoneEndpoint.defaultEndpoint, spec.zoneEndpoint(InstanceName.defaultName(), - defaultId(), - ClusterSpec.Id.from("cluster"))); } @Test public void someEndpoints() { - var spec = DeploymentSpec.fromXml(""" - <deployment> - <instance id='default'> - <prod> - <region active="true">us-east</region> - </prod> - <endpoints> - <endpoint id="foo" container-id="bar"> - <region>us-east</region> - </endpoint> - <endpoint id="nalle" container-id="frosk" /> - <endpoint container-id="quux" /> - <endpoint container-id='bax' type='zone' enabled='true' /> - <endpoint container-id='froz' type='zone' enabled='false' /> - <endpoint container-id='froz' type='private'> - <region>us-east</region> - <allow with='aws-private-link' arn='barn' /> - <allow with='gcp-service-connect' project='nine' /> - </endpoint> - </endpoints> - </instance> - </deployment>"""); + var spec = DeploymentSpec.fromXml("" + + "<deployment>" + + " <instance id='default'>" + + " <prod>" + + " <region active=\"true\">us-east</region>" + + " </prod>" + + " <endpoints>" + + " <endpoint id=\"foo\" container-id=\"bar\">" + + " <region>us-east</region>" + + " </endpoint>" + + " <endpoint id=\"nalle\" container-id=\"frosk\" />" + + " <endpoint container-id=\"quux\" />" + + " </endpoints>" + + " </instance>" + + "</deployment>"); assertEquals( List.of("foo", "nalle", "default"), @@ -1269,59 +1244,18 @@ public class DeploymentSpecTest { ); assertEquals(List.of(RegionName.from("us-east")), spec.requireInstance("default").endpoints().get(0).regions()); - - var zone = from(Environment.prod, RegionName.from("us-east")); - assertEquals(ZoneEndpoint.defaultEndpoint, - spec.zoneEndpoint(InstanceName.from("custom"), zone, ClusterSpec.Id.from("bax"))); - assertEquals(ZoneEndpoint.defaultEndpoint, - spec.zoneEndpoint(InstanceName.from("default"), defaultId(), ClusterSpec.Id.from("bax"))); - assertEquals(ZoneEndpoint.defaultEndpoint, - spec.zoneEndpoint(InstanceName.from("default"), zone, ClusterSpec.Id.from("bax"))); - - assertEquals(new ZoneEndpoint(false, true, List.of(new AllowedUrn(AccessType.awsPrivateLink, "barn"), - new AllowedUrn(AccessType.gcpServiceConnect, "nine"))), - spec.zoneEndpoint(InstanceName.from("default"), zone, ClusterSpec.Id.from("froz"))); } @Test public void invalidEndpoints() { - assertInvalidEndpoints("<endpoint id='FOO' container-id='qrs'/>", - "Endpoint ID must be all lowercase, alphanumeric, with no consecutive dashes, of length 1 to 12, and begin with a character; but got 'FOO'"); - assertInvalidEndpoints("<endpoint id='123' container-id='qrs'/>", - "Endpoint ID must be all lowercase, alphanumeric, with no consecutive dashes, of length 1 to 12, and begin with a character; but got '123'"); - assertInvalidEndpoints("<endpoint id='foo!' container-id='qrs'/>", - "Endpoint ID must be all lowercase, alphanumeric, with no consecutive dashes, of length 1 to 12, and begin with a character; but got 'foo!'"); - assertInvalidEndpoints("<endpoint id='foo.bar' container-id='qrs'/>", - "Endpoint ID must be all lowercase, alphanumeric, with no consecutive dashes, of length 1 to 12, and begin with a character; but got 'foo.bar'"); - assertInvalidEndpoints("<endpoint id='foo--bar' container-id='qrs'/>", - "Endpoint ID must be all lowercase, alphanumeric, with no consecutive dashes, of length 1 to 12, and begin with a character; but got 'foo--bar'"); - assertInvalidEndpoints("<endpoint id='foo-' container-id='qrs'/>", - "Endpoint ID must be all lowercase, alphanumeric, with no consecutive dashes, of length 1 to 12, and begin with a character; but got 'foo-'"); - assertInvalidEndpoints("<endpoint id='foooooooooooo' container-id='qrs'/>", - "Endpoint ID must be all lowercase, alphanumeric, with no consecutive dashes, of length 1 to 12, and begin with a character; but got 'foooooooooooo'"); - - assertInvalidEndpoints("<endpoint id='foo' container-id='qrs'/><endpoint id='foo' container-id='qrs'/>", - "Endpoint ID 'foo' is specified multiple times"); - assertInvalidEndpoints("<endpoint id='default' type='zone' container-id='foo' />", - "Instance-level endpoint 'default': cannot declare 'id' with type 'zone' or 'private'"); - assertInvalidEndpoints("<endpoint id='default' type='private' container-id='foo' />", - "Instance-level endpoint 'default': cannot declare 'id' with type 'zone' or 'private'"); - assertInvalidEndpoints("<endpoint type='zone' />", - "Missing required attribute 'container-id' in 'endpoint'"); - assertInvalidEndpoints("<endpoint type='private' />", - "Missing required attribute 'container-id' in 'endpoint'"); - assertInvalidEndpoints("<endpoint container-id='foo' type='zone'><allow /></endpoint>", - "Instance-level endpoint 'default': only endpoints of type 'private' can specify 'allow' children"); - assertInvalidEndpoints("<endpoint type='private' container-id='foo' enabled='true' />", - "Instance-level endpoint 'default': only endpoints of type 'zone' can specify 'enabled'"); - assertInvalidEndpoints("<endpoint type='zone' container-id='qrs'/><endpoint type='zone' container-id='qrs'/>", - "Multiple zone endpoints (for all regions) declared for container id 'qrs'"); - assertInvalidEndpoints("<endpoint type='private' container-id='qrs'><region>us</region></endpoint>" + - "<endpoint type='private' container-id='qrs'><region>us</region></endpoint>", - "Multiple private endpoints declared for container id 'qrs' in region 'us'"); - assertInvalidEndpoints("<endpoint type='zone' container-id='qrs' />" + - "<endpoint type='zone' container-id='qrs'><region>us</region></endpoint>", - "Zone endpoint for container id 'qrs' declared both with region 'us', and for all regions."); + assertInvalidEndpoints("<endpoint id='FOO' container-id='qrs'/>"); // Uppercase + assertInvalidEndpoints("<endpoint id='123' container-id='qrs'/>"); // Starting with non-character + assertInvalidEndpoints("<endpoint id='foo!' container-id='qrs'/>"); // Non-alphanumeric + assertInvalidEndpoints("<endpoint id='foo.bar' container-id='qrs'/>"); + assertInvalidEndpoints("<endpoint id='foo--bar' container-id='qrs'/>"); // Multiple consecutive dashes + assertInvalidEndpoints("<endpoint id='foo-' container-id='qrs'/>"); // Trailing dash + assertInvalidEndpoints("<endpoint id='foooooooooooo' container-id='qrs'/>"); // Too long + assertInvalidEndpoints("<endpoint id='foo' container-id='qrs'/><endpoint id='foo' container-id='qrs'/>"); // Duplicate } @Test @@ -1337,44 +1271,25 @@ public class DeploymentSpecTest { @Test public void endpointDefaultRegions() { - var spec = DeploymentSpec.fromXml(""" - <deployment> - <instance id='default'> - <prod> - <region>us-east</region> - <region>us-west</region> - </prod> - <endpoints> - <endpoint id="foo" container-id="bar"> - <region>us-east</region> - </endpoint> - <endpoint container-id="bar" type='private'> - <region>us-east</region> - </endpoint> - <endpoint id="nalle" container-id="frosk" /> - <endpoint container-id="quux" /> - <endpoint container-id="quux" type='private' /> - </endpoints> - </instance> - </deployment>"""); + var spec = DeploymentSpec.fromXml("<deployment>" + + " <instance id='default'>" + + " <prod>" + + " <region>us-east</region>" + + " <region>us-west</region>" + + " </prod>" + + " <endpoints>" + + " <endpoint id=\"foo\" container-id=\"bar\">" + + " <region>us-east</region>" + + " </endpoint>" + + " <endpoint id=\"nalle\" container-id=\"frosk\" />" + + " <endpoint container-id=\"quux\" />" + + " </endpoints>" + + " </instance>" + + "</deployment>"); assertEquals(Set.of("us-east"), endpointRegions("foo", spec)); assertEquals(Set.of("us-east", "us-west"), endpointRegions("nalle", spec)); assertEquals(Set.of("us-east", "us-west"), endpointRegions("default", spec)); - assertEquals(new ZoneEndpoint(true, true, List.of()), - spec.zoneEndpoint(InstanceName.from("default"), from("prod", "us-east"), ClusterSpec.Id.from("bar"))); - assertEquals(new ZoneEndpoint(true, false, List.of()), - spec.zoneEndpoint(InstanceName.from("default"), from("prod", "us-west"), ClusterSpec.Id.from("bar"))); - assertEquals(new ZoneEndpoint(true, true, List.of()), - spec.zoneEndpoint(InstanceName.from("default"), from("prod", "us-east"), ClusterSpec.Id.from("quux"))); - assertEquals(new ZoneEndpoint(true, true, List.of()), - spec.zoneEndpoint(InstanceName.from("default"), from("prod", "us-west"), ClusterSpec.Id.from("quux"))); - assertEquals(new HashSet<>() {{ add(null); add(from("prod", "us-east")); }}, - spec.requireInstance("default").zoneEndpoints().get(ClusterSpec.Id.from("bar")).keySet()); - assertEquals(new HashSet<>() {{ add(null); }}, - spec.requireInstance("default").zoneEndpoints().get(ClusterSpec.Id.from("quux")).keySet()); - assertEquals(Set.of(ClusterSpec.Id.from("bar"), ClusterSpec.Id.from("quux")), - spec.requireInstance("default").zoneEndpoints().keySet()); } @Test @@ -1387,16 +1302,14 @@ public class DeploymentSpecTest { <region active="true">us-west</region> </prod> <endpoints> - <endpoint container-id="bar" %s> + <endpoint id="foo" container-id="bar" %s> %s </endpoint> </endpoints> </instance> </deployment>"""; - assertInvalid(String.format(xmlForm, "id='foo' region='us-east'", "<region>us-east</region>"), "Instance-level endpoint 'foo': invalid 'region' attribute"); - assertInvalid(String.format(xmlForm, "id='foo'", "<instance>us-east</instance>"), "Instance-level endpoint 'foo': invalid element 'instance'"); - assertInvalid(String.format(xmlForm, "type='zone'", "<instance>us-east</instance>"), "Instance-level endpoint 'default': invalid element 'instance'"); - assertInvalid(String.format(xmlForm, "type='private'", "<instance>us-east</instance>"), "Instance-level endpoint 'default': invalid element 'instance'"); + assertInvalid(String.format(xmlForm, "region='us-east'", "<region>us-east</region>"), "Instance-level endpoint 'foo': invalid 'region' attribute"); + assertInvalid(String.format(xmlForm, "", "<instance>us-east</instance>"), "Instance-level endpoint 'foo': invalid element 'instance'"); } @Test @@ -1430,73 +1343,6 @@ public class DeploymentSpecTest { assertInvalid(String.format(xmlForm, "region='us-west-1'", "weight='foo'", "", "main", ""), "Application-level endpoint 'foo': invalid weight value 'foo'"); assertInvalid(String.format(xmlForm, "region='us-west-1'", "weight='1'", "", "main", "<region>us-east-3</region>"), "Application-level endpoint 'foo': invalid element 'region'"); assertInvalid(String.format(xmlForm, "region='us-west-1'", "weight='0'", "", "main", ""), "Application-level endpoint 'foo': sum of all weights must be positive, got 0"); - assertInvalid(String.format(xmlForm, "type='zone'", "weight='1'", "", "main", ""), "Endpoints at application level cannot be of type 'zone'"); - assertInvalid(String.format(xmlForm, "type='private'", "weight='1'", "", "main", ""), "Endpoints at application level cannot be of type 'private'"); - } - - @Test - public void cannotTargetDisabledEndpoints() { - assertEquals("Instance-level endpoint 'default': all eligible zone endpoints have 'enabled' set to 'false'", - assertThrows(IllegalArgumentException.class, - () -> DeploymentSpec.fromXml(""" - <deployment> - <instance id="default"> - <prod> - <region>us</region> - <region>eu</region> - </prod> - <endpoints> - <endpoint container-id='id' /> - <endpoint type='zone' container-id='id' enabled='false' /> - </endpoints> - </instance> - </deployment> - """)) - .getMessage()); - - assertEquals("Instance-level endpoint 'default': targets zone endpoint in 'us' with 'enabled' set to 'false'", - assertThrows(IllegalArgumentException.class, - () -> DeploymentSpec.fromXml(""" - <deployment> - <instance id="default"> - <prod> - <region>us</region> - <region>eu</region> - </prod> - <endpoints> - <endpoint container-id='id'> - <region>us</region> - </endpoint> - <endpoint type='zone' container-id='id' enabled='false' /> - </endpoints> - </instance> - </deployment> - """)) - .getMessage()); - - assertEquals("Application-level endpoint 'default': targets 'us' in 'default', but its zone endpoint has 'enabled' set to 'false'", - assertThrows(IllegalArgumentException.class, - () -> DeploymentSpec.fromXml(""" - <deployment> - <instance id="default"> - <prod> - <region>us</region> - <region>eu</region> - </prod> - <endpoints> - <endpoint type='zone' container-id='id' enabled='false'> - <region>us</region> - </endpoint> - </endpoints> - </instance> - <endpoints> - <endpoint container-id='id' region='us'> - <instance weight='1'>default</instance> - </endpoint> - </endpoints> - </deployment> - """)) - .getMessage()); } @Test @@ -1802,11 +1648,11 @@ public class DeploymentSpecTest { } } - private static void assertInvalidEndpoints(String endpointsBody, String error) { - assertEquals(error, - assertThrows(IllegalArgumentException.class, - () -> endpointIds(endpointsBody)) - .getMessage()); + private static void assertInvalidEndpoints(String endpointsBody) { + try { + endpointIds(endpointsBody); + fail("Expected exception for input '" + endpointsBody + "'"); + } catch (IllegalArgumentException ignored) {} } private static Set<String> endpointRegions(String endpointId, DeploymentSpec spec) { diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java index 38410cc5b37..6ff5616a80f 100644 --- a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java +++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java @@ -3,13 +3,8 @@ package com.yahoo.config.application.api; import com.google.common.collect.ImmutableSet; import com.yahoo.config.provision.CloudAccount; -import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; -import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; -import com.yahoo.config.provision.ZoneEndpoint.AccessType; import org.junit.Test; import java.io.StringReader; @@ -25,8 +20,6 @@ import java.util.stream.Collectors; import static com.yahoo.config.application.api.Notifications.Role.author; import static com.yahoo.config.application.api.Notifications.When.failing; import static com.yahoo.config.application.api.Notifications.When.failingCommit; -import static com.yahoo.config.provision.zone.ZoneId.defaultId; -import static com.yahoo.config.provision.zone.ZoneId.from; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -661,26 +654,19 @@ public class DeploymentSpecWithoutInstanceTest { @Test public void someEndpoints() { - var spec = DeploymentSpec.fromXml(""" - <deployment> - <prod> - <region active="true">us-east</region> - </prod> - <endpoints> - <endpoint id="foo" container-id="bar"> - <region>us-east</region> - </endpoint> - <endpoint id="nalle" container-id="frosk" /> - <endpoint container-id="quux" /> - <endpoint container-id='bax' type='zone' enabled='true' /> - <endpoint container-id='froz' type='zone' enabled='false' /> - <endpoint container-id='froz' type='private'> - <region>us-east</region> - <allow with='aws-private-link' arn='barn' /> - <allow with='gcp-service-connect' project='nine' /> - </endpoint> - </endpoints> - </deployment>"""); + var spec = DeploymentSpec.fromXml("" + + "<deployment>" + + " <prod>" + + " <region active=\"true\">us-east</region>" + + " </prod>" + + " <endpoints>" + + " <endpoint id=\"foo\" container-id=\"bar\">" + + " <region>us-east</region>" + + " </endpoint>" + + " <endpoint id=\"nalle\" container-id=\"frosk\" />" + + " <endpoint container-id=\"quux\" />" + + " </endpoints>" + + "</deployment>"); assertEquals( List.of("foo", "nalle", "default"), @@ -693,18 +679,6 @@ public class DeploymentSpecWithoutInstanceTest { ); assertEquals(List.of(RegionName.from("us-east")), spec.requireInstance("default").endpoints().get(0).regions()); - - var zone = from(Environment.prod, RegionName.from("us-east")); - assertEquals(ZoneEndpoint.defaultEndpoint, - spec.zoneEndpoint(InstanceName.from("custom"), zone, ClusterSpec.Id.from("bax"))); - assertEquals(ZoneEndpoint.defaultEndpoint, - spec.zoneEndpoint(InstanceName.from("default"), defaultId(), ClusterSpec.Id.from("bax"))); - assertEquals(ZoneEndpoint.defaultEndpoint, - spec.zoneEndpoint(InstanceName.from("default"), zone, ClusterSpec.Id.from("bax"))); - - assertEquals(new ZoneEndpoint(false, true, List.of(new AllowedUrn(AccessType.awsPrivateLink, "barn"), - new AllowedUrn(AccessType.gcpServiceConnect, "nine"))), - spec.zoneEndpoint(InstanceName.from("default"), zone, ClusterSpec.Id.from("froz"))); } @Test diff --git a/config-model-fat/pom.xml b/config-model-fat/pom.xml index 0dafb96d575..6da024c6705 100644 --- a/config-model-fat/pom.xml +++ b/config-model-fat/pom.xml @@ -79,7 +79,6 @@ com.yahoo.component.provider, com.yahoo.config, com.yahoo.config.application.api, - com.yahoo.config.application.api.xml, com.yahoo.config.model.api, com.yahoo.config.model.api.container, com.yahoo.config.provision, diff --git a/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java b/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java index 756646beddb..95d97bc9e87 100644 --- a/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java +++ b/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java @@ -80,7 +80,7 @@ public class ConfigModelRepo implements ConfigModelRepoAdder, Serializable, Iter ConfigModelRegistry configModelRegistry) throws IOException { Element userServicesElement = getServicesFromApp(deployState.getApplicationPackage()); readConfigModels(root, userServicesElement, deployState, vespaModel, configModelRegistry); - builder.postProc(deployState, root, this); + builder.postProc(deployState.getDeployLogger(), root, this); } private Element getServicesFromApp(ApplicationPackage applicationPackage) throws IOException { diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java index 49194a5d1bb..7cb0672699f 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java @@ -37,7 +37,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea private ApplicationId applicationId = ApplicationId.defaultId(); private List<ConfigServerSpec> configServerSpecs = Collections.emptyList(); private boolean hostedVespa = false; - private Zone zone = Zone.defaultZone(); + private Zone zone; private final Set<ContainerEndpoint> endpoints = Collections.emptySet(); private boolean useDedicatedNodeForLogserver = false; private double defaultTermwiseLimit = 1.0; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/VespaModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/VespaModelBuilder.java index 421e3a2902c..2cf32f1e8ff 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/VespaModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/VespaModelBuilder.java @@ -22,6 +22,6 @@ public abstract class VespaModelBuilder { * @param producerRoot the root producer. * @param configModelRepo a {@link com.yahoo.config.model.ConfigModelRepo instance} */ - public abstract void postProc(DeployState deployState, AbstractConfigProducer producerRoot, ConfigModelRepo configModelRepo); + public abstract void postProc(DeployLogger deployLogger, AbstractConfigProducer producerRoot, ConfigModelRepo configModelRepo); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java index aac968f9038..a31e3fcce71 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java @@ -4,7 +4,6 @@ package com.yahoo.vespa.model.builder.xml.dom; import com.yahoo.collections.Pair; import com.yahoo.component.Version; import com.yahoo.config.application.api.DeployLogger; -import com.yahoo.config.provision.ZoneEndpoint; import com.yahoo.config.model.ConfigModelContext; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.CloudAccount; @@ -12,6 +11,7 @@ import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.DockerImage; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NodeResources; import com.yahoo.text.XML; import com.yahoo.vespa.model.HostResource; @@ -256,13 +256,13 @@ public class NodesSpecification { ClusterSpec.Id clusterId, DeployLogger logger, boolean stateful) { - return provision(hostSystem, clusterType, clusterId, ZoneEndpoint.defaultEndpoint, logger, stateful); + return provision(hostSystem, clusterType, clusterId, LoadBalancerSettings.empty, logger, stateful); } public Map<HostResource, ClusterMembership> provision(HostSystem hostSystem, ClusterSpec.Type clusterType, ClusterSpec.Id clusterId, - ZoneEndpoint zoneEndpoint, + LoadBalancerSettings loadBalancerSettings, DeployLogger logger, boolean stateful) { if (combinedId.isPresent()) @@ -272,7 +272,7 @@ public class NodesSpecification { .exclusive(exclusive) .combinedId(combinedId.map(ClusterSpec.Id::from)) .dockerImageRepository(dockerImageRepo) - .loadBalancerSettings(zoneEndpoint) + .loadBalancerSettings(loadBalancerSettings) .stateful(stateful) .build(); return hostSystem.allocateHosts(cluster, Capacity.from(min, max, required, canFail, cloudAccount), logger); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java index e4e56dcaaca..cb3c43074fc 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java @@ -1,7 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.builder.xml.dom; -import ai.vespa.validation.Validation; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.ApplicationConfigProducerRoot; import com.yahoo.config.model.ConfigModelRepo; @@ -23,15 +22,8 @@ import com.yahoo.vespa.model.container.docproc.ContainerDocproc; import com.yahoo.vespa.model.content.Content; import com.yahoo.vespa.model.search.SearchCluster; import org.w3c.dom.Element; - -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Pattern; /** * Builds Vespa model components using the w3c dom api @@ -51,12 +43,10 @@ public class VespaDomBuilder extends VespaModelBuilder { public static final String VESPAMALLOC = "vespamalloc"; // Intended for vespa engineers public static final String VESPAMALLOC_DEBUG = "vespamalloc-debug"; // Intended for vespa engineers public static final String VESPAMALLOC_DEBUG_STACKTRACE = "vespamalloc-debug-stacktrace"; // Intended for vespa engineers + private static final String CPU_SOCKET_ATTRIB_NAME = "cpu-socket"; public static final String CPU_SOCKET_AFFINITY_ATTRIB_NAME = "cpu-socket-affinity"; public static final String Allocated_MEMORY_ATTRIB_NAME = "allocated-memory"; - private static final String CPU_SOCKET_ATTRIB_NAME = "cpu-socket"; - private static final Pattern clusterPattern = Pattern.compile("([a-z0-9]|[a-z0-9][a-z0-9_-]{0,61}[a-z0-9])"); - public static final Logger log = Logger.getLogger(VespaDomBuilder.class.getPackage().toString()); @@ -242,14 +232,13 @@ public class VespaDomBuilder extends VespaModelBuilder { * @param root root config producer * @param configModelRepo a {@link ConfigModelRepo} */ - public void postProc(DeployState deployState, AbstractConfigProducer root, ConfigModelRepo configModelRepo) { + public void postProc(DeployLogger deployLogger, AbstractConfigProducer root, ConfigModelRepo configModelRepo) { setContentSearchClusterIndexes(configModelRepo); createDocprocMBusServersAndClients(configModelRepo); - if (deployState.isHosted()) validateContainerClusterIds(configModelRepo); } private void createDocprocMBusServersAndClients(ConfigModelRepo pc) { - for (ContainerCluster<?> cluster: ContainerModel.containerClusters(pc)) { + for (ContainerCluster cluster: ContainerModel.containerClusters(pc)) { addServerAndClientsForChains(cluster.getDocproc()); } } @@ -259,19 +248,6 @@ public class VespaDomBuilder extends VespaModelBuilder { docproc.getChains().addServersAndClientsForChains(); } - private void validateContainerClusterIds(ConfigModelRepo pc) { - Map<String, String> normalizedClusterIds = new LinkedHashMap<>(); - for (ContainerCluster<?> cluster: ContainerModel.containerClusters(pc)) { - if (cluster.getHttp() == null) continue; - String name = cluster.getName(); - Validation.requireMatch(name, "container cluster name", clusterPattern); - String clashing = normalizedClusterIds.put(name.replaceAll("_", "-"), name); - if (clashing != null) throw new IllegalArgumentException("container clusters '" + clashing + "' and '" + name + - "' have clashing endpoint names, when '_' is replaced " + - "with '-' to form valid domain names"); - } - } - /** * For some reason, search clusters need to be enumerated. * @param configModelRepo a {@link ConfigModelRepo} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 4c7bad575d2..d0a03be2869 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -11,8 +11,6 @@ import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.application.api.xml.DeploymentSpecXmlReader; import com.yahoo.config.model.ConfigModelContext; import com.yahoo.config.model.api.ApplicationClusterEndpoint; import com.yahoo.config.model.api.ConfigServerSpec; @@ -31,11 +29,10 @@ import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.Zone; -import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.container.logging.FileConnectionLog; import com.yahoo.io.IOUtils; @@ -850,14 +847,11 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } } - private ZoneEndpoint zoneEndpoint(ConfigModelContext context, ClusterSpec.Id cluster) { - InstanceName instance = context.properties().applicationId().instance(); - ZoneId zone = ZoneId.from(context.properties().zone().environment(), - context.properties().zone().region()); - DeploymentSpec spec = context.getApplicationPackage().getDeployment() - .map(new DeploymentSpecXmlReader(false)::read) - .orElse(DeploymentSpec.empty); - return spec.zoneEndpoint(instance, zone, cluster); + private LoadBalancerSettings loadBalancerSettings(Element loadBalancerElement) { + List<String> allowedUrnElements = XML.getChildren(XML.getChild(loadBalancerElement, "private-access"), + "allow-urn") + .stream().map(XML::getValue).toList(); + return new LoadBalancerSettings(allowedUrnElements); } private static Map<String, String> getEnvironmentVariables(Element environmentVariables) { @@ -946,12 +940,11 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { private List<ApplicationContainer> createNodesFromNodeCount(ApplicationContainerCluster cluster, Element containerElement, Element nodesElement, ConfigModelContext context) { NodesSpecification nodesSpecification = NodesSpecification.from(new ModelElement(nodesElement), context); - ClusterSpec.Id clusterId = ClusterSpec.Id.from(cluster.name()); - ZoneEndpoint zoneEndpoint = zoneEndpoint(context, clusterId); + LoadBalancerSettings loadBalancerSettings = loadBalancerSettings(XML.getChild(containerElement, "load-balancer")); Map<HostResource, ClusterMembership> hosts = nodesSpecification.provision(cluster.getRoot().hostSystem(), ClusterSpec.Type.container, - clusterId, - zoneEndpoint, + ClusterSpec.Id.from(cluster.getName()), + loadBalancerSettings, log, getZooKeeper(containerElement) != null); return createNodesFromHosts(hosts, cluster, context.getDeployState()); diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc index 81455084ad2..933ec528c42 100644 --- a/config-model/src/main/resources/schema/containercluster.rnc +++ b/config-model/src/main/resources/schema/containercluster.rnc @@ -6,7 +6,8 @@ ContainerCluster = element container { ContainerServices & DocumentBinding* & NodesOfContainerCluster? & - ClientAuthorize? + ClientAuthorize? & + LoadBalancer? } ContainerServices = @@ -311,6 +312,16 @@ NodesOfContainerCluster = element nodes { ) } +LoadBalancer = element load-balancer { + element private-access { + element allow-urn { + xsd:string + }* + }? +} + + + #DOCUMENT BINDINGS: DocumentBinding = element document { diff --git a/config-model/src/main/resources/schema/deployment.rnc b/config-model/src/main/resources/schema/deployment.rnc index d63b8885a57..444f66a92ab 100644 --- a/config-model/src/main/resources/schema/deployment.rnc +++ b/config-model/src/main/resources/schema/deployment.rnc @@ -150,21 +150,12 @@ EndpointInstance = element instance { text } -AllowedUrn = element allow { - attribute with { xsd:string } & - attribute arn { xsd:string }? & - attribute project { xsd:string }? -} - Endpoint = element endpoint { attribute id { xsd:string }? & attribute container-id { xsd:string } & attribute region { xsd:string }? & - attribute type { xsd:string }? & - attribute enabled { xsd:boolean }? & EndpointRegion* & - EndpointInstance* & - AllowedUrn* + EndpointInstance* } Endpoints = element endpoints { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java index f0c39ecc920..addf4dffde2 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java @@ -22,9 +22,6 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.Zone; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.ZoneEndpoint.AccessType; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; import com.yahoo.config.provisioning.FlavorsConfig; import com.yahoo.container.ComponentsConfig; import com.yahoo.container.QrConfig; @@ -60,6 +57,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.logging.Level; +import java.util.stream.Collectors; import static com.yahoo.config.model.test.TestUtil.joinLines; import static com.yahoo.test.LinePatternMatcher.containsLineWithPattern; @@ -181,63 +179,6 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { } @Test - void container_cluster_with_invalid_name_throws_exception_when_hosted() throws IOException, SAXException { - String servicesXml = """ - <services version='1.0'> - <container id='C-1' version='1.0'> - <nodes count='1' /> - </container> - </services> - """; - - assertEquals("container cluster name must match '([a-z0-9]|[a-z0-9][a-z0-9_-]{0,61}[a-z0-9])', but got: 'C-1'", - assertThrows(IllegalArgumentException.class, - () -> - new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder() - .modelHostProvisioner(new InMemoryProvisioner(4, false)) - .applicationPackage(new MockApplicationPackage.Builder().withServices(servicesXml).build()) - .properties(new TestProperties().setHostedVespa(true)) - .build())) - .getMessage()); - - new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder() - .modelHostProvisioner(new InMemoryProvisioner(4, false)) - .applicationPackage(new MockApplicationPackage.Builder().withServices(servicesXml).build()) - .properties(new TestProperties().setHostedVespa(false)) - .build()); - } - - @Test - void two_clusters_with_clashing_cluster_names_throws_exception_when_hosted() throws IOException, SAXException { - String servicesXml = """ - <services version='1.0'> - <container id='c-1' version='1.0'> - <nodes count='1' /> - </container> - <container id='c_1' version='1.0'> - <nodes count='1' /> - </container> - </services> - """; - - assertEquals("container clusters 'c-1' and 'c_1' have clashing endpoint names, when '_' is replaced with '-' to form valid domain names", - assertThrows(IllegalArgumentException.class, - () -> - new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder() - .modelHostProvisioner(new InMemoryProvisioner(4, false)) - .applicationPackage(new MockApplicationPackage.Builder().withServices(servicesXml).build()) - .properties(new TestProperties().setHostedVespa(true)) - .build())) - .getMessage()); - - new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder() - .modelHostProvisioner(new InMemoryProvisioner(4, false)) - .applicationPackage(new MockApplicationPackage.Builder().withServices(servicesXml).build()) - .properties(new TestProperties().setHostedVespa(false)) - .build()); - } - - @Test void two_clusters_without_explicit_port_throws_exception() { Element cluster1Elem = DomBuilderTest.parse( "<container id='cluster1' version='1.0'>", @@ -257,96 +198,53 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { @Test void load_balancers_can_be_set() throws IOException, SAXException { - // No endpoints - verifyAllowedUrns("", Environment.prod, "eu", ZoneEndpoint.defaultEndpoint); + // No load-balancer or nodes elements + verifyAllowedUrns(""); - // No non-default settings - verifyAllowedUrns(""" - <endpoint type='zone' container-id='default' /> - """, - Environment.prod, - "eu", - ZoneEndpoint.defaultEndpoint); + // No load-balancer element + verifyAllowedUrns("<nodes count='2' />"); - // No allowed urns + // No nodes element verifyAllowedUrns(""" - <endpoint type='private' container-id='default' /> - """, - Environment.prod, - "eu", - new ZoneEndpoint(true, true, List.of())); - - // Various settings - verifyAllowedUrns(""" - <endpoint type='zone' container-id='default' enabled='false' /> - <endpoint type='private' container-id='default'> - <region>eu</region> - <allow with='aws-private-link' arn='barn' /> - <allow with='gcp-service-connect' project='nine' /> - </endpoint> - """, - Environment.prod, - "eu", - new ZoneEndpoint(false, true, List.of(new AllowedUrn(AccessType.awsPrivateLink, "barn"), - new AllowedUrn(AccessType.gcpServiceConnect, "nine")))); - - // Various settings, but wrong region + <load-balancer> + <private-access> + <allow-urn>foo</allow-urn> + <allow-urn>bar</allow-urn> + </private-access> + </load-balancer> + """); + + // Both load-balancer and nodes verifyAllowedUrns(""" - <endpoint type='zone' container-id='default' enabled='false' /> - <endpoint type='private' container-id='default'> - <region>eu</region> - <allow with='aws-private-link' arn='barn' /> - <allow with='gcp-service-connect' project='nine' /> - </endpoint> + <load-balancer> + <private-access> + <allow-urn>foo</allow-urn> + <allow-urn>bar</allow-urn> + </private-access> + </load-balancer> + <nodes count='2' /> """, - Environment.prod, - "us", - ZoneEndpoint.defaultEndpoint); - - // Various settings, but wrong environment - verifyAllowedUrns(""" - <endpoint type='zone' container-id='default' enabled='false' /> - <endpoint type='private' container-id='default'> - <region>eu</region> - <allow with='aws-private-link' arn='barn' /> - <allow with='gcp-service-connect' project='nine' /> - </endpoint> - """, - Environment.dev, - "eu", - ZoneEndpoint.defaultEndpoint); + "foo", "bar"); } - private void verifyAllowedUrns(String endpointsTag, Environment environment, String region, ZoneEndpoint expected) throws IOException, SAXException { + private void verifyAllowedUrns(String containerXml, String... expectedAllowedUrns) throws IOException, SAXException { String servicesXml = """ <container id='default' version='1.0'> - <nodes count='2' /> + %s </container> - """; - String deploymentXml = """ - <deployment version='1.0'> - <prod> - <region>eu</region> - </prod> - <endpoints> - %s - </endpoints> - </deployment> - """.formatted(endpointsTag); - ApplicationPackage applicationPackage = new MockApplicationPackage.Builder().withServices(servicesXml).withDeploymentSpec(deploymentXml).build(); + """.formatted(containerXml); + ApplicationPackage applicationPackage = new MockApplicationPackage.Builder().withServices(servicesXml).build(); InMemoryProvisioner provisioner = new InMemoryProvisioner(true, false, "host1.yahoo.com", "host2.yahoo.com"); VespaModel model = new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder() .modelHostProvisioner(provisioner) .provisioned(provisioner.startProvisionedRecording()) .applicationPackage(applicationPackage) - .properties(new TestProperties().setMultitenant(true) - .setHostedVespa(true) - .setZone(new Zone(environment, RegionName.from(region)))) + .properties(new TestProperties().setMultitenant(true).setHostedVespa(true)) .build()); assertEquals(2, model.hostSystem().getHosts().size()); assertEquals(1, provisioner.provisionedClusters().size()); - assertEquals(expected, - provisioner.provisionedClusters().iterator().next().zoneEndpoint()); + assertEquals(List.of(expectedAllowedUrns), + provisioner.provisionedClusters().iterator().next().loadBalancerSettings().allowedUrns()); } @Test diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java index 9e8388b6442..213166447ca 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java @@ -20,7 +20,7 @@ public class ClusterMembership { private final String stringValue; private ClusterMembership(String stringValue, Version vespaVersion, Optional<DockerImage> dockerImageRepo, - ZoneEndpoint zoneEndpoint) { + LoadBalancerSettings loadBalancerSettings) { String[] components = stringValue.split("/"); if (components.length < 4) throw new RuntimeException("Could not parse '" + stringValue + "' to a cluster membership. " + @@ -49,7 +49,7 @@ public class ClusterMembership { .exclusive(exclusive) .combinedId(combinedId.map(ClusterSpec.Id::from)) .dockerImageRepository(dockerImageRepo) - .loadBalancerSettings(zoneEndpoint) + .loadBalancerSettings(loadBalancerSettings) .stateful(stateful) .build(); this.index = Integer.parseInt(components[3]); @@ -125,12 +125,12 @@ public class ClusterMembership { public String toString() { return stringValue(); } public static ClusterMembership from(String stringValue, Version vespaVersion, Optional<DockerImage> dockerImageRepo) { - return from(stringValue, vespaVersion, dockerImageRepo, ZoneEndpoint.defaultEndpoint); + return from(stringValue, vespaVersion, dockerImageRepo, LoadBalancerSettings.empty); } public static ClusterMembership from(String stringValue, Version vespaVersion, Optional<DockerImage> dockerImageRepo, - ZoneEndpoint zoneEndpoint) { - return new ClusterMembership(stringValue, vespaVersion, dockerImageRepo, zoneEndpoint); + LoadBalancerSettings loadBalancerSettings) { + return new ClusterMembership(stringValue, vespaVersion, dockerImageRepo, loadBalancerSettings); } public static ClusterMembership from(ClusterSpec cluster, int index) { diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java index 196255a8342..153b305dc01 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java @@ -24,12 +24,12 @@ public final class ClusterSpec { private final boolean exclusive; private final Optional<Id> combinedId; private final Optional<DockerImage> dockerImageRepo; - private final ZoneEndpoint zoneEndpoint; + private final LoadBalancerSettings loadBalancerSettings; private final boolean stateful; private ClusterSpec(Type type, Id id, Optional<Group> groupId, Version vespaVersion, boolean exclusive, Optional<Id> combinedId, Optional<DockerImage> dockerImageRepo, - ZoneEndpoint zoneEndpoint, boolean stateful) { + LoadBalancerSettings loadBalancerSettings, boolean stateful) { this.type = type; this.id = id; this.groupId = groupId; @@ -47,7 +47,7 @@ public final class ClusterSpec { if (type.isContent() && !stateful) { throw new IllegalArgumentException("Cluster of type " + type + " must be stateful"); } - this.zoneEndpoint = Objects.requireNonNull(zoneEndpoint); + this.loadBalancerSettings = Objects.requireNonNull(loadBalancerSettings); this.stateful = stateful; } @@ -63,8 +63,8 @@ public final class ClusterSpec { /** Returns the docker image (repository + vespa version) we want this cluster to run */ public Optional<String> dockerImage() { return dockerImageRepo.map(repo -> repo.withTag(vespaVersion).asString()); } - /** Returns any additional zone endpoint settings for application container clusters. */ - public ZoneEndpoint zoneEndpoint() { return zoneEndpoint; } + /** Returns any additional load balancer settings for application container clusters. */ + public LoadBalancerSettings loadBalancerSettings() { return loadBalancerSettings; } /** Returns the version of Vespa that we want this cluster to run */ public Version vespaVersion() { return vespaVersion; } @@ -87,15 +87,15 @@ public final class ClusterSpec { public boolean isStateful() { return stateful; } public ClusterSpec with(Optional<Group> newGroup) { - return new ClusterSpec(type, id, newGroup, vespaVersion, exclusive, combinedId, dockerImageRepo, zoneEndpoint, stateful); + return new ClusterSpec(type, id, newGroup, vespaVersion, exclusive, combinedId, dockerImageRepo, loadBalancerSettings, stateful); } public ClusterSpec withExclusivity(boolean exclusive) { - return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, zoneEndpoint, stateful); + return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, loadBalancerSettings, stateful); } public ClusterSpec exclusive(boolean exclusive) { - return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, zoneEndpoint, stateful); + return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, loadBalancerSettings, stateful); } /** Creates a ClusterSpec when requesting a cluster */ @@ -119,7 +119,7 @@ public final class ClusterSpec { private Version vespaVersion; private boolean exclusive = false; private Optional<Id> combinedId = Optional.empty(); - private ZoneEndpoint zoneEndpoint = ZoneEndpoint.defaultEndpoint; + private LoadBalancerSettings loadBalancerSettings = LoadBalancerSettings.empty; private boolean stateful; private Builder(Type type, Id id, boolean specification) { @@ -135,7 +135,7 @@ public final class ClusterSpec { if (vespaVersion == null) throw new IllegalArgumentException("vespaVersion is required to be set when creating a ClusterSpec with specification()"); } else if (groupId.isPresent()) throw new IllegalArgumentException("groupId is not allowed to be set when creating a ClusterSpec with request()"); - return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, zoneEndpoint, stateful); + return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, loadBalancerSettings, stateful); } public Builder group(Group groupId) { @@ -168,8 +168,8 @@ public final class ClusterSpec { return this; } - public Builder loadBalancerSettings(ZoneEndpoint zoneEndpoint) { - this.zoneEndpoint = zoneEndpoint; + public Builder loadBalancerSettings(LoadBalancerSettings loadBalancerSettings) { + this.loadBalancerSettings = loadBalancerSettings; return this; } @@ -198,12 +198,12 @@ public final class ClusterSpec { vespaVersion.equals(that.vespaVersion) && combinedId.equals(that.combinedId) && dockerImageRepo.equals(that.dockerImageRepo) && - zoneEndpoint.equals(that.zoneEndpoint); + loadBalancerSettings.equals(that.loadBalancerSettings); } @Override public int hashCode() { - return Objects.hash(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, zoneEndpoint, stateful); + return Objects.hash(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, loadBalancerSettings, stateful); } /** diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/LoadBalancerSettings.java b/config-provisioning/src/main/java/com/yahoo/config/provision/LoadBalancerSettings.java new file mode 100644 index 00000000000..723de25fa87 --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/LoadBalancerSettings.java @@ -0,0 +1,20 @@ +package com.yahoo.config.provision; + +import java.util.List; + +/** + * Settings for a load balancer provisioned for an application container cluster. + * + * @author jonmv + */ +public record LoadBalancerSettings(List<String> allowedUrns) { + + public static final LoadBalancerSettings empty = new LoadBalancerSettings(List.of()); + + public LoadBalancerSettings(List<String> allowedUrns) { + this.allowedUrns = List.copyOf(allowedUrns); + } + + public boolean isEmpty() { return allowedUrns.isEmpty(); } + +} diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneEndpoint.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneEndpoint.java deleted file mode 100644 index 10e22f8df06..00000000000 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneEndpoint.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.yahoo.config.provision; - -import ai.vespa.validation.Validation; - -import java.util.List; -import java.util.Objects; - -/** - * Settings for a zone endpoint of a deployment. - * - * TODO: Fix isEmpty - * Inline empty and constructor - * - * @author jonmv - */ -public class ZoneEndpoint { - - public static final ZoneEndpoint defaultEndpoint = new ZoneEndpoint(true, false, List.of()); - - private final boolean isPublicEndpoint; - private final boolean isPrivateEndpoint; - private final List<AllowedUrn> allowedUrns; - - public ZoneEndpoint(List<String> allowedUrns) { - this(true, true, allowedUrns.stream().map(arn -> new AllowedUrn(AccessType.awsPrivateLink, arn)).toList()); - } - - public ZoneEndpoint(boolean isPublicEndpoint, boolean isPrivateEndpoint, List<AllowedUrn> allowedUrns) { - if ( ! allowedUrns.isEmpty() && ! isPrivateEndpoint) - throw new IllegalArgumentException("cannot list allowed urns, without also enabling private visibility"); - this.isPublicEndpoint = isPublicEndpoint; - this.isPrivateEndpoint = isPrivateEndpoint; - this.allowedUrns = List.copyOf(allowedUrns); - } - - /** Whether this has an endpoint which is visible from the public internet. */ - public boolean isPublicEndpoint() { - return isPublicEndpoint; - } - - /** Whether this has an endpoint which is visible through private DNS of the cloud. */ - public boolean isPrivateEndpoint() { - return isPrivateEndpoint; - } - - /** List of allowed URNs, for specified private access types. */ - public List<AllowedUrn> allowedUrns() { - return allowedUrns; - } - - /** List of URNs for the given access type. */ - public List<String> allowedUrnsWith(AccessType type) { - return allowedUrns.stream().filter(urn -> urn.type == type).map(AllowedUrn::urn).toList(); - } - - public boolean isDefault() { - return equals(defaultEndpoint); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ZoneEndpoint that = (ZoneEndpoint) o; - return isPublicEndpoint == that.isPublicEndpoint && isPrivateEndpoint == that.isPrivateEndpoint && allowedUrns.equals(that.allowedUrns); - } - - @Override - public int hashCode() { - return Objects.hash(isPublicEndpoint, isPrivateEndpoint, allowedUrns); - } - - @Override - public String toString() { - return "ZoneEndpoint{" + - "isPublicEndpoint=" + isPublicEndpoint + - ", isPrivateEndpoint=" + isPrivateEndpoint + - ", allowedUrns=" + allowedUrns + - '}'; - } - - public enum AccessType { - awsPrivateLink, - gcpServiceConnect, - } - - /** A URN allowed to access this (private) endpoint, through a {@link AccessType} method. */ - public static class AllowedUrn { - - private final AccessType type; - private final String urn; - - public AllowedUrn(AccessType type, String urn) { - this.type = Objects.requireNonNull(type); - this.urn = Validation.requireNonBlank(urn, "URN"); - } - - /** Type of private connection. */ - public AccessType type() { - return type; - } - - /** URN allowed to access this private endpoint. */ - public String urn() { - return urn; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AllowedUrn that = (AllowedUrn) o; - return type == that.type && urn.equals(that.urn); - } - - @Override - public int hashCode() { - return Objects.hash(type, urn); - } - - @Override - public String toString() { - return "'" + urn + "' through '" + - switch (type) { - case awsPrivateLink -> "aws-private-link"; - case gcpServiceConnect -> "gcp-service-connect"; - } + "'"; - } - - } - -} diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java b/config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java index 64e8a7feb94..01bb0ca45ff 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java @@ -6,10 +6,8 @@ import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NodeResources; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; -import com.yahoo.config.provision.ZoneEndpoint.AccessType; import com.yahoo.slime.ArrayTraverser; import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; @@ -42,12 +40,8 @@ public class AllocatedHostsSerializer { private static final String hostSpecKey = "hostSpec"; private static final String hostSpecHostNameKey = "hostName"; private static final String hostSpecMembershipKey = "membership"; - private static final String loadBalancerSettingsKey = "zoneEndpoint"; - private static final String publicField = "public"; - private static final String privateField = "private"; - private static final String allowedUrnsField = "allowedUrns"; - private static final String accessTypeField = "type"; - private static final String urnField = "urn"; + private static final String loadBalancerSettingsKey = "loadBalancerSettings"; + private static final String allowedUrnsKey = "allowedUrns"; private static final String realResourcesKey = "realResources"; private static final String advertisedResourcesKey = "advertisedResources"; @@ -91,8 +85,9 @@ public class AllocatedHostsSerializer { host.membership().ifPresent(membership -> { object.setString(hostSpecMembershipKey, membership.stringValue()); object.setString(hostSpecVespaVersionKey, membership.cluster().vespaVersion().toFullString()); - if ( ! membership.cluster().zoneEndpoint().isDefault()) - toSlime(object.setObject(loadBalancerSettingsKey), membership.cluster().zoneEndpoint()); + if ( ! membership.cluster().loadBalancerSettings().isEmpty()) + membership.cluster().loadBalancerSettings().allowedUrns() + .forEach(object.setObject(loadBalancerSettingsKey).setArray(allowedUrnsKey)::addString); membership.cluster().dockerImageRepo().ifPresent(repo -> object.setString(hostSpecDockerImageRepoKey, repo.untagged())); }); toSlime(host.realResources(), object.setObject(realResourcesKey)); @@ -227,41 +222,13 @@ public class AllocatedHostsSerializer { object.field(hostSpecDockerImageRepoKey).valid() ? Optional.of(DockerImage.fromString(object.field(hostSpecDockerImageRepoKey).asString())) : Optional.empty(), - zoneEndpoint(object.field(loadBalancerSettingsKey))); + object.field(loadBalancerSettingsKey).valid() + ? new LoadBalancerSettings(SlimeUtils.entriesStream(object.field(loadBalancerSettingsKey).field(allowedUrnsKey)) + .map(Inspector::asString) + .toList()) + : LoadBalancerSettings.empty); } - private static void toSlime(Cursor settingsObject, ZoneEndpoint settings) { - settingsObject.setBool(publicField, settings.isPublicEndpoint()); - settingsObject.setBool(privateField, settings.isPrivateEndpoint()); - if (settings.isPrivateEndpoint()) { - Cursor allowedUrnsArray = settingsObject.setArray(allowedUrnsField); - for (AllowedUrn urn : settings.allowedUrns()) { - Cursor urnObject = allowedUrnsArray.addObject(); - urnObject.setString(urnField, urn.urn()); - urnObject.setString(accessTypeField, - switch (urn.type()) { - case awsPrivateLink -> "awsPrivateLink"; - case gcpServiceConnect -> "gcpServiceConnect"; - }); - } - } - } - - private static ZoneEndpoint zoneEndpoint(Inspector settingsObject) { - if ( ! settingsObject.valid()) return ZoneEndpoint.defaultEndpoint; - return new ZoneEndpoint(settingsObject.field(publicField).asBool(), - settingsObject.field(privateField).asBool(), - SlimeUtils.entriesStream(settingsObject.field(allowedUrnsField)) - .map(urnObject -> new AllowedUrn(switch (urnObject.field(accessTypeField).asString()) { - case "awsPrivateLink" -> AccessType.awsPrivateLink; - case "gcpServiceConnect" -> AccessType.gcpServiceConnect; - default -> throw new IllegalArgumentException("unknown service access type in '" + urnObject + "'"); - }, - urnObject.field(urnField).asString())) - .toList()); - } - - private static Optional<String> optionalString(Inspector inspector) { if ( ! inspector.valid()) return Optional.empty(); return Optional.of(inspector.asString()); diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializerTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializerTest.java index 3404d7ed55e..bcb3b8cd4aa 100644 --- a/config-provisioning/src/test/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializerTest.java +++ b/config-provisioning/src/test/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializerTest.java @@ -6,9 +6,9 @@ import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NetworkPorts; import com.yahoo.config.provision.NodeResources; -import com.yahoo.config.provision.ZoneEndpoint; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -20,6 +20,7 @@ import java.util.Set; import static com.yahoo.config.provision.serialization.AllocatedHostsSerializer.fromJson; import static com.yahoo.config.provision.serialization.AllocatedHostsSerializer.toJson; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * @author bratseth @@ -68,7 +69,7 @@ public class AllocatedHostsSerializerTest { bigSlowDiskSpeedNode, anyDiskSpeedNode, ClusterMembership.from("container/test/0/0", Version.fromString("6.73.1"), - Optional.empty(), new ZoneEndpoint(List.of("burn"))), + Optional.empty(), new LoadBalancerSettings(List.of("burn"))), Optional.empty(), Optional.empty(), Optional.empty())); diff --git a/configserver/src/test/apps/hosted/services.xml b/configserver/src/test/apps/hosted/services.xml index f1435d8cc4f..22bccee9f5a 100644 --- a/configserver/src/test/apps/hosted/services.xml +++ b/configserver/src/test/apps/hosted/services.xml @@ -15,6 +15,11 @@ </http> <search/> <nodes count='1'/> + <load-balancer> + <private-access> + <allow-urn>burn</allow-urn> + </private-access> + </load-balancer> </container> <content id="music" version="1.0"> 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 26330f11d65..a4e26fbe7b3 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 @@ -5,7 +5,6 @@ import ai.vespa.http.DomainName; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; import java.util.List; import java.util.Objects; @@ -85,6 +84,6 @@ public class LoadBalancer { unknown } - public record PrivateServiceInfo(String id, List<AllowedUrn> allowedUrns) { } + public record PrivateServiceInfo(String id, List<String> allowedUrns) { } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 2693fdcbd7c..4159b099387 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -946,7 +946,7 @@ public class ApplicationController { // Either the user is member of the domain admin role, or is given the "launch" privilege on the service. Optional<AthenzUser> athenzUser = getUser(deployer); if (athenzUser.isPresent()) { - // We only need to validate the root and instance in deployment.xml. Dev/perf entries are found at the instance level as well. + // We only need to validate the root and instance in deployment.xml. Not possible to add dev or perf tags to deployment.xml var zone = zoneId.orElseThrow(() -> new IllegalArgumentException("Unable to evaluate access, no zone provided in deployment")); var serviceToLaunch = instanceName .flatMap(instance -> applicationPackage.deploymentSpec().instance(instance)) @@ -954,7 +954,7 @@ public class ApplicationController { .or(() -> applicationPackage.deploymentSpec().athenzService()) .map(service -> new AthenzService(identityDomain.get(), service.value())); - if (serviceToLaunch.isPresent()) { + if(serviceToLaunch.isPresent()) { if ( ! ((AthenzFacade) accessControl).canLaunch(athenzUser.get(), serviceToLaunch.get()) && // launch privilege ! ((AthenzFacade) accessControl).hasTenantAdminAccess(athenzUser.get(), identityDomain.get()) // tenant admin diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java index 842d99abe71..bce9d44f8c6 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java @@ -4,28 +4,26 @@ package com.yahoo.vespa.hosted.controller.application.pkg; import com.yahoo.component.Version; import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.application.api.DeploymentSpec.DeclaredZone; import com.yahoo.config.application.api.Endpoint; import com.yahoo.config.application.api.Endpoint.Level; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.CloudName; -import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; import com.yahoo.config.provision.zone.ZoneApi; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.application.EndpointId; import com.yahoo.vespa.hosted.controller.versions.VespaVersion; import java.time.Instant; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -152,10 +150,10 @@ public class ApplicationPackageValidator { /** Verify endpoint configuration of given application package */ private void validateEndpointChange(Application application, ApplicationPackage applicationPackage, Instant instant) { - for (DeploymentInstanceSpec instance : applicationPackage.deploymentSpec().instances()) { - validateGlobalEndpointChanges(application, instance.name(), applicationPackage, instant); - validateZoneEndpointChanges(application, instance.name(), applicationPackage, instant); - } + applicationPackage.deploymentSpec().instances().forEach(instance -> validateEndpointChange(application, + instance.name(), + applicationPackage, + instant)); } /** Verify that compactable endpoint parts (instance name and endpoint ID) do not clash */ @@ -178,7 +176,7 @@ public class ApplicationPackageValidator { } /** Verify changes to endpoint configuration by comparing given application package to the existing one, if any */ - private void validateGlobalEndpointChanges(Application application, InstanceName instanceName, ApplicationPackage applicationPackage, Instant instant) { + private void validateEndpointChange(Application application, InstanceName instanceName, ApplicationPackage applicationPackage, Instant instant) { var validationId = ValidationId.globalEndpointChange; if (applicationPackage.validationOverrides().allows(validationId, instant)) return; @@ -202,43 +200,6 @@ public class ApplicationPackageValidator { ". " + ValidationOverrides.toAllowMessage(validationId)); } - /** Verify changes to endpoint configuration by comparing given application package to the existing one, if any */ - private void validateZoneEndpointChanges(Application application, InstanceName instance, ApplicationPackage applicationPackage, Instant now) { - ValidationId validationId = ValidationId.zoneEndpointChange; - if (applicationPackage.validationOverrides().allows(validationId, now)) return;; - - String prefix = validationId + ": application '" + application.id() + - (instance.isDefault() ? "" : "." + instance.value()) + "' "; - DeploymentInstanceSpec spec = applicationPackage.deploymentSpec().requireInstance(instance); - for (DeclaredZone zone : spec.zones()) { - if (zone.environment() == Environment.prod) { - Map<ClusterSpec.Id, ZoneEndpoint> newEndpoints = spec.zoneEndpoints(ZoneId.from(zone.environment(), zone.region().get())); - application.deploymentSpec().instance(instance) // If old spec has this instance ... - .filter(oldSpec -> oldSpec.concerns(zone.environment(), zone.region())) // ... and deploys to this zone ... - .map(oldSpec -> oldSpec.zoneEndpoints(ZoneId.from(zone.environment(), zone.region().get()))) - .ifPresent(oldEndpoints -> { // ... then we compare the endpoints present in both. - oldEndpoints.forEach((cluster, oldEndpoint) -> { - ZoneEndpoint newEndpoint = newEndpoints.getOrDefault(cluster, ZoneEndpoint.defaultEndpoint); - if ( ! newEndpoint.allowedUrns().containsAll(oldEndpoint.allowedUrns())) - throw new IllegalArgumentException(prefix + "allows access to cluster '" + cluster.value() + - "' in '" + zone.region().get().value() + "' to " + - oldEndpoint.allowedUrns().stream().map(AllowedUrn::toString).collect(joining(", ", "[", "]")) + - ", but does not include all these in the new deployment spec. " + - "Deploying with the new settings will allow access to " + - (newEndpoint.allowedUrns().isEmpty() ? "no one" : newEndpoint.allowedUrns().stream().map(AllowedUrn::toString).collect(joining(", ", "[", "]")))); - }); - newEndpoints.forEach((cluster, newEndpoint) -> { - ZoneEndpoint oldEndpoint = oldEndpoints.getOrDefault(cluster, ZoneEndpoint.defaultEndpoint); - if (oldEndpoint.isPublicEndpoint() && ! newEndpoint.isPublicEndpoint()) - throw new IllegalArgumentException(prefix + "has a public endpoint for cluster '" + cluster.value() + - "' in '" + zone.region().get().value() + "', but the new deployment spec " + - "disables this"); - }); - }); - } - } - } - /** Returns whether newEndpoints contains all destinations in endpoints */ private static boolean containsAllDestinationsOf(List<Endpoint> endpoints, List<Endpoint> newEndpoints) { var containsAllRegions = true; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java index 73c64be3e47..674d71001e3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java @@ -619,7 +619,7 @@ public class JobController { submission.applicationPackage().deploymentSpec().majorVersion().ifPresent(explicitMajor -> { if ( ! controller.readVersionStatus().isOnCurrentMajor(new Version(explicitMajor))) controller.notificationsDb().setNotification(NotificationSource.from(id), Type.submission, Notification.Level.warning, - "Vespa " + explicitMajor + " will soon reach end of life, upgrade to Vespa " + (explicitMajor + 1) + " now: " + + "Vespa " + explicitMajor + " will soon be end of life, upgrade to Vespa " + (explicitMajor + 1) + " now: " + "https://cloud.vespa.ai/en/vespa" + (explicitMajor + 1) + "-release-notes.html"); // ∠( ᐛ 」∠)_ }); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 3f68be611eb..c9ad34b5082 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -23,7 +23,6 @@ import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.TenantName; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.container.handler.metrics.JsonResponse; @@ -1979,15 +1978,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { lbObject.setString("cluster", lb.cluster().value()); lb.service().ifPresent(service -> { lbObject.setString("serviceId", service.id()); // Really the "serviceName", but this is what the user needs >_< - Cursor urnsArray = lbObject.setArray("allowedUrns"); - for (AllowedUrn urn : service.allowedUrns()) { - Cursor urnObject = urnsArray.addObject(); - urnObject.setString("type", switch (urn.type()) { - case awsPrivateLink -> "aws-private-link"; - case gcpServiceConnect -> "gcp-service-connect"; - }); - urnObject.setString("urn", urn.urn()); - } + service.allowedUrns().forEach(lbObject.setArray("allowedUrns")::addString); Cursor endpointsArray = lbObject.setArray("endpoints"); controller.serviceRegistry().vpcEndpointService() .getConnections(new ClusterId(id, lb.cluster()), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java index db96c265363..95b81dffaed 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java @@ -1118,94 +1118,6 @@ public class ControllerTest { } @Test - void testZoneEndpointChanges() { - DeploymentContext app = tester.newDeploymentContext(); - // Set up app with default settings. - app.submit(ApplicationPackageBuilder.fromDeploymentXml(""" - <deployment> - <prod> - <region>us-east-3</region> - </prod> - </deployment>""")); - - assertEquals("zone-endpoint-change: application 'tenant.application' has a public endpoint for cluster 'foo' in 'us-east-3', but the new deployment spec disables this", - assertThrows(IllegalArgumentException.class, - () -> app.submit(ApplicationPackageBuilder.fromDeploymentXml(""" - <deployment> - <prod> - <region>us-east-3</region> - </prod> - <endpoints> - <endpoint type='zone' container-id='foo' enabled='false' /> - </endpoints> - </deployment>"""))) - .getMessage()); - - // Disabling endpoints is OK with override. - app.submit(ApplicationPackageBuilder.fromDeploymentXml(""" - <deployment> - <prod> - <region>us-east-3</region> - </prod> - <endpoints> - <endpoint type='zone' container-id='foo' enabled='false' /> - </endpoints> - </deployment>""", - ValidationId.zoneEndpointChange)); - - // Enabling endpoints again is OK, as is adding a private endpoint with some URN. - app.submit(ApplicationPackageBuilder.fromDeploymentXml(""" - <deployment> - <prod> - <region>us-east-3</region> - </prod> - <endpoints> - <endpoint type='private' container-id='foo'> - <allow with='aws-private-link' arn='yarn' /> - </endpoint> - </endpoints> - </deployment>""", - ValidationId.zoneEndpointChange)); - - // Changing URNs is guarded. - assertEquals("zone-endpoint-change: application 'tenant.application' allows access to cluster 'foo' in 'us-east-3' to " + - "['yarn' through 'aws-private-link'], but does not include all these in the new deployment spec. " + - "Deploying with the new settings will allow access to ['yarn' through 'gcp-service-connect']", - assertThrows(IllegalArgumentException.class, - () -> app.submit(ApplicationPackageBuilder.fromDeploymentXml(""" - <deployment> - <prod> - <region>us-east-3</region> - </prod> - <endpoints> - <endpoint type='private' container-id='foo'> - <allow with='gcp-service-connect' project='yarn' /> - </endpoint> - </endpoints> - </deployment>"""))) - .getMessage()); - - // Changing cluster, effectively removing old URNs, is also guarded. - assertEquals("zone-endpoint-change: application 'tenant.application' allows access to cluster 'foo' in 'us-east-3' to " + - "['yarn' through 'aws-private-link'], but does not include all these in the new deployment spec. " + - "Deploying with the new settings will allow access to no one", - assertThrows(IllegalArgumentException.class, - () -> app.submit(ApplicationPackageBuilder.fromDeploymentXml(""" - <deployment> - <prod> - <region>us-east-3</region> - </prod> - <endpoints> - <endpoint type='private' container-id='bar'> - <allow with='aws-private-link' arn='yarn' /> - </endpoint> - </endpoints> - </deployment>"""))) - .getMessage()); - } - - - @Test void testReadableApplications() { var db = new MockCuratorDb(tester.controller().system()); var tester = new DeploymentTester(new ControllerTester(db)); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java index 6f859ff3d15..93b804805c3 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java @@ -16,8 +16,6 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; -import com.yahoo.config.provision.ZoneEndpoint.AccessType; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.flags.json.FlagData; import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics; @@ -419,7 +417,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer LoadBalancer.State.active, Optional.of("dns-zone-1"), Optional.empty(), - Optional.of(new PrivateServiceInfo("service", List.of(new AllowedUrn(AccessType.awsPrivateLink, "arne"))))))); + Optional.of(new PrivateServiceInfo("service", List.of("arne")))))); } Application application = applications.get(id); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index be405c7b876..d40485ff5c0 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -701,7 +701,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-central-1/private-service", GET) .userIdentity(USER_ID), """ - {"loadBalancers":[{"cluster":"default","serviceId":"service","allowedUrns":[{"type":"aws-private-link","urn":"arne"}],"endpoints":[{"endpointId":"endpoint-1","state":"available"}]}]}"""); + {"loadBalancers":[{"cluster":"default","serviceId":"service","allowedUrns":["arne"],"endpoints":[{"endpointId":"endpoint-1","state":"available"}]}]}"""); // GET service/state/v1 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-central-1/service/storagenode/host.com/state/v1/?foo=bar", GET) diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java index 2856a38075b..33c9edf694d 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.provision.lb; import ai.vespa.http.DomainName; import com.google.common.collect.ImmutableSortedSet; import com.yahoo.config.provision.CloudAccount; -import com.yahoo.config.provision.ZoneEndpoint; +import com.yahoo.config.provision.LoadBalancerSettings; import java.util.Objects; import java.util.Optional; @@ -24,12 +24,12 @@ public class LoadBalancerInstance { private final Set<Integer> ports; private final Set<String> networks; private final Set<Real> reals; - private final ZoneEndpoint settings; + private final LoadBalancerSettings settings; private final Optional<PrivateServiceId> serviceId; private final CloudAccount cloudAccount; public LoadBalancerInstance(Optional<DomainName> hostname, Optional<String> ipAddress, Optional<DnsZone> dnsZone, - Set<Integer> ports, Set<String> networks, Set<Real> reals, ZoneEndpoint settings, + Set<Integer> ports, Set<String> networks, Set<Real> reals, LoadBalancerSettings settings, Optional<PrivateServiceId> serviceId, CloudAccount cloudAccount) { this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null"); this.ipAddress = Objects.requireNonNull(ipAddress, "ip must be non-null"); @@ -78,7 +78,7 @@ public class LoadBalancerInstance { } /** Static user-configured settings of this load balancer */ - public ZoneEndpoint settings() { + public LoadBalancerSettings settings() { return settings; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java index c19aebcda6e..e0dd41f9008 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java @@ -4,8 +4,8 @@ package com.yahoo.vespa.hosted.provision.lb; import ai.vespa.http.DomainName; import com.google.common.collect.ImmutableSet; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NodeType; -import com.yahoo.config.provision.ZoneEndpoint; import java.util.Collections; import java.util.HashMap; @@ -62,7 +62,7 @@ public class LoadBalancerServiceMock implements LoadBalancerService { Collections.singleton(4443), ImmutableSet.of("10.2.3.0/24", "10.4.5.0/24"), spec.reals(), - spec.settings().orElse(ZoneEndpoint.defaultEndpoint), + spec.settings().orElse(LoadBalancerSettings.empty), spec.settings().map(__ -> PrivateServiceId.of("service")), spec.cloudAccount()); instances.put(id, instance); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerSpec.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerSpec.java index e0ef6739542..dca6d434330 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerSpec.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerSpec.java @@ -5,7 +5,7 @@ import com.google.common.collect.ImmutableSortedSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.ZoneEndpoint; +import com.yahoo.config.provision.LoadBalancerSettings; import java.util.Objects; import java.util.Optional; @@ -21,11 +21,11 @@ public class LoadBalancerSpec { private final ApplicationId application; private final ClusterSpec.Id cluster; private final Set<Real> reals; - private final Optional<ZoneEndpoint> settings; + private final Optional<LoadBalancerSettings> settings; private final CloudAccount cloudAccount; public LoadBalancerSpec(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, - ZoneEndpoint settings, CloudAccount cloudAccount) { + LoadBalancerSettings settings, CloudAccount cloudAccount) { this.application = Objects.requireNonNull(application); this.cluster = Objects.requireNonNull(cluster); this.reals = ImmutableSortedSet.copyOf(Objects.requireNonNull(reals)); @@ -49,7 +49,7 @@ public class LoadBalancerSpec { } /** Static user-configured settings for this load balancer. */ - public Optional<ZoneEndpoint> settings() { + public Optional<LoadBalancerSettings> settings() { return settings; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java index 5dc099460a4..c8fb1226b81 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java @@ -3,8 +3,8 @@ package com.yahoo.vespa.hosted.provision.lb; import ai.vespa.http.DomainName; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NodeType; -import com.yahoo.config.provision.ZoneEndpoint; import java.util.Objects; import java.util.Optional; @@ -29,15 +29,15 @@ public class SharedLoadBalancerService implements LoadBalancerService { @Override public LoadBalancerInstance create(LoadBalancerSpec spec, boolean force) { - if (spec.settings().isPresent() && ! spec.settings().get().isDefault()) - throw new IllegalArgumentException("custom zone endpoint settings are not supported with " + getClass()); + if (spec.settings().isPresent() && ! spec.settings().get().isEmpty()) + throw new IllegalArgumentException("custom load balancer settings are not supported with " + getClass()); return new LoadBalancerInstance(Optional.of(DomainName.of(vipHostname)), Optional.empty(), Optional.empty(), Set.of(4443), Set.of(), spec.reals(), - spec.settings().orElse(ZoneEndpoint.defaultEndpoint), + spec.settings().orElse(LoadBalancerSettings.empty), Optional.empty(), spec.cloudAccount()); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java index 6bac1dab3dd..3d352f5596b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java @@ -3,9 +3,7 @@ package com.yahoo.vespa.hosted.provision.persistence; import ai.vespa.http.DomainName; import com.yahoo.config.provision.CloudAccount; -import com.yahoo.config.provision.ZoneEndpoint; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; -import com.yahoo.config.provision.ZoneEndpoint.AccessType; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.slime.ArrayTraverser; import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; @@ -54,11 +52,7 @@ public class LoadBalancerSerializer { private static final String serviceIdField = "serviceId"; private static final String cloudAccountField = "cloudAccount"; private static final String settingsField = "settings"; - private static final String publicField = "public"; - private static final String privateField = "private"; private static final String allowedUrnsField = "allowedUrns"; - private static final String accessTypeField = "type"; - private static final String urnField = "urn"; public static byte[] toJson(LoadBalancer loadBalancer) { Slime slime = new Slime(); @@ -83,13 +77,15 @@ public class LoadBalancerSerializer { })); loadBalancer.instance() .map(LoadBalancerInstance::settings) - .ifPresent(settings -> toSlime(root.setObject(settingsField), settings)); + .filter(settings -> ! settings.isEmpty()) + .ifPresent(settings -> settings.allowedUrns().forEach(root.setObject(settingsField) + .setArray(allowedUrnsField)::addString)); loadBalancer.instance() .flatMap(LoadBalancerInstance::serviceId) .ifPresent(serviceId -> root.setString(serviceIdField, serviceId.value())); loadBalancer.instance() .map(LoadBalancerInstance::cloudAccount) - .filter(cloudAccount -> ! cloudAccount.isUnspecified()) + .filter(cloudAccount -> !cloudAccount.isUnspecified()) .ifPresent(cloudAccount -> root.setString(cloudAccountField, cloudAccount.value())); try { return SlimeUtils.toJsonBytes(slime); @@ -118,7 +114,7 @@ public class LoadBalancerSerializer { Optional<DomainName> hostname = optionalString(object.field(hostnameField), Function.identity()).filter(s -> !s.isEmpty()).map(DomainName::of); Optional<String> ipAddress = optionalString(object.field(lbIpAddressField), Function.identity()).filter(s -> !s.isEmpty()); Optional<DnsZone> dnsZone = optionalString(object.field(dnsZoneField), DnsZone::new); - ZoneEndpoint settings = zoneEndpoint(object.field(settingsField)); + LoadBalancerSettings settings = loadBalancerSettings(object.field(settingsField)); Optional<PrivateServiceId> serviceId = optionalString(object.field(serviceIdField), PrivateServiceId::of); CloudAccount cloudAccount = optionalString(object.field(cloudAccountField), CloudAccount::from).orElse(CloudAccount.empty); Optional<LoadBalancerInstance> instance = hostname.isEmpty() && ipAddress.isEmpty() ? Optional.empty() : @@ -130,35 +126,11 @@ public class LoadBalancerSerializer { Instant.ofEpochMilli(object.field(changedAtField).asLong())); } - private static void toSlime(Cursor settingsObject, ZoneEndpoint settings) { - settingsObject.setBool(publicField, settings.isPublicEndpoint()); - settingsObject.setBool(privateField, settings.isPrivateEndpoint()); - if (settings.isPrivateEndpoint()) { - Cursor allowedUrnsArray = settingsObject.setArray(allowedUrnsField); - for (AllowedUrn urn : settings.allowedUrns()) { - Cursor urnObject = allowedUrnsArray.addObject(); - urnObject.setString(urnField, urn.urn()); - urnObject.setString(accessTypeField, - switch (urn.type()) { - case awsPrivateLink -> "awsPrivateLink"; - case gcpServiceConnect -> "gcpServiceConnect"; - }); - } - } - } - - private static ZoneEndpoint zoneEndpoint(Inspector settingsObject) { - if ( ! settingsObject.valid()) return ZoneEndpoint.defaultEndpoint; - return new ZoneEndpoint(settingsObject.field(publicField).asBool(), - settingsObject.field(privateField).asBool(), - SlimeUtils.entriesStream(settingsObject.field(allowedUrnsField)) - .map(urnObject -> new AllowedUrn(switch (urnObject.field(accessTypeField).asString()) { - case "awsPrivateLink" -> AccessType.awsPrivateLink; - case "gcpServiceConnect" -> AccessType.gcpServiceConnect; - default -> throw new IllegalArgumentException("unknown service access type in '" + urnObject + "'"); - }, - urnObject.field(urnField).asString())) - .toList()); + private static LoadBalancerSettings loadBalancerSettings(Inspector settingsObject) { + if ( ! settingsObject.valid()) return LoadBalancerSettings.empty; + return new LoadBalancerSettings(SlimeUtils.entriesStream(settingsObject.field(allowedUrnsField)) + .map(Inspector::asString) + .toList()); } private static <T> Optional<T> optionalValue(Inspector field, Function<Inspector, T> fieldMapper) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java index 92fdb1d2e52..3e8124d5309 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java @@ -7,9 +7,9 @@ import com.yahoo.config.provision.ApplicationTransaction; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.TenantName; -import com.yahoo.config.provision.ZoneEndpoint; import com.yahoo.config.provision.exception.LoadBalancerServiceException; import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.flags.BooleanFlag; @@ -108,13 +108,11 @@ public class LoadBalancerProvisioner { * Calling this when no load balancer has been prepared for given cluster is a no-op. */ public void activate(Set<ClusterSpec> clusters, NodeList newActive, ApplicationTransaction transaction) { - Map<ClusterSpec.Id, ZoneEndpoint> activatingClusters = clusters.stream() - // .collect(Collectors.toMap(ClusterSpec::id, ClusterSpec::zoneEndpoint)); - // TODO: this dies with combined clusters Ü - .collect(groupingBy(LoadBalancerProvisioner::effectiveId, - reducing(ZoneEndpoint.defaultEndpoint, - ClusterSpec::zoneEndpoint, - (o, n) -> o.isDefault() ? n : o))); + Map<ClusterSpec.Id, LoadBalancerSettings> activatingClusters = clusters.stream() + .collect(groupingBy(LoadBalancerProvisioner::effectiveId, + reducing(LoadBalancerSettings.empty, + ClusterSpec::loadBalancerSettings, + (o, n) -> o.isEmpty() ? n : o))); for (var cluster : loadBalancedClustersOf(newActive).entrySet()) { if ( ! activatingClusters.containsKey(cluster.getKey())) continue; @@ -211,7 +209,7 @@ public class LoadBalancerProvisioner { requireInstance(id, instance, cloudAccount); } - private void activate(ApplicationTransaction transaction, ClusterSpec.Id cluster, ZoneEndpoint settings, NodeList nodes) { + private void activate(ApplicationTransaction transaction, ClusterSpec.Id cluster, LoadBalancerSettings settings, NodeList nodes) { Instant now = nodeRepository.clock().instant(); LoadBalancerId id = new LoadBalancerId(transaction.application(), cluster); Optional<LoadBalancer> loadBalancer = db.readLoadBalancer(id); @@ -228,7 +226,7 @@ public class LoadBalancerProvisioner { /** Provision or reconfigure a load balancer instance, if necessary */ private Optional<LoadBalancerInstance> provisionInstance(LoadBalancerId id, NodeList nodes, Optional<LoadBalancer> currentLoadBalancer, - ZoneEndpoint zoneEndpoint, + LoadBalancerSettings loadBalancerSettings, CloudAccount cloudAccount) { boolean shouldDeactivateRouting = deactivateRouting.with(FetchVector.Dimension.APPLICATION_ID, id.application().serializedForm()) @@ -239,13 +237,13 @@ public class LoadBalancerProvisioner { } else { reals = realsOf(nodes); } - if (isUpToDate(currentLoadBalancer, reals, zoneEndpoint)) + if (isUpToDate(currentLoadBalancer, reals, loadBalancerSettings)) return currentLoadBalancer.get().instance(); log.log(Level.INFO, () -> "Provisioning instance for " + id + ", targeting: " + reals); try { // Override settings at activation, otherwise keep existing ones. - ZoneEndpoint settings = zoneEndpoint != null ? zoneEndpoint - : currentLoadBalancer.flatMap(LoadBalancer::instance) + LoadBalancerSettings settings = loadBalancerSettings != null ? loadBalancerSettings + : currentLoadBalancer.flatMap(LoadBalancer::instance) .map(LoadBalancerInstance::settings) .orElse(null); LoadBalancerInstance created = service.create(new LoadBalancerSpec(id.application(), id.cluster(), reals, settings, cloudAccount), @@ -308,11 +306,11 @@ public class LoadBalancerProvisioner { } /** Returns whether load balancer has given reals, and settings if specified*/ - private static boolean isUpToDate(Optional<LoadBalancer> loadBalancer, Set<Real> reals, ZoneEndpoint zoneEndpoint) { + private static boolean isUpToDate(Optional<LoadBalancer> loadBalancer, Set<Real> reals, LoadBalancerSettings loadBalancerSettings) { if (loadBalancer.isEmpty()) return false; if (loadBalancer.get().instance().isEmpty()) return false; return loadBalancer.get().instance().get().reals().equals(reals) - && (zoneEndpoint == null || loadBalancer.get().instance().get().settings().equals(zoneEndpoint)); + && (loadBalancerSettings == null || loadBalancer.get().instance().get().settings().equals(loadBalancerSettings)); } /** Returns whether to allow given load balancer to have no reals */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java index 15a799c06d8..fdf69b60690 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.provision.restapi; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ZoneEndpoint.AllowedUrn; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.restapi.SlimeJsonResponse; import com.yahoo.slime.Cursor; @@ -77,17 +76,8 @@ public class LoadBalancersResponse extends SlimeJsonResponse { }); }); lb.instance().ifPresent(instance -> { - if ( ! instance.settings().isDefault()) { - Cursor urnsArray = lbObject.setObject("settings").setArray("allowedUrns"); - for (AllowedUrn urn : instance.settings().allowedUrns()) { - Cursor urnObject = urnsArray.addObject(); - urnObject.setString("type", switch (urn.type()) { - case awsPrivateLink -> "aws-private-link"; - case gcpServiceConnect -> "gcp-service-connect"; - }); - urnObject.setString("urn", urn.urn()); - } - } + if ( ! instance.settings().isEmpty()) + instance.settings().allowedUrns().forEach(lbObject.setObject("settings").setArray("allowedUrns")::addString); instance.serviceId().ifPresent(serviceId -> lbObject.setString("serviceId", serviceId.value())); }); lb.instance() diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java index 92ffe9828c3..91c8f803429 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java @@ -14,12 +14,12 @@ import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; -import com.yahoo.config.provision.ZoneEndpoint; import com.yahoo.transaction.Mutex; import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.curator.mock.MockCurator; @@ -189,7 +189,7 @@ public class MockNodeRepository extends NodeRepository { activate(provisioner.prepare(zoneApp, zoneCluster, Capacity.fromRequiredNodeType(NodeType.host), null), zoneApp, provisioner); ApplicationId app1Id = ApplicationId.from(TenantName.from("tenant1"), ApplicationName.from("application1"), InstanceName.from("instance1")); - ClusterSpec cluster1Id = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("id1")).vespaVersion("6.42").loadBalancerSettings(new ZoneEndpoint(List.of("arne"))).build(); + ClusterSpec cluster1Id = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("id1")).vespaVersion("6.42").loadBalancerSettings(new LoadBalancerSettings(List.of("arne"))).build(); activate(provisioner.prepare(app1Id, cluster1Id, Capacity.from(new ClusterResources(2, 1, new NodeResources(2, 8, 50, 1)), diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java index a646d26ea29..92c7ba7fe27 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java @@ -5,7 +5,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.ZoneEndpoint; +import com.yahoo.config.provision.LoadBalancerSettings; import org.junit.Test; import java.util.Optional; @@ -29,7 +29,7 @@ public class SharedLoadBalancerServiceTest { @Test public void test_create_lb() { var lb = loadBalancerService.create(new LoadBalancerSpec(applicationId, clusterId, reals, - ZoneEndpoint.defaultEndpoint, CloudAccount.empty), + LoadBalancerSettings.empty, CloudAccount.empty), false); assertEquals(Optional.of(HostName.of("vip.example.com")), lb.hostname()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java index dee895b02d2..d5722a59f3e 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java @@ -6,7 +6,7 @@ import com.google.common.collect.ImmutableSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.ZoneEndpoint; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.vespa.hosted.provision.lb.DnsZone; import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerId; @@ -46,7 +46,7 @@ public class LoadBalancerSerializerTest { new Real(DomainName.of("real-2"), "127.0.0.2", 4080)), - new ZoneEndpoint(List.of("123")), + new LoadBalancerSettings(List.of("123")), Optional.of(PrivateServiceId.of("foo")), CloudAccount.from("012345678912"))), LoadBalancer.State.active, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java index 3653e20d848..e32643860f5 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java @@ -9,9 +9,9 @@ import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.LoadBalancerSettings; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; -import com.yahoo.config.provision.ZoneEndpoint; import com.yahoo.config.provision.exception.LoadBalancerServiceException; import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.flags.InMemoryFlagSource; @@ -215,7 +215,7 @@ public class LoadBalancerProvisionerTest { public void provision_load_balancer_combined_cluster() { Supplier<List<LoadBalancer>> lbs = () -> tester.nodeRepository().loadBalancers().list(app1).asList(); var combinedId = ClusterSpec.Id.from("container1"); - var nodes = prepare(app1, clusterRequest(ClusterSpec.Type.combined, ClusterSpec.Id.from("content1"), Optional.of(combinedId), ZoneEndpoint.defaultEndpoint)); + var nodes = prepare(app1, clusterRequest(ClusterSpec.Type.combined, ClusterSpec.Id.from("content1"), Optional.of(combinedId), LoadBalancerSettings.empty)); assertEquals(1, lbs.get().size()); assertEquals("Prepare provisions load balancer with reserved nodes", 2, lbs.get().get(0).instance().get().reals().size()); tester.activate(app1, nodes); @@ -320,10 +320,10 @@ public class LoadBalancerProvisionerTest { tester.activate(app1, prepare(app1, capacity, clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("c1")))); LoadBalancerList loadBalancers = tester.nodeRepository().loadBalancers().list(); assertEquals(1, loadBalancers.size()); - assertEquals(ZoneEndpoint.defaultEndpoint, loadBalancers.first().get().instance().get().settings()); + assertEquals(LoadBalancerSettings.empty, loadBalancers.first().get().instance().get().settings()); // Next deployment contains new settings - ZoneEndpoint settings = new ZoneEndpoint(List.of("alice", "bob")); + LoadBalancerSettings settings = new LoadBalancerSettings(List.of("alice", "bob")); tester.activate(app1, prepare(app1, capacity, clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("c1"), Optional.empty(), settings))); loadBalancers = tester.nodeRepository().loadBalancers().list(); assertEquals(1, loadBalancers.size()); @@ -430,10 +430,10 @@ public class LoadBalancerProvisionerTest { } private static ClusterSpec clusterRequest(ClusterSpec.Type type, ClusterSpec.Id id) { - return clusterRequest(type, id, Optional.empty(), ZoneEndpoint.defaultEndpoint); + return clusterRequest(type, id, Optional.empty(), LoadBalancerSettings.empty); } - private static ClusterSpec clusterRequest(ClusterSpec.Type type, ClusterSpec.Id id, Optional<ClusterSpec.Id> combinedId, ZoneEndpoint settings) { + private static ClusterSpec clusterRequest(ClusterSpec.Type type, ClusterSpec.Id id, Optional<ClusterSpec.Id> combinedId, LoadBalancerSettings settings) { return ClusterSpec.request(type, id).vespaVersion("6.42").combinedId(combinedId).loadBalancerSettings(settings).build(); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java index 086df4d0c33..5143aa91f56 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java @@ -49,8 +49,8 @@ public class VirtualNodeProvisioningTest { private static final NodeResources resources1 = new NodeResources(4, 8, 100, 1); private static final NodeResources resources2 = new NodeResources(1, 4, 100, 1, NodeResources.DiskSpeed.fast, NodeResources.StorageType.local); - private static final ClusterSpec contentClusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("my-content")).vespaVersion("6.42").build(); - private static final ClusterSpec containerClusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("my-container")).vespaVersion("6.42").build(); + private static final ClusterSpec contentClusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent")).vespaVersion("6.42").build(); + private static final ClusterSpec containerClusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer")).vespaVersion("6.42").build(); private final ApplicationId applicationId = ProvisioningTester.applicationId("test"); @@ -242,7 +242,7 @@ public class VirtualNodeProvisioningTest { Version wantedVespaVersion = Version.fromString("6.39"); int nodeCount = 7; List<HostSpec> hosts = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("my-content")).vespaVersion(wantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent")).vespaVersion(wantedVespaVersion).build(), nodeCount, 1, resources2); tester.activate(application1, new HashSet<>(hosts)); @@ -253,7 +253,7 @@ public class VirtualNodeProvisioningTest { // Upgrade Vespa version on nodes Version upgradedWantedVespaVersion = Version.fromString("6.40"); List<HostSpec> upgradedHosts = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("my-content")).vespaVersion(upgradedWantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent")).vespaVersion(upgradedWantedVespaVersion).build(), nodeCount, 1, resources2); tester.activate(application1, new HashSet<>(upgradedHosts)); NodeList upgradedNodes = tester.getNodes(application1, Node.State.active); @@ -275,7 +275,7 @@ public class VirtualNodeProvisioningTest { int nodeCount = 7; try { List<HostSpec> nodes = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("my-content")).vespaVersion(wantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent")).vespaVersion(wantedVespaVersion).build(), nodeCount, 1, resources2); fail("Expected the allocation to fail due to parent hosts not being active yet"); } catch (NodeAllocationException expected) { } @@ -285,7 +285,7 @@ public class VirtualNodeProvisioningTest { // Try allocating tenants again List<HostSpec> nodes = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("my-content")).vespaVersion(wantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent")).vespaVersion(wantedVespaVersion).build(), nodeCount, 1, resources2); tester.activate(application1, new HashSet<>(nodes)); @@ -309,14 +309,14 @@ public class VirtualNodeProvisioningTest { Version wantedVespaVersion = Version.fromString("6.39"); List<HostSpec> nodes = tester.prepare(application2_1, - ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("my-content")).vespaVersion(wantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContent")).vespaVersion(wantedVespaVersion).build(), 6, 1, resources); assertHostSpecParentReservation(nodes, Optional.empty(), tester); // We do not get nodes on hosts reserved to tenant1 tester.activate(application2_1, nodes); try { tester.prepare(application2_2, - ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("my-content")).vespaVersion(wantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContent")).vespaVersion(wantedVespaVersion).build(), 5, 1, resources); fail("Expected exception"); } @@ -325,7 +325,7 @@ public class VirtualNodeProvisioningTest { } nodes = tester.prepare(application1_1, - ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("my-content")).vespaVersion(wantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContent")).vespaVersion(wantedVespaVersion).build(), 10, 1, resources); assertHostSpecParentReservation(nodes, Optional.of(tenant1), tester); tester.activate(application1_1, nodes); @@ -346,14 +346,14 @@ public class VirtualNodeProvisioningTest { try { // No capacity for 'container' nodes tester.prepare(applicationId, - ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("my-content")).vespaVersion(wantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContent")).vespaVersion(wantedVespaVersion).build(), 6, 1, resources); fail("Expected to fail node allocation"); } catch (NodeAllocationException ignored) { } // Same cluster, but content type is now 'content' List<HostSpec> nodes = tester.prepare(applicationId, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("my-content")).vespaVersion(wantedVespaVersion).build(), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent")).vespaVersion(wantedVespaVersion).build(), 6, 1, resources); tester.activate(applicationId, nodes); @@ -445,7 +445,7 @@ public class VirtualNodeProvisioningTest { assertEquals("No room for 3 nodes as 2 of 4 hosts are exclusive", "Could not satisfy request for 3 nodes with " + "[vcpu: 1.0, memory: 4.0 Gb, disk 100.0 Gb, bandwidth: 1.0 Gbps, architecture: x86_64] " + - "in tenant2.app2 container cluster 'my-container' 6.39: " + + "in tenant2.app2 container cluster 'myContainer' 6.39: " + "Node allocation failure on group 0: " + "Not enough suitable nodes available due to host exclusivity constraints", e.getMessage()); @@ -465,14 +465,14 @@ public class VirtualNodeProvisioningTest { tester.makeReadyChildren(1, resources2, "host2"); tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("my-content")).vespaVersion("6.42").build(), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent")).vespaVersion("6.42").build(), 2, 1, resources2.with(NodeResources.StorageType.remote)); } catch (NodeAllocationException e) { assertEquals("Could not satisfy request for 2 nodes with " + "[vcpu: 1.0, memory: 4.0 Gb, disk 100.0 Gb, bandwidth: 1.0 Gbps, storage type: remote, architecture: x86_64] " + - "in tenant.app1 content cluster 'my-content'" + + "in tenant.app1 content cluster 'myContent'" + " 6.42: Node allocation failure on group 0", e.getMessage()); } @@ -672,7 +672,7 @@ public class VirtualNodeProvisioningTest { private void prepareAndActivate(ApplicationId application, int nodeCount, boolean exclusive, NodeResources resources, ProvisioningTester tester) { Set<HostSpec> hosts = new HashSet<>(tester.prepare(application, - ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("my-container")).vespaVersion("6.39").exclusive(exclusive).build(), + ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer")).vespaVersion("6.39").exclusive(exclusive).build(), Capacity.from(new ClusterResources(nodeCount, 1, resources), false, true))); tester.activate(application, hosts); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/load-balancers.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/load-balancers.json index becca98a913..bbccc72c7f9 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/load-balancers.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/load-balancers.json @@ -30,12 +30,7 @@ } ], "settings": { - "allowedUrns": [ - { - "type": "aws-private-link", - "urn": "arne" - } - ] + "allowedUrns": [ "arne" ] }, "serviceId": "service" }, diff --git a/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/ApacheClusterTest.java b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/ApacheClusterTest.java index 30ed8dcfdd4..f45f7f9d246 100644 --- a/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/ApacheClusterTest.java +++ b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/ApacheClusterTest.java @@ -48,9 +48,9 @@ class ApacheClusterTest { Map.of("name1", () -> "value1", "name2", () -> "value2"), "content".getBytes(UTF_8), - Duration.ofSeconds(10)), + Duration.ofSeconds(5)), vessel); - HttpResponse response = vessel.get(15, TimeUnit.SECONDS); + HttpResponse response = vessel.get(5, TimeUnit.SECONDS); assertEquals("{}", new String(response.body(), UTF_8)); assertEquals(200, response.code()); |