diff options
11 files changed, 257 insertions, 140 deletions
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index 4633e0ae870..1a1a25c973a 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.flags; import com.yahoo.component.Vtag; import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.flags.custom.HostCapacity; +import com.yahoo.vespa.flags.custom.SharedHost; import java.math.BigDecimal; import java.util.List; @@ -62,13 +63,6 @@ public class Flags { "Takes effect on next tick.", HOSTNAME); - public static final UnboundLongFlag THIN_POOL_GB = defineLongFlag( - "thin-pool-gb", -1, - "The size of the disk reserved for the thin pool with dynamic provisioning in AWS, in base-2 GB. " + - "If <0, the default is used (which may depend on the zone and node type).", - "Takes effect immediately (but used only during provisioning).", - NODE_TYPE); - public static final UnboundDoubleFlag CONTAINER_CPU_CAP = defineDoubleFlag( "container-cpu-cap", 0, "Hard limit on how many CPUs a container may use. This value is multiplied by CPU allocated to node, so " + @@ -96,6 +90,12 @@ public class Flags { "Otherwise it specifies the total (unallocated or not) capacity.", "Takes effect on next iteration of DynamicProvisioningMaintainer."); + public static final UnboundJacksonFlag<SharedHost> SHARED_HOST = defineJacksonFlag( + "shared-host", SharedHost.createDisabled(), SharedHost.class, + "Specifies whether shared hosts can be provisioned, and if so, the advertised " + + "node resources of the host, the maximum number of containers, etc.", + "Takes effect on next iteration of DynamicProvisioningMaintainer."); + public static final UnboundListFlag<String> INACTIVE_MAINTENANCE_JOBS = defineListFlag( "inactive-maintenance-jobs", List.of(), String.class, "The list of maintenance jobs that are inactive.", diff --git a/flags/src/main/java/com/yahoo/vespa/flags/custom/HostResources.java b/flags/src/main/java/com/yahoo/vespa/flags/custom/HostResources.java new file mode 100644 index 00000000000..c0b5d7a523c --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/custom/HostResources.java @@ -0,0 +1,132 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags.custom; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * The advertised node resources of a host, similar to config-provision's NodeResources, + * but with additional host-specific resources like the number of containers. + * + * @author freva + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class HostResources { + private static final Set<String> validDiskSpeeds = Set.of("slow", "fast"); + private static final Set<String> validStorageTypes = Set.of("remote", "local"); + + private final double vcpu; + + private final double memoryGb; + + private final double diskGb; + + private final double bandwidthGbps; + + private final String diskSpeed; + + private final String storageType; + + private final int containers; + + @JsonCreator + public HostResources(@JsonProperty("vcpu") Double vcpu, + @JsonProperty("memoryGb") Double memoryGb, + @JsonProperty("diskGb") Double diskGb, + @JsonProperty("bandwidthGbps") Double bandwidthGbps, + @JsonProperty("diskSpeed") String diskSpeed, + @JsonProperty("storageType") String storageType, + @JsonProperty("containers") Integer containers) { + this.vcpu = requirePositive("vcpu", vcpu); + this.memoryGb = requirePositive("memoryGb", memoryGb); + this.diskGb = requirePositive("diskGb", diskGb); + this.bandwidthGbps = requirePositive("bandwidthGbps", Optional.ofNullable(bandwidthGbps).orElse(0.3)); + this.diskSpeed = validateEnum("diskSpeed", validDiskSpeeds, diskSpeed); + this.storageType = validateEnum("storageType", validStorageTypes, storageType); + this.containers = requirePositive("containers", containers); + } + + @JsonProperty("vcpu") + public double vcpu() { return vcpu; } + + @JsonProperty("memoryGb") + public double memoryGb() { return memoryGb; } + + @JsonProperty("diskGb") + public double diskGb() { return diskGb; } + + @JsonProperty("bandwidthGbps") + public double bandwidthGbps() { return bandwidthGbps; } + + @JsonProperty("diskSpeed") + public String diskSpeed() { return diskSpeed; } + + @JsonProperty("storageType") + public String storageType() { return storageType; } + + @JsonProperty("containers") + public int containers() { return containers; } + + private static double requirePositive(String name, Double value) { + requireNonNull(name, value); + if (value <= 0) + throw new IllegalArgumentException("'" + name + "' must be positive, was " + value); + return value; + } + + private static int requirePositive(String name, Integer value) { + requireNonNull(name, value); + if (value <= 0) + throw new IllegalArgumentException("'" + name + "' must be positive, was " + value); + return value; + } + + private static String validateEnum(String name, Set<String> validValues, String value) { + requireNonNull(name, value); + if (!validValues.contains(value)) + throw new IllegalArgumentException("Invalid " + name + ", valid values are: " + + validValues + ", got: " + value); + return value; + } + + private static <T> T requireNonNull(String name, T value) { + return Objects.requireNonNull(value, () -> "'" + name + "' has not been specified"); + } + + @Override + public String toString() { + return "HostResources{" + + "vcpu=" + vcpu + + ", memoryGb=" + memoryGb + + ", diskGb=" + diskGb + + ", bandwidthGbps=" + bandwidthGbps + + ", diskSpeed='" + diskSpeed + '\'' + + ", storageType='" + storageType + '\'' + + ", containers=" + containers + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + HostResources resources = (HostResources) o; + return Double.compare(resources.vcpu, vcpu) == 0 && + Double.compare(resources.memoryGb, memoryGb) == 0 && + Double.compare(resources.diskGb, diskGb) == 0 && + Double.compare(resources.bandwidthGbps, bandwidthGbps) == 0 && + diskSpeed.equals(resources.diskSpeed) && + storageType.equals(resources.storageType) && + containers == resources.containers; + } + + @Override + public int hashCode() { + return Objects.hash(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, containers); + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/custom/NodeResources.java b/flags/src/main/java/com/yahoo/vespa/flags/custom/NodeResources.java deleted file mode 100644 index 38afc8a1481..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/custom/NodeResources.java +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.flags.custom; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -/** - * @author freva - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class NodeResources { - private static final Set<String> validDiskSpeeds = Set.of("any", "slow", "fast"); - - @JsonProperty("vcpu") - private final double vcpu; - - @JsonProperty("memoryGb") - private final double memoryGb; - - @JsonProperty("diskGb") - private final double diskGb; - - @JsonProperty("bandwidthGbps") - private final double bandwidthGbps; - - @JsonProperty("diskSpeed") - private final String diskSpeed; - - public NodeResources(@JsonProperty("vcpu") double vcpu, - @JsonProperty("memoryGb") double memoryGb, - @JsonProperty("diskGb") double diskGb, - @JsonProperty("bandwidthGbps") Double bandwidthGbps, - @JsonProperty("diskSpeed") String diskSpeed) { - this.vcpu = requirePositive("vcpu", vcpu); - this.memoryGb = requirePositive("memoryGb", memoryGb); - this.diskGb = requirePositive("diskGb", diskGb); - this.bandwidthGbps = requirePositive("bandwidthGbps", Optional.ofNullable(bandwidthGbps).orElse(0.3)); - this.diskSpeed = Optional.ofNullable(diskSpeed).orElse("fast"); - - if (!validDiskSpeeds.contains(this.diskSpeed)) - throw new IllegalArgumentException("Invalid diskSpeed, valid values are: " + validDiskSpeeds + ", got: " + diskSpeed); - } - - public double vcpu() { - return vcpu; - } - - public double memoryGb() { - return memoryGb; - } - - public double diskGb() { - return diskGb; - } - - public double bandwidthGbps() { - return bandwidthGbps; - } - - public String diskSpeed() { - return diskSpeed; - } - - private static double requirePositive(String name, double value) { - if (value <= 0) - throw new IllegalArgumentException("'" + name + "' must be positive, was " + value); - return value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - NodeResources resources = (NodeResources) o; - return Double.compare(resources.vcpu, vcpu) == 0 && - Double.compare(resources.memoryGb, memoryGb) == 0 && - Double.compare(resources.diskGb, diskGb) == 0 && - Double.compare(resources.bandwidthGbps, bandwidthGbps) == 0 && - diskSpeed.equals(resources.diskSpeed); - } - - @Override - public int hashCode() { - return Objects.hash(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed); - } -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java b/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java new file mode 100644 index 00000000000..e463159eb8f --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java @@ -0,0 +1,64 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags.custom; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.yahoo.vespa.flags.Flags; + +import java.util.List; +import java.util.Objects; + +/** + * Defines properties related to shared hosts, see {@link Flags#SHARED_HOST}. + * + * @author hakon + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(value = JsonInclude.Include.NON_NULL) +public class SharedHost { + private final List<HostResources> resources; + + public static SharedHost createDisabled() { + return new SharedHost(null); + } + + @JsonCreator + public SharedHost(@JsonProperty("resources") List<HostResources> resources) { + this.resources = resources == null ? List.of() : List.copyOf(resources); + } + + @JsonProperty("resources") + public List<HostResources> getResourcesOrNull() { + return resources.isEmpty() ? null : resources; + } + + @JsonIgnore + public List<HostResources> getHostResources() { + return resources; + } + + public boolean isEnabled() { + return resources.size() > 0; + } + + @Override + public String toString() { + return resources.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SharedHost that = (SharedHost) o; + return resources.equals(that.resources); + } + + @Override + public int hashCode() { + return Objects.hash(resources); + } +} diff --git a/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java b/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java index 0f486794c3a..28e84bcf3e5 100644 --- a/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java +++ b/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java @@ -4,6 +4,8 @@ package com.yahoo.vespa.flags; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.node.BooleanNode; +import com.yahoo.vespa.flags.custom.HostResources; +import com.yahoo.vespa.flags.custom.SharedHost; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -107,6 +109,15 @@ public class FlagsTest { List.of(instance)); } + @Test + public void testSharedHostFlag() { + SharedHost sharedHost = new SharedHost(List.of(new HostResources( + 4.0, 16.0, 50.0, null, + "fast", "local", + 10))); + testGeneric(Flags.SHARED_HOST, sharedHost); + } + private <T> void testGeneric(UnboundFlag<T, ?, ?> unboundFlag, T value) { FlagSource source = mock(FlagSource.class); Flag<T, ?> flag = unboundFlag.bindTo(source); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java index 0f70542f141..240e9f49328 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java @@ -26,6 +26,7 @@ import com.yahoo.yolean.Exceptions; import java.time.Duration; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -113,18 +114,32 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer { /** - * Provision the nodes necessary to satisfy given capacity. + * Provision hosts to ensure there is room to allocate spare nodes. * - * @return excess hosts that can safely be deprovisioned, if any + * @param advertisedSpareCapacity the advertised resources of the spare nodes + * @param nodes list of all nodes + * @return excess hosts that can safely be deprovisioned: An excess host 1. contains no nodes allocated + * to an application, and assuming the spare nodes have been allocated, and 2. is not parked + * without wantToDeprovision (which means an operator is looking at the node). */ - private List<Node> provision(List<NodeResources> capacity, NodeList nodes) { - List<Node> existingHosts = availableHostsOf(nodes); - if (nodeRepository().zone().getCloud().dynamicProvisioning()) { - existingHosts = removableHostsOf(existingHosts, nodes); - } else if (capacity.isEmpty()) { + private List<Node> provision(List<NodeResources> advertisedSpareCapacity, NodeList nodes) { + if (!nodeRepository().zone().getCloud().dynamicProvisioning()) { return List.of(); } - List<Node> excessHosts = new ArrayList<>(existingHosts); + + Map<String, Node> hostsByHostname = new HashMap<>(nodes.hosts().asList().stream() + .filter(host -> host.state() != Node.State.parked || host.status().wantToDeprovision()) + .collect(Collectors.toMap(Node::hostname, Function.identity()))); + + nodes.asList().stream() + .filter(node -> node.allocation().isPresent()) + .flatMap(node -> node.parentHostname().stream()) + .distinct() + .forEach(hostsByHostname::remove); + + List<Node> excessHosts = new ArrayList<>(hostsByHostname.values()); + + var capacity = new ArrayList<>(advertisedSpareCapacity); for (Iterator<NodeResources> it = capacity.iterator(); it.hasNext() && !excessHosts.isEmpty(); ) { NodeResources resources = it.next(); excessHosts.stream() @@ -138,12 +153,13 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer { it.remove(); }); } + // Pre-provisioning is best effort, do one host at a time capacity.forEach(resources -> { try { Version osVersion = nodeRepository().osVersions().targetFor(NodeType.host).orElse(Version.emptyVersion); List<Node> hosts = hostProvisioner.provisionHosts(nodeRepository().database().getProvisionIndexes(1), - resources, preprovisionAppId, osVersion) + resources, preprovisionAppId, osVersion, false) .stream() .map(ProvisionedHost::generateHost) .collect(Collectors.toList()); @@ -154,7 +170,8 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer { log.log(Level.WARNING, "Failed to pre-provision " + resources + ", will retry in " + interval(), e); } }); - return removableHostsOf(excessHosts, nodes); + + return excessHosts; } @@ -178,27 +195,4 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer { } } } - - /** Returns hosts that are considered available, i.e. not parked or flagged for deprovisioning */ - private static List<Node> availableHostsOf(NodeList nodes) { - return nodes.hosts() - .matching(host -> host.state() != Node.State.parked || host.status().wantToDeprovision()) - .asList(); - } - - /** Returns the subset of given hosts that have no containers and are thus removable */ - private static List<Node> removableHostsOf(List<Node> hosts, NodeList allNodes) { - Map<String, Node> hostsByHostname = hosts.stream() - .collect(Collectors.toMap(Node::hostname, - Function.identity())); - - allNodes.asList().stream() - .filter(node -> node.allocation().isPresent()) - .flatMap(node -> node.parentHostname().stream()) - .distinct() - .forEach(hostsByHostname::remove); - - return List.copyOf(hostsByHostname.values()); - } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java index 99557cb0908..f9960716162 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java @@ -88,7 +88,7 @@ public class GroupPreparer { .map(deficit -> hostProvisioner.get().provisionHosts(nodeRepository.database().getProvisionIndexes(deficit.getCount()), deficit.getFlavor(), application, - osVersion)) + osVersion, false)) .orElseGet(List::of); // At this point we have started provisioning of the hosts, the first priority is to make sure that diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostProvisioner.java index 816ef9459ba..5a7456ab997 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostProvisioner.java @@ -21,14 +21,16 @@ public interface HostProvisioner { * * @param provisionIndexes list of unique provision indexes which will be used to generate the node hostnames * on the form of <code>[prefix][index].[domain]</code> - * @param resources the resources needed per node + * @param resources the resources needed per node - the provisioned host may be significantly larger * @param applicationId id of the application that will own the provisioned host * @param osVersion the OS version to use. If this version does not exist, implementations may choose a suitable * fallback version. + * @param forceExclusive whether to force the provisioning of an exclusive host. * @return list of {@link ProvisionedHost} describing the provisioned nodes */ List<ProvisionedHost> provisionHosts(List<Integer> provisionIndexes, NodeResources resources, - ApplicationId applicationId, Version osVersion); + ApplicationId applicationId, Version osVersion, + boolean forceExclusive); /** * Continue provisioning of given list of Nodes. diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java index 999ceeccc58..03c42bf20ff 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java @@ -276,7 +276,9 @@ class AutoscalingTester { } @Override - public List<ProvisionedHost> provisionHosts(List<Integer> provisionIndexes, NodeResources resources, ApplicationId applicationId, Version osVersion) { + public List<ProvisionedHost> provisionHosts(List<Integer> provisionIndexes, NodeResources resources, + ApplicationId applicationId, Version osVersion, + boolean forceExclusive) { Flavor hostFlavor = hostFlavors.stream().filter(f -> matches(f, resources)).findAny() .orElseThrow(() -> new RuntimeException("No flavor matching " + resources + ". Flavors: " + hostFlavors)); 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 e75f94123c7..c6052829faf 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 @@ -325,7 +325,8 @@ public class DynamicProvisioningMaintainerTest { } @Override - public List<ProvisionedHost> provisionHosts(List<Integer> provisionIndexes, NodeResources resources, ApplicationId applicationId, Version osVersion) { + public List<ProvisionedHost> provisionHosts(List<Integer> provisionIndexes, NodeResources resources, + ApplicationId applicationId, Version osVersion, boolean forceExclusive) { Flavor hostFlavor = flavors.getFlavors().stream() .filter(f -> !f.isDocker()) .filter(f -> f.resources().compatibleWith(resources)) diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java index 41f13748a07..c678f8902b5 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java @@ -39,6 +39,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -68,7 +69,7 @@ public class DynamicDockerProvisionTest { mockHostProvisioner(hostProvisioner, tester.nodeRepository().flavors().getFlavorOrThrow("small")); List<HostSpec> hostSpec = tester.prepare(application1, clusterSpec("myContent.t1.a1"), 4, 1, flavor); - verify(hostProvisioner).provisionHosts(List.of(100, 101, 102, 103), flavor, application1, Version.emptyVersion); + verify(hostProvisioner).provisionHosts(List.of(100, 101, 102, 103), flavor, application1, Version.emptyVersion, false); // Total of 8 nodes should now be in node-repo, 4 hosts in state provisioned, and 4 reserved nodes assertEquals(8, tester.nodeRepository().list().size()); @@ -86,7 +87,7 @@ public class DynamicDockerProvisionTest { List<Integer> expectedProvisionIndexes = List.of(100, 101); mockHostProvisioner(hostProvisioner, tester.nodeRepository().flavors().getFlavorOrThrow("large")); tester.prepare(application, clusterSpec("myContent.t2.a2"), 2, 1, flavor); - verify(hostProvisioner).provisionHosts(expectedProvisionIndexes, flavor, application, Version.emptyVersion); + verify(hostProvisioner).provisionHosts(expectedProvisionIndexes, flavor, application, Version.emptyVersion, false); // Ready the provisioned hosts, add an IP addresses to pool and activate them for (Integer i : expectedProvisionIndexes) { @@ -101,7 +102,7 @@ public class DynamicDockerProvisionTest { mockHostProvisioner(hostProvisioner, tester.nodeRepository().flavors().getFlavorOrThrow("small")); tester.prepare(application, clusterSpec("another-id"), 2, 1, flavor); // Verify there was only 1 call to provision hosts (during the first prepare) - verify(hostProvisioner).provisionHosts(any(), any(), any(), any()); + verify(hostProvisioner).provisionHosts(any(), any(), any(), any(), anyBoolean()); // Node-repo should now consist of 2 active hosts with 2 reserved nodes on each assertEquals(6, tester.nodeRepository().list().size()); @@ -345,7 +346,7 @@ public class DynamicDockerProvisionTest { return provisionIndexes.stream() .map(i -> new ProvisionedHost("id-" + i, "host-" + i, hostFlavor, "host-" + i + "-1", nodeResources, Version.emptyVersion)) .collect(Collectors.toList()); - }).when(hostProvisioner).provisionHosts(any(), any(), any(), any()); + }).when(hostProvisioner).provisionHosts(any(), any(), any(), any(), anyBoolean()); } private static class MockHostProvisioner implements HostProvisioner { @@ -359,7 +360,7 @@ public class DynamicDockerProvisionTest { } @Override - public List<ProvisionedHost> provisionHosts(List<Integer> provisionIndexes, NodeResources resources, ApplicationId applicationId, Version osVersion) { + public List<ProvisionedHost> provisionHosts(List<Integer> provisionIndexes, NodeResources resources, ApplicationId applicationId, Version osVersion, boolean forceExclusive) { Optional<Flavor> hostFlavor = hostFlavors.stream().filter(f -> compatible(f, resources)).findFirst(); if (hostFlavor.isEmpty()) throw new OutOfCapacityException("No host flavor matches " + resources); |