diff options
Diffstat (limited to 'node-repository')
33 files changed, 175 insertions, 79 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java index 8646121bd4b..f00414aa654 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java @@ -1,6 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.applications; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterInfo; import com.yahoo.config.provision.IntRange; import com.yahoo.config.provision.Capacity; @@ -33,6 +34,7 @@ public class Cluster { private final ClusterResources min, max; private final IntRange groupSize; private final boolean required; + private final Optional<CloudAccount> cloudAccount; private final List<Autoscaling> suggestions; private final Autoscaling target; private final ClusterInfo clusterInfo; @@ -47,6 +49,7 @@ public class Cluster { ClusterResources maxResources, IntRange groupSize, boolean required, + Optional<CloudAccount> cloudAccount, List<Autoscaling> suggestions, Autoscaling target, ClusterInfo clusterInfo, @@ -58,6 +61,7 @@ public class Cluster { this.max = Objects.requireNonNull(maxResources); this.groupSize = Objects.requireNonNull(groupSize); this.required = required; + this.cloudAccount = Objects.requireNonNull(cloudAccount); this.suggestions = Objects.requireNonNull(suggestions); Objects.requireNonNull(target); if (target.resources().isPresent() && ! target.resources().get().isWithin(minResources, maxResources)) @@ -89,6 +93,9 @@ public class Cluster { */ public boolean required() { return required; } + /** Returns the enclave cloud account of this cluster, or empty if not enclave. */ + public Optional<CloudAccount> cloudAccount() { return cloudAccount; } + /** * Returns the computed resources (between min and max, inclusive) this cluster should * have allocated at the moment (whether or not it actually has it), @@ -134,19 +141,19 @@ public class Cluster { public Cluster withConfiguration(boolean exclusive, Capacity capacity) { return new Cluster(id, exclusive, capacity.minResources(), capacity.maxResources(), capacity.groupSize(), capacity.isRequired(), - suggestions, target, capacity.clusterInfo(), bcpGroupInfo, scalingEvents); + capacity.cloudAccount(), suggestions, target, capacity.clusterInfo(), bcpGroupInfo, scalingEvents); } public Cluster withSuggestions(List<Autoscaling> suggestions) { - return new Cluster(id, exclusive, min, max, groupSize, required, suggestions, target, clusterInfo, bcpGroupInfo, scalingEvents); + return new Cluster(id, exclusive, min, max, groupSize, required, cloudAccount, suggestions, target, clusterInfo, bcpGroupInfo, scalingEvents); } public Cluster withTarget(Autoscaling target) { - return new Cluster(id, exclusive, min, max, groupSize, required, suggestions, target, clusterInfo, bcpGroupInfo, scalingEvents); + return new Cluster(id, exclusive, min, max, groupSize, required, cloudAccount, suggestions, target, clusterInfo, bcpGroupInfo, scalingEvents); } public Cluster with(BcpGroupInfo bcpGroupInfo) { - return new Cluster(id, exclusive, min, max, groupSize, required, suggestions, target, clusterInfo, bcpGroupInfo, scalingEvents); + return new Cluster(id, exclusive, min, max, groupSize, required, cloudAccount, suggestions, target, clusterInfo, bcpGroupInfo, scalingEvents); } /** Add or update (based on "at" time) a scaling event */ @@ -160,7 +167,7 @@ public class Cluster { scalingEvents.add(scalingEvent); prune(scalingEvents); - return new Cluster(id, exclusive, min, max, groupSize, required, suggestions, target, clusterInfo, bcpGroupInfo, scalingEvents); + return new Cluster(id, exclusive, min, max, groupSize, required, cloudAccount, suggestions, target, clusterInfo, bcpGroupInfo, scalingEvents); } @Override @@ -192,7 +199,7 @@ public class Cluster { public static Cluster create(ClusterSpec.Id id, boolean exclusive, Capacity requested) { return new Cluster(id, exclusive, requested.minResources(), requested.maxResources(), requested.groupSize(), requested.isRequired(), - List.of(), Autoscaling.empty(), requested.clusterInfo(), BcpGroupInfo.empty(), List.of()); + requested.cloudAccount(), List.of(), Autoscaling.empty(), requested.clusterInfo(), BcpGroupInfo.empty(), List.of()); } /** The predicted time it will take to rescale this cluster. */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java index cb70eb977c4..75a00fa951e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.provision.autoscale; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; @@ -35,10 +36,12 @@ public class AllocatableResources { /** Fake allocatable resources from requested capacity */ public AllocatableResources(ClusterResources requested, ClusterSpec clusterSpec, - NodeRepository nodeRepository) { + NodeRepository nodeRepository, + CloudAccount cloudAccount) { this.nodes = requested.nodes(); this.groups = requested.groups(); - this.realResources = nodeRepository.resourcesCalculator().requestToReal(requested.nodeResources(), nodeRepository.exclusiveAllocation(clusterSpec), false); + this.realResources = nodeRepository.resourcesCalculator().requestToReal(requested.nodeResources(), cloudAccount, + nodeRepository.exclusiveAllocation(clusterSpec), false); this.advertisedResources = requested.nodeResources(); this.clusterSpec = clusterSpec; this.fulfilment = 1; @@ -180,17 +183,20 @@ public class AllocatableResources { // We decide resources: Add overhead to what we'll request (advertised) to make sure real becomes (at least) cappedNodeResources var allocatableResources = calculateAllocatableResources(wantedResources, nodeRepository, + model.cloudAccount(), clusterSpec, applicationLimits, exclusive, true); var worstCaseRealResources = nodeRepository.resourcesCalculator().requestToReal(allocatableResources.advertisedResources, + model.cloudAccount(), exclusive, false); if ( ! systemLimits.isWithinRealLimits(worstCaseRealResources, clusterSpec)) { allocatableResources = calculateAllocatableResources(wantedResources, nodeRepository, + model.cloudAccount(), clusterSpec, applicationLimits, exclusive, @@ -210,7 +216,7 @@ public class AllocatableResources { for (Flavor flavor : nodeRepository.flavors().getFlavors()) { // Flavor decide resources: Real resources are the worst case real resources we'll get if we ask for these advertised resources NodeResources advertisedResources = nodeRepository.resourcesCalculator().advertisedResourcesOf(flavor); - NodeResources realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive, false); + NodeResources realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, model.cloudAccount(), exclusive, false); // Adjust where we don't need exact match to the flavor if (flavor.resources().storageType() == NodeResources.StorageType.remote) { @@ -251,20 +257,21 @@ public class AllocatableResources { private static AllocatableResources calculateAllocatableResources(ClusterResources wantedResources, NodeRepository nodeRepository, + CloudAccount cloudAccount, ClusterSpec clusterSpec, Limits applicationLimits, boolean exclusive, boolean bestCase) { var systemLimits = nodeRepository.nodeResourceLimits(); - var advertisedResources = nodeRepository.resourcesCalculator().realToRequest(wantedResources.nodeResources(), exclusive, bestCase); + var advertisedResources = nodeRepository.resourcesCalculator().realToRequest(wantedResources.nodeResources(), cloudAccount, exclusive, bestCase); advertisedResources = systemLimits.enlargeToLegal(advertisedResources, clusterSpec, exclusive, true); // Ask for something legal advertisedResources = applicationLimits.cap(advertisedResources); // Overrides other conditions, even if it will then fail - var realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive, bestCase); // What we'll really get + var realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, cloudAccount, exclusive, bestCase); // What we'll really get if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec) && advertisedResources.storageType() == NodeResources.StorageType.any) { // Since local disk reserves some of the storage, try to constrain to remote disk advertisedResources = advertisedResources.with(NodeResources.StorageType.remote); - realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive, bestCase); + realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, cloudAccount, exclusive, bestCase); } return new AllocatableResources(wantedResources.with(realResources), advertisedResources, diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java index 45ef2d1d7b5..61d4ced1367 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java @@ -10,7 +10,6 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import static com.yahoo.vespa.hosted.provision.autoscale.Autoscaler.headroomRequiredToScaleDown; @@ -38,9 +37,7 @@ public class AllocationOptimizer { * @return the best allocation, if there are any possible legal allocations, fulfilling the target * fully or partially, within the limits */ - public Optional<AllocatableResources> findBestAllocation(Load loadAdjustment, - ClusterModel model, - Limits limits) { + public Optional<AllocatableResources> findBestAllocation(Load loadAdjustment, ClusterModel model, Limits limits) { return findBestAllocations(loadAdjustment, model, limits).stream().findFirst(); } @@ -51,9 +48,7 @@ public class AllocationOptimizer { * @return the best allocations, if there are any possible legal allocations, fulfilling the target * fully or partially, within the limits. The list contains the three best allocations, sorted from most to least preferred. */ - public List<AllocatableResources> findBestAllocations(Load loadAdjustment, - ClusterModel model, - Limits limits) { + public List<AllocatableResources> findBestAllocations(Load loadAdjustment, ClusterModel model, Limits limits) { if (limits.isEmpty()) limits = Limits.of(new ClusterResources(minimumNodes, 1, NodeResources.unspecified()), new ClusterResources(maximumNodes, maximumNodes, NodeResources.unspecified()), diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java index 986ab830283..a0f9d6e260a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java @@ -1,6 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.autoscale; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; @@ -123,6 +124,7 @@ public class ClusterModel { public Application application() { return application; } public ClusterSpec clusterSpec() { return clusterSpec; } + public CloudAccount cloudAccount() { return cluster.cloudAccount().orElse(CloudAccount.empty); } public AllocatableResources current() { return current; } private ClusterNodesTimeseries nodeTimeseries() { return nodeTimeseries; } private ClusterTimeseries clusterTimeseries() { return clusterTimeseries; } @@ -438,6 +440,7 @@ public class ClusterModel { clusterSpec, application.id()); return nodeRepository.resourcesCalculator().requestToReal(initialResources, + cloudAccount(), nodeRepository.exclusiveAllocation(clusterSpec), false).memoryGb(); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MemoryMetricsDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MemoryMetricsDb.java index 940109bab8a..8b2dc44669f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MemoryMetricsDb.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MemoryMetricsDb.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.provision.autoscale; import com.yahoo.collections.Pair; +import com.yahoo.component.annotation.Inject; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; @@ -33,6 +34,11 @@ public class MemoryMetricsDb implements MetricsDb { /** Lock all access for now since we modify lists inside a map */ private final Object lock = new Object(); + @Inject + public MemoryMetricsDb() { + this(Clock.systemUTC()); + } + public MemoryMetricsDb(Clock clock) { this.clock = clock; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java index e9230d2c91a..057430b381f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java @@ -296,6 +296,11 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { } } + private static String getStr(Record record, int col) { + CharSequence charSequence = record.getStrA(col); + return charSequence != null ? charSequence.toString() : ""; + } + private ListMap<String, NodeMetricSnapshot> getNodeSnapshots(Instant startTime, Set<String> hostnames, SqlExecutionContext context) throws SqlException { @@ -312,7 +317,7 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { try (RecordCursor cursor = factory.getCursor(context)) { Record record = cursor.getRecord(); while (cursor.hasNext()) { - String hostname = record.getStr(0).toString(); + String hostname = getStr(record, 0); if (hostnames.isEmpty() || hostnames.contains(hostname)) { snapshots.put(hostname, new NodeMetricSnapshot(Instant.ofEpochMilli(record.getTimestamp(1) / 1000), @@ -345,9 +350,9 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { try (RecordCursor cursor = factory.getCursor(context)) { Record record = cursor.getRecord(); while (cursor.hasNext()) { - String applicationIdString = record.getStr(0).toString(); + String applicationIdString = getStr(record, 0); if ( ! application.serializedForm().equals(applicationIdString)) continue; - String clusterId = record.getStr(1).toString(); + String clusterId = getStr(record, 1); if (cluster.value().equals(clusterId)) { snapshots.add(new ClusterMetricSnapshot(Instant.ofEpochMilli(record.getTimestamp(2) / 1000), record.getFloat(3), @@ -475,7 +480,7 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { try (RecordCursor cursor = factory.getCursor(context)) { Record record = cursor.getRecord(); while (cursor.hasNext()) { - columns.add(record.getStr(0).toString()); + columns.add(getStr(record, 0)); } } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java index ab103b0bfcf..939c958efdd 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DirtyExpirer.java @@ -27,8 +27,8 @@ public class DirtyExpirer extends Expirer { private final boolean wantToDeprovisionOnExpiry; - DirtyExpirer(NodeRepository nodeRepository, Duration dirtyTimeout, Metric metric) { - super(Node.State.dirty, History.Event.Type.deallocated, nodeRepository, dirtyTimeout, metric); + DirtyExpirer(NodeRepository nodeRepository, Duration expiryTime, Metric metric) { + super(Node.State.dirty, History.Event.Type.deallocated, nodeRepository, expiryTime, metric); // Deprovision hosts on expiry if dynamically provisioned this.wantToDeprovisionOnExpiry = nodeRepository.zone().cloud().dynamicProvisioning(); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java index d97f4566e57..76abf5c2aef 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java @@ -16,7 +16,7 @@ import java.util.List; /** * Maintenance job which moves inactive nodes to dirty or parked after timeout. * - * The timeout is in place to provide a grace period in which nodes can be brought back to active + * The expiry time is in place to provide a grace period in which nodes can be brought back to active * if they were deactivated in error. As inactive nodes retain their state * they can be brought back to active and correct state faster than a new node. * @@ -32,12 +32,12 @@ import java.util.List; public class InactiveExpirer extends Expirer { private final NodeRepository nodeRepository; - private final Duration timeout; + private final Duration expiryTime; - InactiveExpirer(NodeRepository nodeRepository, Duration timeout, Metric metric) { - super(Node.State.inactive, History.Event.Type.deactivated, nodeRepository, timeout, metric); + InactiveExpirer(NodeRepository nodeRepository, Duration expiryTime, Metric metric) { + super(Node.State.inactive, History.Event.Type.deactivated, nodeRepository, expiryTime, metric); this.nodeRepository = nodeRepository; - this.timeout = timeout; + this.expiryTime = expiryTime; } @Override @@ -49,12 +49,12 @@ public class InactiveExpirer extends Expirer { @Override protected boolean isExpired(Node node) { - return super.isExpired(node, timeout(node)) || + return super.isExpired(node, expiryTime(node)) || node.allocation().get().owner().instance().isTester(); } - private Duration timeout(Node node) { - return timeout; + private Duration expiryTime(Node node) { + return expiryTime; } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java index 24901cb10a9..adcc486d5b4 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ProvisionedExpirer.java @@ -27,8 +27,8 @@ public class ProvisionedExpirer extends Expirer { private final NodeRepository nodeRepository; - ProvisionedExpirer(NodeRepository nodeRepository, Duration timeout, Metric metric) { - super(Node.State.provisioned, History.Event.Type.provisioned, nodeRepository, timeout, metric); + ProvisionedExpirer(NodeRepository nodeRepository, Duration expiryTime, Metric metric) { + super(Node.State.provisioned, History.Event.Type.provisioned, nodeRepository, expiryTime, metric); this.nodeRepository = nodeRepository; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Reports.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Reports.java index 8e82307deb1..e10a4e82a0c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Reports.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Reports.java @@ -6,7 +6,6 @@ import com.yahoo.slime.Inspector; import com.yahoo.slime.ObjectTraverser; import com.yahoo.slime.Type; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -20,8 +19,8 @@ public class Reports { private final Map<String, Report> reports; - public Reports() { this(Collections.emptyMap()); } - private Reports(Map<String, Report> reports) { this.reports = Collections.unmodifiableMap(reports); } + public Reports() { this(Map.of()); } + private Reports(Map<String, Report> reports) { this.reports = Map.copyOf(reports); } public boolean isEmpty() { return reports.isEmpty(); } public Optional<Report> getReport(String id) { return Optional.ofNullable(reports.get(id)); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ParentHostFilter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ParentHostFilter.java index d5aa82a8dc2..821fcd51e18 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ParentHostFilter.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ParentHostFilter.java @@ -12,7 +12,7 @@ import java.util.stream.Collectors; /** * Filter based on the parent host value (for virtualized nodes). * - * @author dybis + * @author Haakon Dybdahl */ public class ParentHostFilter { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java index 1315207efb8..8a5780496b1 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java @@ -1,6 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.persistence; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterInfo; import com.yahoo.config.provision.IntRange; import com.yahoo.config.provision.ApplicationId; @@ -56,7 +57,7 @@ public class ApplicationSerializer { private static final String maxResourcesKey = "max"; private static final String groupSizeKey = "groupSize"; private static final String requiredKey = "required"; - private static final String suggestedKey = "suggested"; + private static final String cloudAccountKey = "cloudAccount"; private static final String suggestionsKey = "suggestionsKey"; private static final String clusterInfoKey = "clusterInfo"; private static final String bcpDeadlineKey = "bcpDeadline"; @@ -141,8 +142,7 @@ public class ApplicationSerializer { toSlime(cluster.maxResources(), clusterObject.setObject(maxResourcesKey)); toSlime(cluster.groupSize(), clusterObject.setObject(groupSizeKey)); clusterObject.setBool(requiredKey, cluster.required()); - // TODO(olaa): Remove 'suggested' once all configservers have stopped reading entry - toSlime(Autoscaling.empty(), clusterObject.setObject(suggestedKey)); + cluster.cloudAccount().ifPresent(cloudAccount -> clusterObject.setString(cloudAccountKey, cloudAccount.value())); toSlime(cluster.suggestions(), clusterObject.setArray(suggestionsKey)); toSlime(cluster.target(), clusterObject.setObject(targetKey)); if (! cluster.clusterInfo().isEmpty()) @@ -159,6 +159,7 @@ public class ApplicationSerializer { clusterResourcesFromSlime(clusterObject.field(maxResourcesKey)), intRangeFromSlime(clusterObject.field(groupSizeKey)), clusterObject.field(requiredKey).asBool(), + optionalCloudAccount(clusterObject.field(cloudAccountKey)), suggestionsFromSlime(clusterObject.field(suggestionsKey)), autoscalingFromSlime(clusterObject.field(targetKey)), clusterInfoFromSlime(clusterObject.field(clusterInfoKey)), @@ -329,6 +330,10 @@ public class ApplicationSerializer { }; } + private static Optional<CloudAccount> optionalCloudAccount(Inspector inspector) { + return inspector.valid() ? Optional.of(CloudAccount.from(inspector.asString())) : Optional.empty(); + } + private static Optional<Instant> optionalInstant(Inspector inspector) { return inspector.valid() ? Optional.of(Instant.ofEpochMilli(inspector.asLong())) : Optional.empty(); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java index d511570881b..d2edaaf3737 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java @@ -157,7 +157,7 @@ public class CuratorDb { * @return the nodes in their persisted state */ public List<Node> writeTo(List<Node> nodes, Agent agent, Optional<String> reason) { - if (nodes.isEmpty()) return Collections.emptyList(); + if (nodes.isEmpty()) return List.of(); List<Node> writtenNodes = new ArrayList<>(nodes.size()); @@ -191,7 +191,7 @@ public class CuratorDb { } public Node writeTo(Node.State toState, Node node, Agent agent, Optional<String> reason) { - return writeTo(toState, Collections.singletonList(node), agent, reason).get(0); + return writeTo(toState, List.of(node), agent, reason).get(0); } /** diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java index a1306d7831a..ec48e1d5f34 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java @@ -51,6 +51,8 @@ public class CapacityPolicies { private ClusterResources applyOn(ClusterResources resources, Capacity capacity, ApplicationId application, boolean exclusive) { int nodes = decideSize(resources.nodes(), capacity.isRequired(), application.instance().isTester()); int groups = Math.min(resources.groups(), nodes); // cannot have more groups than nodes + while (groups > 1 && nodes % groups != 0) + groups--; // Must be divisible by the number of groups var nodeResources = decideNodeResources(resources.nodeResources(), capacity.isRequired(), exclusive); return new ClusterResources(nodes, groups, nodeResources); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java index 0d6a98f50a3..dae4b11a609 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java @@ -1,6 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.provisioning; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; @@ -41,10 +42,10 @@ public class EmptyProvisionServiceProvider implements ProvisionServiceProvider { public NodeResources advertisedResourcesOf(Flavor flavor) { return flavor.resources(); } @Override - public NodeResources requestToReal(NodeResources resources, boolean exclusive, boolean bestCase) { return resources; } + public NodeResources requestToReal(NodeResources resources, CloudAccount cloudAccount, boolean exclusive, boolean bestCase) { return resources; } @Override - public NodeResources realToRequest(NodeResources resources, boolean exclusive, boolean bestCase) { return resources; } + public NodeResources realToRequest(NodeResources resources, CloudAccount cloudAccount, boolean exclusive, boolean bestCase) { return resources; } @Override public long reservedDiskSpaceInBase2Gb(NodeType nodeType, boolean sharedHost) { return 0; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java index e474ae6eea3..204660f9869 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java @@ -1,6 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.provisioning; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; @@ -28,13 +29,14 @@ public interface HostResourcesCalculator { * Used with exclusive hosts: * Returns the lowest possible real resources we'll get if requesting the given advertised resources */ - NodeResources requestToReal(NodeResources advertisedResources, boolean exclusiveAllocation, boolean bestCase); + NodeResources requestToReal(NodeResources advertisedResources, CloudAccount cloudAccount, + boolean exclusiveAllocation, boolean bestCase); /** * Used with shared hosts: * Returns the advertised resources we need to request to be sure to get at least the given real resources. */ - NodeResources realToRequest(NodeResources realResources, boolean exclusiveAllocation, boolean bestCase); + NodeResources realToRequest(NodeResources realResources, CloudAccount cloudAccount, boolean exclusiveAllocation, boolean bestCase); /** * Returns the disk space to reserve in base2 GB. This space is reserved for use by the host, e.g. for storing diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java index 8c52f389daf..b149a9af2c2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java @@ -179,6 +179,13 @@ class NodeAllocation { if (violatesExclusivity(candidate) != NodeCandidate.ExclusivityViolation.NONE) return Retirement.violatesExclusivity; if (requiredHostFlavor.isPresent() && ! candidate.parent.map(node -> node.flavor().name()).equals(requiredHostFlavor)) return Retirement.violatesHostFlavor; if (candidate.violatesSpares) return Retirement.violatesSpares; + + var group = candidate.allocation().get().membership().cluster().group(); + if (cluster.isStateful() && group.isPresent() && requested.count().isPresent()) { + long nodesInGroup = nodes.values().stream().filter(n -> groupOf(n).equals(group) && ! isRetired(n)).count(); + if (nodesInGroup >= requested.groupSize()) + return Retirement.groupSurplus; + } return Retirement.none; } @@ -290,6 +297,10 @@ class NodeAllocation { return candidate.allocation().flatMap(a -> a.membership().cluster().group()); } + private boolean isRetired(NodeCandidate candidate) { + return candidate.allocation().map(a -> a.membership().retired()).orElse(false); + } + private Node resize(Node node) { NodeResources hostResources = allNodes.parentOf(node).get().flavor().resources(); return node.with(new Flavor(requested.resources().get() @@ -463,6 +474,7 @@ class NodeAllocation { violatesHostFlavor("node violates host flavor"), violatesHostFlavorGeneration("node violates host flavor generation"), violatesSpares("node is assigned to a host we want to use as a spare"), + groupSurplus("group has enough nodes"), none(""); private final String description; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java index d4c4e86f0a3..d8565b81e41 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java @@ -55,6 +55,7 @@ public abstract class NodeCandidate implements Nodelike, Comparable<NodeCandidat final boolean exclusiveSwitch; /** True if this node belongs to a group which will not be needed after this deployment */ + // TODO: Always false final boolean isSurplus; /** This node does not exist in the node repository yet */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java index 8e910a4d61c..7ac80dfbdb3 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java @@ -6,6 +6,7 @@ import com.yahoo.config.provision.ActivationContext; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationTransaction; import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostFilter; @@ -187,7 +188,8 @@ public class NodeRepositoryProvisioner implements Provisioner { boolean firstDeployment = nodes.isEmpty(); var current = firstDeployment // start at min, preserve current resources otherwise - ? new AllocatableResources(initialResourcesFrom(requested, clusterSpec, application.id()), clusterSpec, nodeRepository) + ? new AllocatableResources(initialResourcesFrom(requested, clusterSpec, application.id()), clusterSpec, + nodeRepository, requested.cloudAccount().orElse(CloudAccount.empty)) : new AllocatableResources(nodes, nodeRepository); var model = new ClusterModel(nodeRepository, application, clusterSpec, cluster, nodes, current, nodeRepository.metricsDb(), nodeRepository.clock()); return within(Limits.of(requested), model, firstDeployment); @@ -199,9 +201,7 @@ public class NodeRepositoryProvisioner implements Provisioner { /** Make the minimal adjustments needed to the current resources to stay within the limits */ - private ClusterResources within(Limits limits, - ClusterModel model, - boolean firstDeployment) { + private ClusterResources within(Limits limits, ClusterModel model, boolean firstDeployment) { if (limits.min().equals(limits.max())) return limits.min(); // Don't change current deployments that are still legal @@ -209,9 +209,7 @@ public class NodeRepositoryProvisioner implements Provisioner { return model.current().advertisedResources(); // Otherwise, find an allocation that preserves the current resources as well as possible - return allocationOptimizer.findBestAllocation(Load.one(), - model, - limits) + return allocationOptimizer.findBestAllocation(Load.one(), model, limits) .orElseThrow(() -> newNoAllocationPossible(model.current().clusterSpec(), limits)) .advertisedResources(); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/HostCapacityResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/HostCapacityResponse.java index 9f7c795cf48..9fbc096667e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/HostCapacityResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/HostCapacityResponse.java @@ -12,7 +12,6 @@ import com.yahoo.vespa.hosted.provision.maintenance.CapacityChecker; import java.io.IOException; import java.io.OutputStream; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -46,7 +45,7 @@ public class HostCapacityResponse extends HttpResponse { } private List<Node> parseHostList(String hosts) { - List<String> hostNames = Arrays.asList(hosts.split(",")); + List<String> hostNames = List.of(hosts.split(",")); try { return capacityChecker.nodesFromHostnames(hostNames); } catch (IllegalArgumentException e) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java index c518087f325..cfe4886c903 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java @@ -27,7 +27,6 @@ import com.yahoo.vespa.orchestrator.status.HostInfo; import com.yahoo.vespa.orchestrator.status.HostStatus; import java.net.URI; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; @@ -66,7 +65,7 @@ class NodesResponse extends SlimeJsonResponse { this.wantedDockerTagFlag = PermanentFlags.WANTED_DOCKER_TAG.bindTo(nodeRepository.flagSource()); // Cannot use Set.of() because the nodeRepository account can also be the empty account (at least in tests). - var nonEnclaveAccounts = new HashSet<>(Arrays.asList(CloudAccount.empty, nodeRepository.zone().cloud().account())); + var nonEnclaveAccounts = new HashSet<>(List.of(CloudAccount.empty, nodeRepository.zone().cloud().account())); this.filter = NodesV2ApiHandler.toNodeFilter(request, nonEnclaveAccounts); Cursor root = slime.setObject(); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java index f653416d973..5795e25d247 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java @@ -8,7 +8,7 @@ import com.yahoo.config.provision.SystemName; * For running NodeRepository API with some mocked data. * This is used by both NodeAdmin and NodeRepository tests. * - * @author dybis + * @author Haakon Dybdahl */ public class ContainerConfig { @@ -27,6 +27,7 @@ public class ContainerConfig { <accesslog type='disabled'/> <component id='com.yahoo.test.ManualClock'/> <component id='com.yahoo.vespa.curator.mock.MockCurator'/> + <component id='com.yahoo.vespa.hosted.provision.autoscale.MemoryMetricsDb'/> <component id='com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock'/> <component id='com.yahoo.vespa.hosted.provision.testutils.MockDeployer'/> <component id='com.yahoo.vespa.hosted.provision.testutils.MockInfraDeployer'/> @@ -34,7 +35,6 @@ public class ContainerConfig { <component id='com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub'/> <component id='com.yahoo.vespa.hosted.provision.testutils.MockDuperModel'/> <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors'/> - <component id='com.yahoo.vespa.hosted.provision.autoscale.QuestMetricsDb'/> <component id='com.yahoo.vespa.hosted.provision.testutils.MockMetricsFetcher'/> <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository'/> <component id='com.yahoo.vespa.hosted.provision.testutils.MockProvisionServiceProvider'/> diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java index 5f0ac4fbcdb..166fcb8edc7 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -37,7 +38,7 @@ public class MockNameResolver implements NameResolver { Objects.requireNonNull(hostname, "hostname must be non-null"); Arrays.stream(ipAddress).forEach(ip -> Objects.requireNonNull(ip, "ipAddress must be non-null")); records.computeIfAbsent(hostname, (k) -> new HashSet<>()) - .addAll(Arrays.asList(ipAddress)); + .addAll(List.of(ipAddress)); return this; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ServiceMonitorStub.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ServiceMonitorStub.java index 5409061c441..700a16577af 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ServiceMonitorStub.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ServiceMonitorStub.java @@ -19,7 +19,6 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.service.monitor.ServiceModel; import com.yahoo.vespa.service.monitor.ServiceMonitor; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -38,7 +37,7 @@ public class ServiceMonitorStub implements ServiceMonitor { @Inject @SuppressWarnings("unused") public ServiceMonitorStub(NodeRepository nodeRepository) { - this(Collections.emptyMap(), nodeRepository); + this(Map.of(), nodeRepository); } /** Create a service monitor where all nodes are initially up */ diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java index 5e4dfdc974d..8318ec65f05 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.autoscale; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; @@ -92,7 +93,7 @@ public class ClusterModelTest { return new ClusterModel(nodeRepository, application.with(status), clusterSpec, cluster, - new AllocatableResources(clusterResources(), clusterSpec, nodeRepository), + new AllocatableResources(clusterResources(), clusterSpec, nodeRepository, cluster.cloudAccount().orElse(CloudAccount.empty)), clock, Duration.ofMinutes(10), Duration.ofMinutes(5), timeseries(cluster,100, queryRate, writeRate, clock), ClusterNodesTimeseries.empty()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsHostResourcesCalculatorImpl.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsHostResourcesCalculatorImpl.java index 0fa73aa50a5..f2507786a8b 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsHostResourcesCalculatorImpl.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsHostResourcesCalculatorImpl.java @@ -1,6 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.autoscale.awsnodes; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; @@ -43,7 +44,7 @@ public class AwsHostResourcesCalculatorImpl implements HostResourcesCalculator { } @Override - public NodeResources requestToReal(NodeResources advertisedResources, boolean exclusive, boolean bestCase) { + public NodeResources requestToReal(NodeResources advertisedResources, CloudAccount cloudAccount, boolean exclusive, boolean bestCase) { var consideredFlavors = consideredFlavorsGivenAdvertised(advertisedResources); double memoryOverhead = consideredFlavors.stream() .mapToDouble(flavor -> resourcesCalculator.memoryOverhead(flavor, advertisedResources, false)) @@ -56,7 +57,7 @@ public class AwsHostResourcesCalculatorImpl implements HostResourcesCalculator { } @Override - public NodeResources realToRequest(NodeResources realResources, boolean exclusive, boolean bestCase) { + public NodeResources realToRequest(NodeResources realResources, CloudAccount cloudAccount, boolean exclusive, boolean bestCase) { double chosenMemoryOverhead = bestCase ? Integer.MAX_VALUE : 0; double chosenDiskOverhead = bestCase ? Integer.MAX_VALUE : 0; for (VespaFlavor flavor : consideredFlavorsGivenReal(realResources)) { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java index f25d4cc3c30..72f402ca997 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java @@ -1,6 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.persistence; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterInfo; import com.yahoo.config.provision.IntRange; import com.yahoo.config.provision.ApplicationId; @@ -40,6 +41,7 @@ public class ApplicationSerializerTest { new ClusterResources(12, 6, new NodeResources(3, 6, 21, 24)), IntRange.empty(), true, + Optional.empty(), List.of(), Autoscaling.empty(), ClusterInfo.empty(), @@ -52,6 +54,7 @@ public class ApplicationSerializerTest { new ClusterResources(14, 7, new NodeResources(3, 6, 21, 24)), IntRange.of(3, 5), false, + Optional.of(CloudAccount.from("aws:123456789012")), List.of(new Autoscaling(Autoscaling.Status.unavailable, "", Optional.of(new ClusterResources(20, 10, @@ -97,6 +100,7 @@ public class ApplicationSerializerTest { assertEquals(originalCluster.maxResources(), serializedCluster.maxResources()); assertEquals(originalCluster.groupSize(), serializedCluster.groupSize()); assertEquals(originalCluster.required(), serializedCluster.required()); + assertEquals(originalCluster.cloudAccount(), serializedCluster.cloudAccount()); assertEquals(originalCluster.suggestions(), serializedCluster.suggestions()); assertEquals(originalCluster.target(), serializedCluster.target()); assertEquals(originalCluster.clusterInfo(), serializedCluster.clusterInfo()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java index ff5ffd82bf1..a91902c8eba 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java @@ -399,7 +399,7 @@ public class DynamicAllocationTest { } @Test - public void node_resources_are_relaxed_in_dev() { + public void node_resources_are_reduced_in_dev() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.dev, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.fast)), NodeType.host, 10, true); tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.slow)), NodeType.host, 10, true); @@ -419,6 +419,22 @@ public class DynamicAllocationTest { } @Test + public void node_resources_are_reduced_in_staging() { + var resources = new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.fast); + ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.staging, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); + tester.makeReadyNodes(3, new Flavor(resources), NodeType.host, 10, true); + tester.activateTenantHosts(); + + ApplicationId application = ProvisioningTester.applicationId(); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("1").build(); + + List<HostSpec> hosts = tester.prepare(application, cluster, 36, 2, resources); + tester.activate(application, hosts); + assertEquals(3, hosts.size()); + assertEquals(1, hosts.stream().map(host -> host.membership().get().cluster().group().get()).distinct().count()); + } + + @Test public void switching_from_legacy_flavor_syntax_to_resources_does_not_cause_reallocation() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(2, new Flavor(new NodeResources(5, 20, 1400, 3)), NodeType.host, 10, true); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java index abcef421b4c..78a34326949 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java @@ -553,6 +553,34 @@ public class DynamicProvisioningTest { 2, 1, resources); } + @Test + public void split_into_two_groups() { + List<Flavor> flavors = List.of(new Flavor("2x", new NodeResources(2, 20, 200, 0.1, fast, local))); + + ProvisioningTester tester = new ProvisioningTester.Builder().dynamicProvisioning(true, false) + .flavors(flavors) + .hostProvisioner(new MockHostProvisioner(flavors)) + .nameResolver(nameResolver) + .build(); + + tester.activateTenantHosts(); + + ApplicationId app1 = applicationId("app1"); + ClusterSpec cluster1 = ClusterSpec.request(content, new ClusterSpec.Id("cluster1")).vespaVersion("8").build(); + + System.out.println("Initial deployment ----------------------"); + tester.activate(app1, cluster1, Capacity.from(resources(6, 1, 2, 20, 200, fast, StorageType.any))); + tester.assertNodes("Initial deployment: 1 group", + 6, 1, 2, 20, 200, fast, remote, app1, cluster1); + + System.out.println("Split into 2 groups ---------------------"); + tester.activate(app1, cluster1, Capacity.from(resources(6, 2, 2, 20, 200, fast, StorageType.any))); + tester.assertNodes("Change to 2 groups: Gets 6 active non-retired nodes", + 6, 2, 2, 20, 200, fast, remote, app1, cluster1); + List<Node> retired = tester.nodeRepository().nodes().list().owner(app1).cluster(cluster1.id()).state(Node.State.active).retired().asList(); + assertEquals("... and in addition 3 retired nodes", 3, retired.size()); + } + private ProvisioningTester tester(boolean sharing) { var hostProvisioner = new MockHostProvisioner(new NodeFlavors(ProvisioningTester.createConfig()).getFlavors(), nameResolver, 0); return new ProvisioningTester.Builder().dynamicProvisioning(true, sharing).hostProvisioner(hostProvisioner).nameResolver(nameResolver).build(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java index 1f8178dff6a..183ff85da47 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.provisioning; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; @@ -143,6 +144,7 @@ public class DynamicProvisioningTester { cluster.maxResources(), cluster.groupSize(), cluster.required(), + cluster.cloudAccount(), cluster.suggestions(), cluster.target(), cluster.clusterInfo(), @@ -265,12 +267,12 @@ public class DynamicProvisioningTester { } @Override - public NodeResources requestToReal(NodeResources resources, boolean exclusive, boolean bestCase) { + public NodeResources requestToReal(NodeResources resources, CloudAccount cloudAccount, boolean exclusive, boolean bestCase) { return resources.withMemoryGb(resources.memoryGb()); } @Override - public NodeResources realToRequest(NodeResources resources, boolean exclusive, boolean bestCase) { + public NodeResources realToRequest(NodeResources resources, CloudAccount cloudAccount, boolean exclusive, boolean bestCase) { return resources.withMemoryGb(resources.memoryGb()); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java index 5f2790e886a..7b690b880c2 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java @@ -288,13 +288,13 @@ public class ProvisioningTest { assertEquals("Superfluous container nodes are also dirtyed", 4-2 + 5-2 + 1 + 4-2, tester.nodeRepository().nodes().list(Node.State.dirty).size()); assertEquals("Superfluous content nodes are retired", - 5-3 + 6-3 - 1, tester.getNodes(application1, Node.State.active).retired().size()); + 5-3 + 6-3 -1, tester.getNodes(application1, Node.State.active).retired().size()); // increase content slightly SystemState state6 = prepare(application1, 2, 2, 4, 3, defaultResources, tester); tester.activate(application1, state6.allHosts); assertEquals("One content node is unretired", - 5-4 + 6-3 - 1, tester.getNodes(application1, Node.State.active).retired().size()); + 5-4 + 6-3 -1, tester.getNodes(application1, Node.State.active).retired().size()); // Then reserve more SystemState state7 = prepare(application1, 8, 2, 2, 2, defaultResources, tester); @@ -505,7 +505,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(31, hostFlavor.resources()).activateTenantHosts(); + tester.makeReadyHosts(32, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = ProvisioningTester.applicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java index 48bed11d83f..4ec290dd7ba 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java @@ -811,13 +811,13 @@ public class ProvisioningTester { } @Override - public NodeResources requestToReal(NodeResources resources, boolean exclusive, boolean bestCase) { + public NodeResources requestToReal(NodeResources resources, CloudAccount cloudAccount, boolean exclusive, boolean bestCase) { return resources.withMemoryGb(resources.memoryGb() - memoryTaxGb) .withDiskGb(resources.diskGb() - ( resources.storageType() == local ? localDiskTax : 0) ); } @Override - public NodeResources realToRequest(NodeResources resources, boolean exclusive, boolean bestCase) { + public NodeResources realToRequest(NodeResources resources, CloudAccount cloudAccount, boolean exclusive, boolean bestCase) { return resources.withMemoryGb(resources.memoryGb() + memoryTaxGb) .withDiskGb(resources.diskGb() + ( resources.storageType() == local ? localDiskTax : 0) ); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningCompleteHostCalculatorTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningCompleteHostCalculatorTest.java index c18815fc439..1bf42d72180 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningCompleteHostCalculatorTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningCompleteHostCalculatorTest.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.provisioning; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; @@ -62,8 +63,8 @@ public class VirtualNodeProvisioningCompleteHostCalculatorTest { Flavor hostFlavor = new Flavor(new NodeResources(20, 40, 1000, 4)); var calculator = new CompleteResourcesCalculator(hostFlavor); var originalReal = new NodeResources(0.7, 6.0, 12.9, 1.0); - var realToRequest = calculator.realToRequest(originalReal, false, false); - var requestToReal = calculator.requestToReal(realToRequest, false, false); + var realToRequest = calculator.realToRequest(originalReal, CloudAccount.empty, false, false); + var requestToReal = calculator.requestToReal(realToRequest, CloudAccount.empty, false, false); var realResourcesOf = calculator.realResourcesOf(realToRequest); assertEquals(originalReal, requestToReal); assertEquals(originalReal, realResourcesOf); @@ -93,7 +94,8 @@ public class VirtualNodeProvisioningCompleteHostCalculatorTest { } @Override - public NodeResources requestToReal(NodeResources advertisedResources, boolean exclusive, boolean bestCase) { + public NodeResources requestToReal(NodeResources advertisedResources, CloudAccount cloudAccount, + boolean exclusive, boolean bestCase) { double memoryOverhead = memoryOverhead(advertisedResourcesOf(hostFlavor).memoryGb(), advertisedResources, false); double diskOverhead = diskOverhead(advertisedResourcesOf(hostFlavor).diskGb(), advertisedResources, false); return advertisedResources.withMemoryGb(advertisedResources.memoryGb() - memoryOverhead) @@ -108,7 +110,8 @@ public class VirtualNodeProvisioningCompleteHostCalculatorTest { } @Override - public NodeResources realToRequest(NodeResources realResources, boolean exclusive, boolean bestCase) { + public NodeResources realToRequest(NodeResources realResources, CloudAccount cloudAccount, + boolean exclusive, boolean bestCase) { double memoryOverhead = memoryOverhead(advertisedResourcesOf(hostFlavor).memoryGb(), realResources, true); double diskOverhead = diskOverhead(advertisedResourcesOf(hostFlavor).diskGb(), realResources, true); return realResources.withMemoryGb(realResources.memoryGb() + memoryOverhead) |