From e06a6964a293de546dde991a98fb8b2426ca54e1 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Wed, 30 Oct 2019 12:35:34 +0100 Subject: Remember requested resources on nodes This may be different from assigned resources e.g in that requested resources may specify DiskSpeed.any while assigned resources always have a definite disk speed. --- config-provisioning/abi-spec.json | 2 + .../java/com/yahoo/config/provision/HostSpec.java | 20 ++++-- .../serialization/AllocatedHostsSerializer.java | 79 +++++++++++----------- .../AllocatedHostsSerializerTest.java | 8 ++- .../com/yahoo/vespa/hosted/provision/Node.java | 5 +- .../vespa/hosted/provision/node/Allocation.java | 29 +++++--- .../provision/persistence/NodeSerializer.java | 47 +++++++------ .../hosted/provision/provisioning/Activator.java | 16 ++--- .../provision/provisioning/NodeAllocation.java | 12 ++-- .../provision/provisioning/NodePrioritizer.java | 2 +- .../provisioning/NodeRepositoryProvisioner.java | 8 ++- .../hosted/provision/provisioning/NodeSpec.java | 12 +++- .../hosted/provision/provisioning/Preparer.java | 3 +- .../maintenance/CapacityCheckerTester.java | 2 +- .../DynamicProvisioningMaintainerTest.java | 1 + .../provision/maintenance/MetricsReporterTest.java | 7 +- .../provision/persistence/SerializationTest.java | 32 +++++---- .../provisioning/AllocationSimulator.java | 5 +- .../provisioning/DynamicDockerAllocationTest.java | 2 +- .../provisioning/InfraDeployerImplTest.java | 2 +- .../provision/provisioning/ProvisioningTest.java | 32 +++++++++ .../provision/provisioning/ProvisioningTester.java | 3 +- 22 files changed, 212 insertions(+), 117 deletions(-) diff --git a/config-provisioning/abi-spec.json b/config-provisioning/abi-spec.json index 4e50d31d74a..d8b17009b65 100644 --- a/config-provisioning/abi-spec.json +++ b/config-provisioning/abi-spec.json @@ -477,12 +477,14 @@ "public void (java.lang.String, java.util.List, java.util.Optional, java.util.Optional)", "public void (java.lang.String, java.util.List, java.util.Optional, java.util.Optional, java.util.Optional)", "public void (java.lang.String, java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, java.util.Optional)", + "public void (java.lang.String, java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, java.util.Optional, java.util.Optional)", "public java.lang.String hostname()", "public java.util.List aliases()", "public java.util.Optional flavor()", "public java.util.Optional version()", "public java.util.Optional membership()", "public java.util.Optional networkPorts()", + "public java.util.Optional requestedResources()", "public java.lang.String toString()", "public boolean equals(java.lang.Object)", "public int hashCode()", diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java index ba7a3c2f06b..a046df1ac9a 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java @@ -29,6 +29,8 @@ public class HostSpec implements Comparable { private final Optional networkPorts; + private Optional requestedResources; + public HostSpec(String hostname, Optional membership) { this(hostname, new ArrayList<>(), Optional.empty(), membership); } @@ -61,15 +63,22 @@ public class HostSpec implements Comparable { public HostSpec(String hostname, List aliases, Optional flavor, Optional membership, Optional version, Optional networkPorts) { + this(hostname, aliases, flavor, membership, version, networkPorts, Optional.empty()); + } + + public HostSpec(String hostname, List aliases, Optional flavor, + Optional membership, Optional version, + Optional networkPorts, Optional requestedResources) { if (hostname == null || hostname.isEmpty()) throw new IllegalArgumentException("Hostname must be specified"); - Objects.requireNonNull(version, "Version cannot be null but can be empty"); - Objects.requireNonNull(networkPorts, "Network ports cannot be null but can be empty"); + + this.hostname = hostname; this.aliases = List.copyOf(aliases); this.flavor = flavor; this.membership = membership; - this.version = version; - this.networkPorts = networkPorts; + this.version = Objects.requireNonNull(version, "Version cannot be null but can be empty");; + this.networkPorts = Objects.requireNonNull(networkPorts, "Network ports cannot be null but can be empty");; + this.requestedResources = Objects.requireNonNull(requestedResources, "RequestedResources cannot be null"); } /** Returns the name identifying this host */ @@ -89,6 +98,9 @@ public class HostSpec implements Comparable { /** Returns the network port allocations on this host, or empty if not present */ public Optional networkPorts() { return networkPorts; } + /** Returns the requested resources leading to this host being provisioned, or empty if not known */ + public Optional requestedResources() { return requestedResources; } + @Override public String toString() { return hostname + 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 a7ba286baa7..4a060fb5143 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 @@ -49,6 +49,7 @@ public class AllocatedHostsSerializer { private static final String flavorKey = "flavor"; private static final String resourcesKey = "resources"; + private static final String requestedResourcesKey = "requestedResources"; private static final String vcpuKey = "vcpu"; private static final String memoryKey = "memory"; private static final String diskKey = "disk"; @@ -75,16 +76,17 @@ public class AllocatedHostsSerializer { toSlime(host, array.addObject().setObject(hostSpecKey)); } - private static void toSlime(HostSpec host, Cursor cursor) { - cursor.setString(hostSpecHostNameKey, host.hostname()); - aliasesToSlime(host, cursor); + private static void toSlime(HostSpec host, Cursor object) { + object.setString(hostSpecHostNameKey, host.hostname()); + aliasesToSlime(host, object); host.membership().ifPresent(membership -> { - cursor.setString(hostSpecMembershipKey, membership.stringValue()); - cursor.setString(hostSpecVespaVersionKey, membership.cluster().vespaVersion().toFullString()); + object.setString(hostSpecMembershipKey, membership.stringValue()); + object.setString(hostSpecVespaVersionKey, membership.cluster().vespaVersion().toFullString()); }); - host.flavor().ifPresent(flavor -> toSlime(flavor, cursor)); - host.version().ifPresent(version -> cursor.setString(hostSpecCurrentVespaVersionKey, version.toFullString())); - host.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, cursor.setArray(hostSpecNetworkPortsKey))); + host.flavor().ifPresent(flavor -> toSlime(flavor, object)); + host.requestedResources().ifPresent(resources -> toSlime(resources, object.setObject(requestedResourcesKey))); + host.version().ifPresent(version -> object.setString(hostSpecCurrentVespaVersionKey, version.toFullString())); + host.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, object.setArray(hostSpecNetworkPortsKey))); } private static void aliasesToSlime(HostSpec spec, Cursor cursor) { @@ -95,20 +97,19 @@ public class AllocatedHostsSerializer { } private static void toSlime(Flavor flavor, Cursor object) { - if (flavor.isConfigured()) { + if (flavor.isConfigured()) object.setString(flavorKey, flavor.name()); - } - else { - NodeResources resources = flavor.resources(); - Cursor resourcesObject = object.setObject(resourcesKey); - resourcesObject.setDouble(vcpuKey, resources.vcpu()); - resourcesObject.setDouble(memoryKey, resources.memoryGb()); - resourcesObject.setDouble(diskKey, resources.diskGb()); - resourcesObject.setDouble(bandwidthKey, resources.bandwidthGbps()); - resourcesObject.setString(diskSpeedKey, diskSpeedToString(resources.diskSpeed())); - } + else + toSlime(flavor.resources(), object.setObject(resourcesKey)); } + private static void toSlime(NodeResources resources, Cursor resourcesObject) { + resourcesObject.setDouble(vcpuKey, resources.vcpu()); + resourcesObject.setDouble(memoryKey, resources.memoryGb()); + resourcesObject.setDouble(diskKey, resources.diskGb()); + resourcesObject.setDouble(bandwidthKey, resources.bandwidthGbps()); + resourcesObject.setString(diskSpeedKey, diskSpeedToString(resources.diskSpeed())); + } public static AllocatedHosts fromJson(byte[] json, Optional nodeFlavors) { return fromSlime(SlimeUtils.jsonToSlime(json).get(), nodeFlavors); @@ -122,14 +123,13 @@ public class AllocatedHostsSerializer { } private static HostSpec hostFromSlime(Inspector object, Optional nodeFlavors) { - Optional membership = - object.field(hostSpecMembershipKey).valid() ? Optional.of(membershipFromSlime(object)) : Optional.empty(); - Optional flavor = flavorFromSlime(object, nodeFlavors); - Optional version = - optionalString(object.field(hostSpecCurrentVespaVersionKey)).map(com.yahoo.component.Version::new); - Optional networkPorts = - NetworkPortsSerializer.fromSlime(object.field(hostSpecNetworkPortsKey)); - return new HostSpec(object.field(hostSpecHostNameKey).asString(), aliasesFromSlime(object), flavor, membership, version, networkPorts); + return new HostSpec(object.field(hostSpecHostNameKey).asString(), + aliasesFromSlime(object), + flavorFromSlime(object, nodeFlavors), + object.field(hostSpecMembershipKey).valid() ? Optional.of(membershipFromSlime(object)) : Optional.empty(), + optionalString(object.field(hostSpecCurrentVespaVersionKey)).map(com.yahoo.component.Version::new), + NetworkPortsSerializer.fromSlime(object.field(hostSpecNetworkPortsKey)), + nodeResourcesFromSlime(object.field(requestedResourcesKey))); } private static List aliasesFromSlime(Inspector object) { @@ -140,20 +140,19 @@ public class AllocatedHostsSerializer { } private static Optional flavorFromSlime(Inspector object, Optional nodeFlavors) { - if (object.field(flavorKey).valid() && nodeFlavors.isPresent() && nodeFlavors.get().exists(object.field(flavorKey).asString())) { + if (object.field(flavorKey).valid() && nodeFlavors.isPresent() && nodeFlavors.get().exists(object.field(flavorKey).asString())) return nodeFlavors.get().getFlavor(object.field(flavorKey).asString()); - } - else if (object.field(resourcesKey).valid()) { - Inspector resources = object.field(resourcesKey); - return Optional.of(new Flavor(new NodeResources(resources.field(vcpuKey).asDouble(), - resources.field(memoryKey).asDouble(), - resources.field(diskKey).asDouble(), - resources.field(bandwidthKey).asDouble(), - diskSpeedFromSlime(resources.field(diskSpeedKey))))); - } - else { - return Optional.empty(); - } + else + return nodeResourcesFromSlime(object.field(resourcesKey)).map(resources -> new Flavor(resources)); + } + + private static Optional nodeResourcesFromSlime(Inspector resources) { + if ( ! resources.valid()) return Optional.empty(); + return Optional.of(new NodeResources(resources.field(vcpuKey).asDouble(), + resources.field(memoryKey).asDouble(), + resources.field(diskKey).asDouble(), + resources.field(bandwidthKey).asDouble(), + diskSpeedFromSlime(resources.field(diskSpeedKey)))); } private static NodeResources.DiskSpeed diskSpeedFromSlime(Inspector diskSpeed) { 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 a5af9aa0cb5..afec242de0b 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 @@ -42,7 +42,12 @@ public class AllocatedHostsSerializerTest { hosts.add(new HostSpec("flavor-from-resources-1", Collections.emptyList(), new Flavor(new NodeResources(0.5, 3.1, 4, 1)))); hosts.add(new HostSpec("flavor-from-resources-2", - Collections.emptyList(), new Flavor(new NodeResources(0.5, 3.1, 4, 1, NodeResources.DiskSpeed.any)))); + Collections.emptyList(), + Optional.of(new Flavor(new NodeResources(0.5, 3.1, 4, 1, NodeResources.DiskSpeed.slow))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.of(new NodeResources(0.5, 3.1, 4, 1, NodeResources.DiskSpeed.any)))); hosts.add(new HostSpec("configured-flavor", Collections.emptyList(), configuredFlavors.getFlavorOrThrow("C/12/45/100"))); hosts.add(new HostSpec("with-version", @@ -67,6 +72,7 @@ public class AllocatedHostsSerializerTest { assertEquals(expectedHost.version(), deserializedHost.version()); assertEquals(expectedHost.networkPorts(), deserializedHost.networkPorts()); assertEquals(expectedHost.aliases(), deserializedHost.aliases()); + assertEquals(expectedHost.requestedResources(), deserializedHost.requestedResources()); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java index 80b59bb40d0..49f96d65c81 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java @@ -249,8 +249,9 @@ public final class Node { } /** Returns a copy of this with allocation set as specified. node.state is *not* changed. */ - public Node allocate(ApplicationId owner, ClusterMembership membership, Instant at) { - return this.with(new Allocation(owner, membership, new Generation(0, 0), false)) + public Node allocate(ApplicationId owner, ClusterMembership membership, NodeResources requestedResources, Instant at) { + return this + .with(new Allocation(owner, membership, requestedResources, new Generation(0, 0), false)) .with(history.with(new History.Event(History.Event.Type.reserved, Agent.application, at))); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Allocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Allocation.java index 53e1ae3721e..30ef84c6927 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Allocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Allocation.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.node; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.NetworkPorts; +import com.yahoo.config.provision.NodeResources; import java.util.Optional; @@ -17,6 +18,8 @@ public class Allocation { private final ApplicationId owner; private final ClusterMembership clusterMembership; + private final NodeResources requestedResources; + /** * Restart generation, see {@link com.yahoo.vespa.hosted.provision.node.Generation}, * wanted is increased when a restart of the services on the node is needed, current is updated @@ -30,15 +33,16 @@ public class Allocation { private final Optional networkPorts; - public Allocation(ApplicationId owner, ClusterMembership clusterMembership, + public Allocation(ApplicationId owner, ClusterMembership clusterMembership, NodeResources requestedResources, Generation restartGeneration, boolean removable) { - this(owner, clusterMembership, restartGeneration, removable, Optional.empty()); + this(owner, clusterMembership, requestedResources, restartGeneration, removable, Optional.empty()); } - public Allocation(ApplicationId owner, ClusterMembership clusterMembership, + public Allocation(ApplicationId owner, ClusterMembership clusterMembership, NodeResources requestedResources, Generation restartGeneration, boolean removable, Optional networkPorts) { this.owner = owner; this.clusterMembership = clusterMembership; + this.requestedResources = requestedResources; this.restartGeneration = restartGeneration; this.removable = removable; this.networkPorts = networkPorts; @@ -50,6 +54,9 @@ public class Allocation { /** Returns the role this node is allocated to */ public ClusterMembership membership() { return clusterMembership; } + /** Returns the node resources requested, leading to this allocation on this node */ + public NodeResources requestedResources() { return requestedResources; } + /** Returns the restart generation (wanted and current) of this */ public Generation restartGeneration() { return restartGeneration; } @@ -58,33 +65,37 @@ public class Allocation { /** Returns a copy of this which is retired */ public Allocation retire() { - return new Allocation(owner, clusterMembership.retire(), restartGeneration, removable, networkPorts); + return new Allocation(owner, clusterMembership.retire(), requestedResources, restartGeneration, removable, networkPorts); } /** Returns a copy of this which is not retired */ public Allocation unretire() { - return new Allocation(owner, clusterMembership.unretire(), restartGeneration, removable, networkPorts); + return new Allocation(owner, clusterMembership.unretire(), requestedResources, restartGeneration, removable, networkPorts); } /** Return whether this node is ready to be removed from the application */ public boolean isRemovable() { return removable; } + public Allocation withRequestedResources(NodeResources resources) { + return new Allocation(owner, clusterMembership, resources, restartGeneration, removable, networkPorts); + } + /** Returns a copy of this with the current restart generation set to generation */ public Allocation withRestart(Generation generation) { - return new Allocation(owner, clusterMembership, generation, removable, networkPorts); + return new Allocation(owner, clusterMembership, requestedResources, generation, removable, networkPorts); } /** Returns a copy of this allocation where removable is set to true */ public Allocation removable() { - return new Allocation(owner, clusterMembership, restartGeneration, true, networkPorts); + return new Allocation(owner, clusterMembership, requestedResources, restartGeneration, true, networkPorts); } public Allocation with(ClusterMembership newMembership) { - return new Allocation(owner, newMembership, restartGeneration, removable, networkPorts); + return new Allocation(owner, newMembership, requestedResources, restartGeneration, removable, networkPorts); } public Allocation withNetworkPorts(NetworkPorts ports) { - return new Allocation(owner, clusterMembership, restartGeneration, removable, Optional.of(ports)); + return new Allocation(owner, clusterMembership, requestedResources, restartGeneration, removable, Optional.of(ports)); } @Override diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java index f4277408956..39473ed0b3a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java @@ -92,6 +92,7 @@ public class NodeSerializer { private static final String applicationIdKey = "applicationId"; private static final String instanceIdKey = "instanceId"; private static final String serviceIdKey = "serviceId"; // legacy name, TODO: change to membership with backwards compat + private static final String requestedResourcesKey = "requestedResources"; private static final String restartGenerationKey = "restartGeneration"; private static final String currentRestartGenerationKey = "currentRestartGeneration"; private static final String removableKey = "removable"; @@ -155,17 +156,20 @@ public class NodeSerializer { } } else { - NodeResources resources = flavor.resources(); - Cursor resourcesObject = object.setObject(resourcesKey); - resourcesObject.setDouble(vcpuKey, resources.vcpu()); - resourcesObject.setDouble(memoryKey, resources.memoryGb()); - resourcesObject.setDouble(diskKey, resources.diskGb()); - resourcesObject.setDouble(bandwidthKey, resources.bandwidthGbps()); - resourcesObject.setString(diskSpeedKey, diskSpeedToString(resources.diskSpeed())); + toSlime(flavor.resources(), object.setObject(resourcesKey)); } } + private void toSlime(NodeResources resources, Cursor resourcesObject) { + resourcesObject.setDouble(vcpuKey, resources.vcpu()); + resourcesObject.setDouble(memoryKey, resources.memoryGb()); + resourcesObject.setDouble(diskKey, resources.diskGb()); + resourcesObject.setDouble(bandwidthKey, resources.bandwidthGbps()); + resourcesObject.setString(diskSpeedKey, diskSpeedToString(resources.diskSpeed())); + } + private void toSlime(Allocation allocation, Cursor object) { + toSlime(allocation.requestedResources(), object.setObject(requestedResourcesKey)); object.setString(tenantIdKey, allocation.owner().tenant().value()); object.setString(applicationIdKey, allocation.owner().application().value()); object.setString(instanceIdKey, allocation.owner().instance().value()); @@ -200,15 +204,16 @@ public class NodeSerializer { } private Node nodeFromSlime(Node.State state, Inspector object) { + Flavor flavor = flavorFromSlime(object); return new Node(object.field(idKey).asString(), new IP.Config(ipAddressesFromSlime(object, ipAddressesKey), ipAddressesFromSlime(object, ipAddressPoolKey)), object.field(hostnameKey).asString(), parentHostnameFromSlime(object), - flavorFromSlime(object), + flavor, statusFromSlime(object), state, - allocationFromSlime(object.field(instanceKey)), + allocationFromSlime(flavor.resources(), object.field(instanceKey)), historyFromSlime(object.field(historyKey)), nodeTypeFromString(object.field(nodeTypeKey).asString()), Reports.fromSlime(object.field(reportsKey)), @@ -235,18 +240,25 @@ public class NodeSerializer { return flavor.with(FlavorOverrides.ofDisk(resources.field(diskKey).asDouble())); } else { - return new Flavor(new NodeResources(resources.field(vcpuKey).asDouble(), - resources.field(memoryKey).asDouble(), - resources.field(diskKey).asDouble(), - resources.field(bandwidthKey).asDouble(), - diskSpeedFromSlime(resources.field(diskSpeedKey)))); + return new Flavor(resourcesFromSlime(resources).get()); } } - private Optional allocationFromSlime(Inspector object) { - if ( ! object.valid()) return Optional.empty(); + private Optional resourcesFromSlime(Inspector resources) { + if ( ! resources.valid()) return Optional.empty(); + + return Optional.of(new NodeResources(resources.field(vcpuKey).asDouble(), + resources.field(memoryKey).asDouble(), + resources.field(diskKey).asDouble(), + resources.field(bandwidthKey).asDouble(), + diskSpeedFromSlime(resources.field(diskSpeedKey)))); + } + + private Optional allocationFromSlime(NodeResources assignedResources, Inspector object) { + if ( ! object.valid()) return Optional.empty(); // TODO: Remove this line (and to the simplifications that follows) after November 2019 return Optional.of(new Allocation(applicationIdFromSlime(object), clusterMembershipFromSlime(object), + resourcesFromSlime(object.field(requestedResourcesKey)).orElse(assignedResources), generationFromSlime(object, restartGenerationKey, currentRestartGenerationKey), object.field(removableKey).asBool(), NetworkPortsSerializer.fromSlime(object.field(networkPortsKey)))); @@ -367,8 +379,6 @@ public class NodeSerializer { } private Agent eventAgentFromSlime(Inspector eventAgentField) { - if ( ! eventAgentField.valid()) return Agent.system; // TODO: Remove after April 2017 - switch (eventAgentField.asString()) { case "application" : return Agent.application; case "system" : return Agent.system; @@ -416,7 +426,6 @@ public class NodeSerializer { } private static NodeResources.DiskSpeed diskSpeedFromSlime(Inspector diskSpeed) { - if ( ! diskSpeed.valid()) return NodeResources.DiskSpeed.fast; // TODO: Remove this line after June 2019 switch (diskSpeed.asString()) { case "fast" : return NodeResources.DiskSpeed.fast; case "slow" : return NodeResources.DiskSpeed.slow; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java index 8a6f308eb34..e994e86d07c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java @@ -138,21 +138,21 @@ class Activator { return notFoundHosts.isEmpty(); } - /** - * Returns the input nodes with the changes resulting from applying the settings in hosts to the given list of nodes. - */ + /** Returns the input nodes with the changes resulting from applying the settings in hosts to the given list of nodes. */ private List updateFrom(Collection hosts, List nodes) { List updated = new ArrayList<>(); for (Node node : nodes) { HostSpec hostSpec = getHost(node.hostname(), hosts); node = hostSpec.membership().get().retired() ? node.retire(nodeRepository.clock().instant()) : node.unretire(); - Allocation allocation = node.allocation().get().with(hostSpec.membership().get()); - if (hostSpec.networkPorts().isPresent()) { - allocation = allocation.withNetworkPorts(hostSpec.networkPorts().get()); - } - node = node.with(allocation); if (hostSpec.flavor().isPresent()) // Docker nodes may change flavor node = node.with(hostSpec.flavor().get()); + Allocation allocation = node.allocation().get() + .with(hostSpec.membership().get()) + .withRequestedResources(hostSpec.requestedResources() + .orElse(node.flavor().resources())); + if (hostSpec.networkPorts().isPresent()) + allocation = allocation.withNetworkPorts(hostSpec.networkPorts().get()); + node = node.with(allocation); updated.add(node); } return updated; 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 f6822f64570..e381fdc654e 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 @@ -140,6 +140,7 @@ class NodeAllocation { } offeredPriority.node = offered.allocate(application, ClusterMembership.from(cluster, highestIndex.add(1)), + requestedNodes.resources().orElse(offeredPriority.node.flavor().resources()), clock.instant()); accepted.add(acceptNode(offeredPriority, false)); } @@ -223,6 +224,10 @@ class NodeAllocation { private Node acceptNode(PrioritizableNode prioritizableNode, boolean wantToRetire) { Node node = prioritizableNode.node; + + if (node.allocation().isPresent()) // Record the currently requested resources + node = node.with(node.allocation().get().withRequestedResources(requestedNodes.resources().orElse(node.flavor().resources()))); + if (! wantToRetire) { if ( ! node.state().equals(Node.State.active)) { // reactivated node - make sure its not retired @@ -278,14 +283,13 @@ class NodeAllocation { * Returns {@link FlavorCount} describing the docker node deficit for the given {@link NodeSpec}. * * @return empty if the requested spec is not count based or the requested flavor type is not docker or - * the request is already fulfilled. Otherwise returns {@link FlavorCount} containing the required flavor - * and node count to cover the deficit. + * the request is already fulfilled. Otherwise returns {@link FlavorCount} containing the required flavor + * and node count to cover the deficit. */ Optional getFulfilledDockerDeficit() { return Optional.of(requestedNodes) .filter(NodeSpec.CountNodeSpec.class::isInstance) - .map(NodeSpec.CountNodeSpec.class::cast) - .map(spec -> new FlavorCount(spec.resources(), spec.fulfilledDeficitCount(acceptedOfRequestedFlavor))) + .map(spec -> new FlavorCount(spec.resources().get(), spec.fulfilledDeficitCount(acceptedOfRequestedFlavor))) .filter(flavorCount -> flavorCount.getCount() > 0); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java index cc27b1b2196..a7081060f2e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java @@ -219,7 +219,7 @@ public class NodePrioritizer { private static NodeResources resources(NodeSpec requestedNodes) { if ( ! (requestedNodes instanceof NodeSpec.CountNodeSpec)) return null; - return ((NodeSpec.CountNodeSpec)requestedNodes).resources(); + return requestedNodes.resources().get(); } private boolean isDocker() { 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 e437fd4211a..f78eaf40f60 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 @@ -103,7 +103,8 @@ public class NodeRepositoryProvisioner implements Provisioner { effectiveGroups = 1; // type request with multiple groups is not supported } - return asSortedHosts(preparer.prepare(application, cluster, requestedNodes, effectiveGroups)); + return asSortedHosts(preparer.prepare(application, cluster, requestedNodes, effectiveGroups), + requestedCapacity.nodeResources()); } @Override @@ -123,7 +124,7 @@ public class NodeRepositoryProvisioner implements Provisioner { loadBalancerProvisioner.ifPresent(lbProvisioner -> lbProvisioner.deactivate(application, transaction)); } - private List asSortedHosts(List nodes) { + private List asSortedHosts(List nodes, Optional requestedResources) { nodes.sort(Comparator.comparingInt(node -> node.allocation().get().membership().index())); List hosts = new ArrayList<>(nodes.size()); for (Node node : nodes) { @@ -134,7 +135,8 @@ public class NodeRepositoryProvisioner implements Provisioner { Optional.of(node.flavor()), Optional.of(nodeAllocation.membership()), node.status().vespaVersion(), - nodeAllocation.networkPorts())); + nodeAllocation.networkPorts(), + requestedResources)); if (nodeAllocation.networkPorts().isPresent()) { log.log(LogLevel.DEBUG, () -> "Prepared node " + node.hostname() + " has port allocations"); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java index da8d6a2ccf6..8eaa6662187 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java @@ -8,6 +8,7 @@ import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.Node; import java.util.Objects; +import java.util.Optional; /** * A specification of a set of nodes. @@ -60,6 +61,9 @@ public interface NodeSpec { */ Node assignRequestedFlavor(Node node); + /** Returns the resources requested by this or empty if none are explicitly requested */ + Optional resources(); + static NodeSpec from(int nodeCount, NodeResources flavor, boolean exclusive, boolean canFail) { return new CountNodeSpec(nodeCount, flavor, exclusive, canFail); } @@ -83,8 +87,9 @@ public interface NodeSpec { this.canFail = canFail; } - public NodeResources resources() { - return requestedNodeResources; + @Override + public Optional resources() { + return Optional.of(requestedNodeResources); } @Override @@ -198,6 +203,9 @@ public interface NodeSpec { @Override public Node assignRequestedFlavor(Node node) { return node; } + @Override + public Optional resources() { return Optional.empty(); } + @Override public String toString() { return "request for all nodes of type '" + type + "'"; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java index 6054e298277..e387645888c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Optional; +import java.util.stream.Collectors; /** * Performs preparation of node activation changes for an application. @@ -51,7 +52,7 @@ class Preparer { // Note: This operation may make persisted changes to the set of reserved and inactive nodes, // but it may not change the set of active nodes, as the active nodes must stay in sync with the // active config model which is changed on activate - public List prepareNodes(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, int wantedGroups) { + private List prepareNodes(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, int wantedGroups) { List surplusNodes = findNodesInRemovableGroups(application, cluster, wantedGroups); MutableInteger highestIndex = new MutableInteger(findHighestIndex(application, cluster)); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java index 3a29204d2f4..b2bc21d1cf1 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java @@ -266,7 +266,7 @@ public class CapacityCheckerTester { nodeModel.parentHostname, f, nodeModel.type); if (membership != null) { - return node.allocate(owner, membership, Instant.now()); + return node.allocate(owner, membership, node.flavor().resources(), Instant.now()); } else { return node; } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java index 0a4ca831d3d..71788fb1a30 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java @@ -199,6 +199,7 @@ public class DynamicProvisioningMaintainerTest { .map(app -> new Allocation( app, ClusterMembership.from("container/default/0/0", Version.fromString("7.3")), + flavor.resources(), Generation.initial(), false)); var ipConfig = new IP.Config(state == Node.State.active ? Set.of("::1") : Set.of(), Set.of()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java index db4ba661b64..ed6e6c6e9a7 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java @@ -126,12 +126,12 @@ public class MetricsReporterTest { Node container1 = Node.createDockerNode(Set.of("::2"), "container1", "dockerHost", new NodeResources(1, 3, 2, 1), NodeType.tenant); - container1 = container1.with(allocation(Optional.of("app1")).get()); + container1 = container1.with(allocation(Optional.of("app1"), container1).get()); nodeRepository.addDockerNodes(new LockedNodeList(List.of(container1), nodeRepository.lockAllocation())); Node container2 = Node.createDockerNode(Set.of("::3"), "container2", "dockerHost", new NodeResources(2, 4, 4, 1), NodeType.tenant); - container2 = container2.with(allocation(Optional.of("app2")).get()); + container2 = container2.with(allocation(Optional.of("app2"), container2).get()); nodeRepository.addDockerNodes(new LockedNodeList(List.of(container2), nodeRepository.lockAllocation())); Orchestrator orchestrator = mock(Orchestrator.class); @@ -171,10 +171,11 @@ public class MetricsReporterTest { .instanceName("default").build(); } - private Optional allocation(Optional tenant) { + private Optional allocation(Optional tenant, Node owner) { if (tenant.isPresent()) { Allocation allocation = new Allocation(app(tenant.get()), ClusterMembership.from("container/id1/0/3", new Version()), + owner.flavor().resources(), Generation.initial(), false); return Optional.of(allocation); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java index 0cfdf80a8a1..5a4c87aaf77 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java @@ -10,6 +10,7 @@ import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.NetworkPorts; 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.host.FlavorOverrides; @@ -70,6 +71,7 @@ public class SerializationTest { @Test public void testReservedNodeSerialization() { Node node = createNode(); + NodeResources requestedResources = new NodeResources(1.2, 3.4, 5.6, 7.8, NodeResources.DiskSpeed.any); clock.advance(Duration.ofMinutes(3)); assertEquals(0, node.history().events().size()); @@ -77,6 +79,7 @@ public class SerializationTest { ApplicationName.from("myApplication"), InstanceName.from("myInstance")), ClusterMembership.from("content/myId/0/0", Vtag.currentVersion), + requestedResources, clock.instant()); assertEquals(1, node.history().events().size()); node = node.withRestart(new Generation(1, 2)); @@ -99,23 +102,13 @@ public class SerializationTest { assertEquals(2, copy.status().failCount()); assertEquals(node.allocation().get().owner(), copy.allocation().get().owner()); assertEquals(node.allocation().get().membership(), copy.allocation().get().membership()); + assertEquals(node.allocation().get().requestedResources(), copy.allocation().get().requestedResources()); assertEquals(node.allocation().get().isRemovable(), copy.allocation().get().isRemovable()); assertEquals(1, copy.history().events().size()); assertEquals(clock.instant().truncatedTo(MILLIS), copy.history().event(History.Event.Type.reserved).get().at()); assertEquals(NodeType.tenant, copy.type()); } - @Test - public void testDefaultType() { - Node node = createNode().allocate(ApplicationId.from(TenantName.from("myTenant"), - ApplicationName.from("myApplication"), - InstanceName.from("myInstance")), - ClusterMembership.from("content/myId/0/0", Vtag.currentVersion), - clock.instant()); - Node copy = nodeSerializer.fromJson(Node.State.provisioned, nodeSerializer.toJson(node)); - assertEquals(NodeType.host, copy.type()); - } - @Test public void testRebootAndRestartAndTypeNoCurrentValuesSerialization() { String nodeData = @@ -127,11 +120,13 @@ public class SerializationTest { " \"history\" : [\n" + " {\n" + " \"type\" : \"provisioned\",\n" + - " \"at\" : 1444391401389\n" + + " \"at\" : 1444391401389,\n" + + " \"agent\" : \"system\"\n" + " },\n" + " {\n" + " \"type\" : \"reserved\",\n" + - " \"at\" : 1444391402611\n" + + " \"at\" : 1444391402611,\n" + + " \"agent\" : \"system\"\n" + " }\n" + " ],\n" + " \"instance\" : {\n" + @@ -172,6 +167,7 @@ public class SerializationTest { ApplicationName.from("myApplication"), InstanceName.from("myInstance")), ClusterMembership.from("content/myId/0/0", Vtag.currentVersion), + node.flavor().resources(), clock.instant()); assertEquals(1, node.history().events().size()); clock.advance(Duration.ofMinutes(2)); @@ -221,6 +217,7 @@ public class SerializationTest { ApplicationName.from("myApplication"), InstanceName.from("myInstance")), ClusterMembership.from("content/myId/0/0", Vtag.currentVersion), + node.flavor().resources(), clock.instant()); node = node.with(node.status().setFailCount(0)); @@ -405,6 +402,7 @@ public class SerializationTest { ApplicationName.from("myApplication"), InstanceName.from("myInstance")), ClusterMembership.from("content/myId/0/0", Vtag.currentVersion), + node.flavor().resources(), clock.instant()); assertTrue(node.allocation().isPresent()); node = node.with(node.allocation().get().withNetworkPorts(ports)); @@ -437,7 +435,13 @@ public class SerializationTest { } private Node createNode() { - return Node.create("myId", new IP.Config(Set.of("127.0.0.1"), Set.of()), "myHostname", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("default"), NodeType.host); + return Node.create("myId", + new IP.Config(Set.of("127.0.0.1"), Set.of()), + "myHostname", + Optional.empty(), + Optional.empty(), + nodeFlavors.getFlavorOrThrow("default"), + NodeType.tenant); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java index e3aefdd4512..9cd95e401a9 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java @@ -80,7 +80,7 @@ public class AllocationSimulator { private Node node(String hostname, Flavor flavor, Optional parent, Optional tenant) { var ipConfig = new IP.Config(Set.of("127.0.0.1"), parent.isPresent() ? Set.of() : getAdditionalIP()); return new Node("fake", ipConfig, hostname, parent, flavor, Status.initial(), - parent.isPresent() ? Node.State.ready : Node.State.active, allocation(tenant), History.empty(), + parent.isPresent() ? Node.State.ready : Node.State.active, allocation(tenant, flavor), History.empty(), parent.isPresent() ? NodeType.tenant : NodeType.host, new Reports(), Optional.empty()); } @@ -90,10 +90,11 @@ public class AllocationSimulator { return h; } - private Optional allocation(Optional tenant) { + private Optional allocation(Optional tenant, Flavor flavor) { if (tenant.isPresent()) { Allocation allocation = new Allocation(app(tenant.get()), ClusterMembership.from("container/id1/3", new Version()), + flavor.resources(), Generation.initial(), false); return Optional.of(allocation); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java index a3512c480d3..618df82e38b 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java @@ -426,7 +426,7 @@ public class DynamicDockerAllocationTest { Node node1a = Node.create("open1", new IP.Config(Set.of("127.0.233." + index), Set.of()), hostname, Optional.of(parentHostname), Optional.empty(), new Flavor(flavor), NodeType.tenant); ClusterMembership clusterMembership1 = ClusterMembership.from( clusterSpec.with(Optional.of(ClusterSpec.Group.from(0))), index); // Need to add group here so that group is serialized in node allocation - Node node1aAllocation = node1a.allocate(id, clusterMembership1, Instant.now()); + Node node1aAllocation = node1a.allocate(id, clusterMembership1, node1a.flavor().resources(), Instant.now()); tester.nodeRepository().addNodes(Collections.singletonList(node1aAllocation)); NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(tester.getCurator())); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImplTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImplTest.java index da77bebccda..48bd091011e 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImplTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImplTest.java @@ -140,7 +140,7 @@ public class InfraDeployerImplTest { Optional nodeWithAllocation = wantedVespaVersion.map(version -> { ClusterSpec clusterSpec = application.getClusterSpecWithVersion(version).with(Optional.of(ClusterSpec.Group.from(0))); ClusterMembership membership = ClusterMembership.from(clusterSpec, 1); - Allocation allocation = new Allocation(application.getApplicationId(), membership, Generation.initial(), false); + Allocation allocation = new Allocation(application.getApplicationId(), membership, node.flavor().resources(), Generation.initial(), false); return node.with(allocation); }); return nodeRepository.database().writeTo(state, nodeWithAllocation.orElse(node), Agent.system, Optional.empty()); 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 877038b8722..a8b534e02ef 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 @@ -319,6 +319,38 @@ public class ProvisioningTest { tester.activate(application, state.allHosts); } + @Test + public void requested_resources_info_is_retained() { + ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); + + tester.makeReadyNodes(13, defaultResources, NodeType.host, 1); + tester.prepareAndActivateInfraApplication(tester.makeApplicationId(), NodeType.host); + ApplicationId application = tester.makeApplicationId(); + + { + // Deploy with disk-speed any and make sure that information is retained + SystemState state = prepare(application, 0, 0, 3, 3, + defaultResources.anySpeed(), + tester); + assertEquals(6, state.allHosts.size()); + tester.activate(application, state.allHosts); + assertTrue(state.allHosts.stream().allMatch(host -> host.requestedResources().get().diskSpeed() == NodeResources.DiskSpeed.any)); + assertTrue(tester.nodeRepository().getNodes(application).stream().allMatch(node -> node.allocation().get().requestedResources().diskSpeed() == NodeResources.DiskSpeed.any)); + } + + { + // Deploy (with some additional nodes) with disk-speed fast and make sure *that* information is retained + // even though it does not lead to new nodes + SystemState state = prepare(application, 0, 0, 5, 3, + defaultResources, + tester); + assertEquals(8, state.allHosts.size()); + tester.activate(application, state.allHosts); + assertTrue(state.allHosts.stream().allMatch(host -> host.requestedResources().get().diskSpeed() == NodeResources.DiskSpeed.fast)); + assertTrue(tester.nodeRepository().getNodes(application).stream().allMatch(node -> node.allocation().get().requestedResources().diskSpeed() == NodeResources.DiskSpeed.fast)); + } + } + @Test public void deploy_specific_vespa_version() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.dev, RegionName.from("us-east"))).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 af635ada89d..242b0d8ca9e 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 @@ -148,12 +148,13 @@ public class ProvisioningTester { return hosts1; } - public void activate(ApplicationId application, Collection hosts) { + public Collection activate(ApplicationId application, Collection hosts) { NestedTransaction transaction = new NestedTransaction(); transaction.add(new CuratorTransaction(curator)); provisioner.activate(transaction, application, hosts); transaction.commit(); assertEquals(toHostNames(hosts), toHostNames(nodeRepository.getNodes(application, Node.State.active))); + return hosts; } public void prepareAndActivateInfraApplication(ApplicationId application, NodeType nodeType, Version version) { -- cgit v1.2.3