diff options
10 files changed, 136 insertions, 62 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java index 4d0c61a4177..3c7468870e6 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java @@ -113,7 +113,7 @@ public class Autoscaler { } /** The duration of the window we need to consider to make a scaling decision */ - private Duration scalingWindow(ClusterSpec.Type clusterType) { + static Duration scalingWindow(ClusterSpec.Type clusterType) { if (clusterType.isContent()) return Duration.ofHours(12); // Ideally we should use observed redistribution time return Duration.ofHours(12); // TODO: Measure much more often to get this down to minutes. And, ideally we should take node startup time into account } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetrics.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetrics.java index b0d73833bc6..daed5a34873 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetrics.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetrics.java @@ -24,7 +24,7 @@ public interface NodeMetrics { private final String hostname; private final String name; - private long timestampSecond; + private final long timestampSecond; private final double value; public MetricValue(String hostname, String name, long timestampSecond, double value) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDb.java index 7fa81f0b78e..48bcd435806 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDb.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDb.java @@ -1,8 +1,11 @@ // Copyright Verizon Media. 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.ClusterSpec; +import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; + import java.time.Clock; -import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; @@ -11,7 +14,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.logging.Logger; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -22,21 +25,34 @@ import java.util.stream.Collectors; */ public class NodeMetricsDb { - private static final Duration dbWindow = Duration.ofHours(24); + private final NodeRepository nodeRepository; /** Measurements by key. Each list of measurements is sorted by increasing timestamp */ - private Map<MeasurementKey, List<Measurement>> db = new HashMap<>(); + private final Map<NodeMeasurementsKey, NodeMeasurements> db = new HashMap<>(); /** Lock all access for now since we modify lists inside a map */ private final Object lock = new Object(); - /** Add a measurement to this */ + public NodeMetricsDb(NodeRepository nodeRepository) { + this.nodeRepository = nodeRepository; + } + + /** Add measurements to this */ public void add(Collection<NodeMetrics.MetricValue> metricValues) { synchronized (lock) { for (var value : metricValues) { Resource resource = Resource.fromMetric(value.name()); - List<Measurement> measurements = db.computeIfAbsent(new MeasurementKey(value.hostname(), resource), - (__) -> new ArrayList<>()); + NodeMeasurementsKey key = new NodeMeasurementsKey(value.hostname(), resource); + NodeMeasurements measurements = db.get(key); + if (measurements == null) { // new node + Optional<Node> node = nodeRepository.getNode(value.hostname()); + if (node.isEmpty()) continue; + if (node.get().allocation().isEmpty()) continue; + measurements = new NodeMeasurements(value.hostname(), + resource, + node.get().allocation().get().membership().cluster().type()); + db.put(key, measurements); + } measurements.add(new Measurement(value.timestampSecond() * 1000, (float)resource.valueFromMetric(value.value()))); } @@ -46,17 +62,12 @@ public class NodeMetricsDb { /** Must be called intermittently (as long as add is called) to gc old measurements */ public void gc(Clock clock) { synchronized (lock) { - // TODO: We may need to do something more complicated to avoid spending too much memory to - // lower the measurement interval (see NodeRepositoryMaintenance) // Each measurement is Object + long + float = 16 + 8 + 4 = 28 bytes // 24 hours with 1k nodes and 3 resources and 1 measurement/sec is about 10Gb - long oldestTimestamp = clock.instant().minus(dbWindow).toEpochMilli(); - for (Iterator<List<Measurement>> i = db.values().iterator(); i.hasNext(); ) { - List<Measurement> measurements = i.next(); - while (!measurements.isEmpty() && measurements.get(0).timestamp < oldestTimestamp) - measurements.remove(0); - + for (Iterator<NodeMeasurements> i = db.values().iterator(); i.hasNext(); ) { + var measurements = i.next(); + measurements.removeOlderThan(clock.instant().minus(Autoscaler.scalingWindow(measurements.type)).toEpochMilli()); if (measurements.isEmpty()) i.remove(); } @@ -71,19 +82,22 @@ public class NodeMetricsDb { public class Window { private final long startTime; - private List<MeasurementKey> keys; + private final List<NodeMeasurementsKey> keys; private Window(Instant startTime, Resource resource, List<String> hostnames) { this.startTime = startTime.toEpochMilli(); - keys = hostnames.stream().map(hostname -> new MeasurementKey(hostname, resource)).collect(Collectors.toList()); + keys = hostnames.stream().map(hostname -> new NodeMeasurementsKey(hostname, resource)).collect(Collectors.toList()); } public int measurementCount() { synchronized (lock) { - return (int) keys.stream() - .flatMap(key -> db.getOrDefault(key, List.of()).stream()) - .filter(measurement -> measurement.timestamp >= startTime) - .count(); + int count = 0; + for (NodeMeasurementsKey key : keys) { + NodeMeasurements measurements = db.get(key); + if (measurements == null) continue; + count += measurements.after(startTime).size(); + } + return count; } } @@ -91,8 +105,8 @@ public class NodeMetricsDb { public int hostnames() { synchronized (lock) { int count = 0; - for (MeasurementKey key : keys) { - List<Measurement> measurements = db.get(key); + for (NodeMeasurementsKey key : keys) { + NodeMeasurements measurements = db.get(key); if (measurements == null || measurements.isEmpty()) continue; if (measurements.get(measurements.size() - 1).timestamp >= startTime) @@ -106,8 +120,8 @@ public class NodeMetricsDb { synchronized (lock) { double sum = 0; int count = 0; - for (MeasurementKey key : keys) { - List<Measurement> measurements = db.get(key); + for (NodeMeasurementsKey key : keys) { + NodeMeasurements measurements = db.get(key); if (measurements == null) continue; int index = measurements.size() - 1; @@ -124,12 +138,12 @@ public class NodeMetricsDb { } - private static class MeasurementKey { + private static class NodeMeasurementsKey { private final String hostname; private final Resource resource; - public MeasurementKey(String hostname, Resource resource) { + public NodeMeasurementsKey(String hostname, Resource resource) { this.hostname = hostname; this.resource = resource; } @@ -142,15 +156,49 @@ public class NodeMetricsDb { @Override public boolean equals(Object o) { if (this == o) return true; - if ( ! (o instanceof MeasurementKey)) return false; - MeasurementKey other = (MeasurementKey)o; + if ( ! (o instanceof NodeMeasurementsKey)) return false; + NodeMeasurementsKey other = (NodeMeasurementsKey)o; if ( ! this.hostname.equals(other.hostname)) return false; if ( ! this.resource.equals(other.resource)) return false; return true; } @Override - public String toString() { return "measurements of " + resource + " for " + hostname; } + public String toString() { return "key to measurements of " + resource + " for " + hostname; } + + } + + private static class NodeMeasurements { + + private final String hostname; + private final Resource resource; + private final ClusterSpec.Type type; + private final List<Measurement> measurements = new ArrayList<>(); + + public NodeMeasurements(String hostname, Resource resource, ClusterSpec.Type type) { + this.hostname = hostname; + this.resource = resource; + this.type = type; + } + + void removeOlderThan(long oldestTimestamp) { + while (!measurements.isEmpty() && measurements.get(0).timestamp < oldestTimestamp) + measurements.remove(0); + } + + boolean isEmpty() { return measurements.isEmpty(); } + + int size() { return measurements.size(); } + + Measurement get(int index) { return measurements.get(index); } + + void add(Measurement measurement) { measurements.add(measurement); } + + public List<Measurement> after(long oldestTimestamp) { + return measurements.stream() + .filter(measurement -> measurement.timestamp >= oldestTimestamp) + .collect(Collectors.toList()); + } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java index d7829df4152..511d2f988d0 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java @@ -34,9 +34,9 @@ public class NodeRepositoryTest { NodeRepositoryTester tester = new NodeRepositoryTester(); assertEquals(0, tester.nodeRepository().getNodes().size()); - tester.addNode("id1", "host1", "default", NodeType.host); - tester.addNode("id2", "host2", "default", NodeType.host); - tester.addNode("id3", "host3", "default", NodeType.host); + tester.addHost("id1", "host1", "default", NodeType.host); + tester.addHost("id2", "host2", "default", NodeType.host); + tester.addHost("id3", "host3", "default", NodeType.host); assertEquals(3, tester.nodeRepository().getNodes().size()); @@ -50,7 +50,7 @@ public class NodeRepositoryTest { @Test public void only_allow_docker_containers_remove_in_ready() { NodeRepositoryTester tester = new NodeRepositoryTester(); - tester.addNode("id1", "host1", "docker", NodeType.tenant); + tester.addHost("id1", "host1", "docker", NodeType.tenant); try { tester.nodeRepository().removeRecursively("host1"); // host1 is in state provisioned @@ -66,9 +66,9 @@ public class NodeRepositoryTest { @Test public void only_remove_tenant_docker_containers_for_new_allocations() { NodeRepositoryTester tester = new NodeRepositoryTester(); - tester.addNode("host1", "host1", "default", NodeType.tenant); - tester.addNode("host2", "host2", "docker", NodeType.tenant); - tester.addNode("cfg1", "cfg1", "docker", NodeType.config); + tester.addHost("host1", "host1", "default", NodeType.tenant); + tester.addHost("host2", "host2", "docker", NodeType.tenant); + tester.addHost("cfg1", "cfg1", "docker", NodeType.config); tester.setNodeState("host1", Node.State.dirty); tester.setNodeState("host2", Node.State.dirty); @@ -87,8 +87,8 @@ public class NodeRepositoryTest { @Test public void fail_readying_with_hard_fail() { NodeRepositoryTester tester = new NodeRepositoryTester(); - tester.addNode("host1", "host1", "default", NodeType.tenant); - tester.addNode("host2", "host2", "default", NodeType.tenant); + tester.addHost("host1", "host1", "default", NodeType.tenant); + tester.addHost("host2", "host2", "default", NodeType.tenant); tester.setNodeState("host1", Node.State.dirty); tester.setNodeState("host2", Node.State.dirty); @@ -113,8 +113,8 @@ public class NodeRepositoryTest { public void delete_host_only_after_all_the_children_have_been_deleted() { NodeRepositoryTester tester = new NodeRepositoryTester(); - tester.addNode("id1", "host1", "default", NodeType.host); - tester.addNode("id2", "host2", "default", NodeType.host); + tester.addHost("id1", "host1", "default", NodeType.host); + tester.addHost("id2", "host2", "default", NodeType.host); tester.addNode("node10", "node10", "host1", "docker", NodeType.tenant); tester.addNode("node11", "node11", "host1", "docker", NodeType.tenant); tester.addNode("node12", "node12", "host1", "docker", NodeType.tenant); @@ -151,7 +151,7 @@ public class NodeRepositoryTest { String cfghost1 = "cfghost1"; String cfg1 = "cfg1"; - tester.addNode("id1", cfghost1, "default", NodeType.confighost); + tester.addHost("id1", cfghost1, "default", NodeType.confighost); tester.addNode("id2", cfg1, cfghost1, "docker", NodeType.config); tester.setNodeState(cfghost1, Node.State.active); tester.setNodeState(cfg1, Node.State.active); @@ -177,8 +177,8 @@ public class NodeRepositoryTest { Instant testStart = tester.nodeRepository().clock().instant(); tester.clock().advance(Duration.ofSeconds(1)); - tester.addNode("id1", "host1", "default", NodeType.host); - tester.addNode("id2", "host2", "default", NodeType.host); + tester.addHost("id1", "host1", "default", NodeType.host); + tester.addHost("id2", "host2", "default", NodeType.host); assertFalse(tester.nodeRepository().getNode("host1").get().history().hasEventAfter(History.Event.Type.deprovisioned, testStart)); // Set host 1 properties and deprovision it @@ -196,7 +196,7 @@ public class NodeRepositoryTest { assertTrue(host1.history().hasEventAfter(History.Event.Type.deprovisioned, testStart)); // Adding it again preserves some information from the deprovisioned host and removes it - tester.addNode("id2", "host1", "default", NodeType.host); + tester.addHost("id2", "host1", "default", NodeType.host); host1 = tester.nodeRepository().getNode("host1").get(); assertEquals("This is the newly added node", "id2", host1.id()); assertFalse("The old 'host1' is removed", @@ -213,8 +213,8 @@ public class NodeRepositoryTest { public void dirty_host_only_if_we_can_dirty_children() { NodeRepositoryTester tester = new NodeRepositoryTester(); - tester.addNode("id1", "host1", "default", NodeType.host); - tester.addNode("id2", "host2", "default", NodeType.host); + tester.addHost("id1", "host1", "default", NodeType.host); + tester.addHost("id2", "host2", "default", NodeType.host); tester.addNode("node10", "node10", "host1", "docker", NodeType.tenant); tester.addNode("node11", "node11", "host1", "docker", NodeType.tenant); tester.addNode("node12", "node12", "host1", "docker", NodeType.tenant); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java index 9b0500303d8..92f7e7404a6 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeFlavors; +import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.Zone; import com.yahoo.config.provisioning.FlavorsConfig; @@ -54,7 +55,7 @@ public class NodeRepositoryTester { return nodeRepository.getNodes(type, inState); } - public Node addNode(String id, String hostname, String flavor, NodeType type) { + public Node addHost(String id, String hostname, String flavor, NodeType type) { Node node = nodeRepository.createNode(id, hostname, Optional.empty(), nodeFlavors.getFlavorOrThrow(flavor), type); return nodeRepository.addNodes(Collections.singletonList(node), Agent.system).get(0); @@ -66,6 +67,12 @@ public class NodeRepositoryTester { return nodeRepository.addNodes(Collections.singletonList(node), Agent.system).get(0); } + public Node addNode(String id, String hostname, String parentHostname, NodeResources resources) { + Node node = nodeRepository.createNode(id, hostname, Optional.of(parentHostname), + new Flavor(resources), NodeType.tenant); + return nodeRepository.addNodes(Collections.singletonList(node), Agent.system).get(0); + } + /** * Moves a node directly to the given state without doing any validation, useful * to create wanted test scenario without having to move every node through series 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 cb39e8fecce..0a127eacae1 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 @@ -67,7 +67,7 @@ class AutoscalingTester { .build(); hostResourcesCalculator = new MockHostResourcesCalculator(zone); - db = new NodeMetricsDb(); + db = new NodeMetricsDb(provisioningTester.nodeRepository()); autoscaler = new Autoscaler(db, nodeRepository()); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java index 0a3436bc379..27d54840586 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java @@ -1,7 +1,16 @@ // Copyright Verizon Media. 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.ApplicationId; +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterResources; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.NodeResources; +import com.yahoo.config.provision.NodeType; import com.yahoo.test.ManualClock; +import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.NodeRepositoryTester; +import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester; import org.junit.Test; import java.time.Duration; @@ -14,23 +23,33 @@ public class NodeMetricsDbTest { @Test public void testNodeMetricsDb() { - ManualClock clock = new ManualClock(); - NodeMetricsDb db = new NodeMetricsDb(); + ProvisioningTester tester = new ProvisioningTester.Builder().build(); + tester.makeReadyHosts(10, new NodeResources(10, 100, 1000, 10)).deployZoneApp(); + ApplicationId app1 = tester.makeApplicationId("app1"); + tester.deployZoneApp(); + var hosts = + tester.activate(app1, + ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("7.0").build(), + Capacity.from(new ClusterResources(2, 1, new NodeResources(1, 4, 10, 1)))); + String node0 = hosts.iterator().next().hostname(); + + ManualClock clock = tester.clock(); + NodeMetricsDb db = new NodeMetricsDb(tester.nodeRepository()); List<NodeMetrics.MetricValue> values = new ArrayList<>(); for (int i = 0; i < 40; i++) { - values.add(new NodeMetrics.MetricValue("host0", "cpu.util", clock.instant().getEpochSecond(), 0.9f)); - clock.advance(Duration.ofHours(1)); + values.add(new NodeMetrics.MetricValue(node0, "cpu.util", clock.instant().getEpochSecond(), 0.9f)); + clock.advance(Duration.ofMinutes(30)); } db.add(values); // Avoid off-by-one bug when the below windows starts exactly on one of the above getEpochSecond() timestamps. clock.advance(Duration.ofMinutes(1)); - assertEquals(29, db.getWindow(clock.instant().minus(Duration.ofHours(30)), Resource.cpu, List.of("host0")).measurementCount()); - assertEquals( 0, db.getWindow(clock.instant().minus(Duration.ofHours(30)), Resource.memory, List.of("host0")).measurementCount()); + assertEquals(29, db.getWindow(clock.instant().minus(Duration.ofHours(15)), Resource.cpu, List.of(node0)).measurementCount()); + assertEquals( 0, db.getWindow(clock.instant().minus(Duration.ofHours(15)), Resource.memory, List.of(node0)).measurementCount()); db.gc(clock); - assertEquals(23, db.getWindow(clock.instant().minus(Duration.ofHours(30)), Resource.cpu, List.of("host0")).measurementCount()); - assertEquals( 0, db.getWindow(clock.instant().minus(Duration.ofHours(30)), Resource.memory, List.of("host0")).measurementCount()); + assertEquals(23, db.getWindow(clock.instant().minus(Duration.ofHours(15)), Resource.cpu, List.of(node0)).measurementCount()); + assertEquals( 0, db.getWindow(clock.instant().minus(Duration.ofHours(15)), Resource.memory, List.of(node0)).measurementCount()); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java index c34db3210c1..88195cf0ed9 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java @@ -54,7 +54,7 @@ public class AutoscalingMaintainerTest { app2, new MockDeployer.ApplicationContext(app2, cluster2, Capacity.from(new ClusterResources(2, 1, highResources)))); MockDeployer deployer = new MockDeployer(tester.provisioner(), tester.clock(), apps); - NodeMetricsDb nodeMetricsDb = new NodeMetricsDb(); + NodeMetricsDb nodeMetricsDb = new NodeMetricsDb(tester.nodeRepository()); AutoscalingMaintainer maintainer = new AutoscalingMaintainer(tester.nodeRepository(), nodeMetricsDb, deployer, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java index be5c7f423c7..31a9fcb8999 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java @@ -44,7 +44,7 @@ public class ScalingSuggestionsMaintainerTest { ApplicationId app2 = tester.makeApplicationId("app2"); ClusterSpec cluster2 = tester.contentClusterSpec(); - NodeMetricsDb nodeMetricsDb = new NodeMetricsDb(); + NodeMetricsDb nodeMetricsDb = new NodeMetricsDb(tester.nodeRepository()); tester.makeReadyNodes(20, "flt", NodeType.host, 8); tester.deployZoneApp(); 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 e45ea09d372..edbbb3ed2e3 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 @@ -136,7 +136,7 @@ public class InfraDeployerImplTest { } private Node addNode(int id, Node.State state, Optional<Version> wantedVespaVersion) { - Node node = tester.addNode("id-" + id, "node-" + id, "default", nodeType); + Node node = tester.addHost("id-" + id, "node-" + id, "default", nodeType); Optional<Node> nodeWithAllocation = wantedVespaVersion.map(version -> { ClusterSpec clusterSpec = application.getClusterSpecWithVersion(version).with(Optional.of(ClusterSpec.Group.from(0))); ClusterMembership membership = ClusterMembership.from(clusterSpec, 1); |