aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java22
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java183
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java18
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java2
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java89
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java45
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java96
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java1
-rw-r--r--config-provisioning/src/main/resources/configdefinitions/flavors.def14
-rw-r--r--config-provisioning/src/test/java/com/yahoo/config/provision/NodeFlavorsTest.java52
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java27
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorConfigBuilder.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java18
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java16
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeFlavors.java7
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java1
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java11
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java10
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java10
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java16
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java96
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java231
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java14
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node9.json2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json2
35 files changed, 771 insertions, 244 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
index 448bd91c374..2439475e95c 100644
--- a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
+++ b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
@@ -33,8 +33,6 @@ import java.util.Set;
*/
public class InMemoryProvisioner implements HostProvisioner {
- private static final NodeResources defaultResources = new NodeResources(1, 3, 9);
-
/**
* If this is true an exception is thrown when all nodes are used.
* If false this will simply return nodes best effort, preferring to satisfy the
@@ -56,27 +54,27 @@ public class InMemoryProvisioner implements HostProvisioner {
/** Use this index as start index for all clusters */
private final int startIndexForClusters;
- /** Creates this with a number of nodes with resources 1, 3, 9 */
+ /** Creates this with a number of nodes of the flavor 'default' */
public InMemoryProvisioner(int nodeCount) {
- this(Collections.singletonMap(defaultResources,
+ this(Collections.singletonMap(NodeResources.fromLegacyName("default"),
createHostInstances(nodeCount)), true, 0);
}
/** Creates this with a set of host names of the flavor 'default' */
public InMemoryProvisioner(boolean failOnOutOfCapacity, String... hosts) {
- this(Collections.singletonMap(defaultResources,
+ this(Collections.singletonMap(NodeResources.fromLegacyName("default"),
toHostInstances(hosts)), failOnOutOfCapacity, 0);
}
/** Creates this with a set of hosts of the flavor 'default' */
public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, String ... retiredHostNames) {
- this(Collections.singletonMap(defaultResources,
+ this(Collections.singletonMap(NodeResources.fromLegacyName("default"),
hosts.asCollection()), failOnOutOfCapacity, 0, retiredHostNames);
}
/** Creates this with a set of hosts of the flavor 'default' */
public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, int startIndexForClusters, String ... retiredHostNames) {
- this(Collections.singletonMap(defaultResources,
+ this(Collections.singletonMap(NodeResources.fromLegacyName("default"),
hosts.asCollection()), failOnOutOfCapacity, startIndexForClusters, retiredHostNames);
}
@@ -112,9 +110,9 @@ public class InMemoryProvisioner implements HostProvisioner {
@Override
public HostSpec allocateHost(String alias) {
if (legacyMapping.containsKey(alias)) return legacyMapping.get(alias);
- List<Host> defaultHosts = freeNodes.get(defaultResources);
- if (defaultHosts.isEmpty()) throw new IllegalArgumentException("No more hosts with default resources available");
- Host newHost = freeNodes.removeValue(defaultResources, 0);
+ List<Host> defaultHosts = freeNodes.get(NodeResources.fromLegacyName("default"));
+ if (defaultHosts.isEmpty()) throw new IllegalArgumentException("No more hosts of default flavor available");
+ Host newHost = freeNodes.removeValue(NodeResources.fromLegacyName("default"), 0);
HostSpec hostSpec = new HostSpec(newHost.hostname(), newHost.aliases(), newHost.flavor(), Optional.empty(), newHost.version());
legacyMapping.put(alias, hostSpec);
return hostSpec;
@@ -130,11 +128,11 @@ public class InMemoryProvisioner implements HostProvisioner {
int capacity = failOnOutOfCapacity || requestedCapacity.isRequired()
? requestedCapacity.nodeCount()
- : Math.min(requestedCapacity.nodeCount(), freeNodes.get(defaultResources).size() + totalAllocatedTo(cluster));
+ : Math.min(requestedCapacity.nodeCount(), freeNodes.get(NodeResources.fromLegacyName("default")).size() + totalAllocatedTo(cluster));
if (groups > capacity)
groups = capacity;
- NodeResources nodeResources = requestedCapacity.nodeResources().orElse(defaultResources);
+ NodeResources nodeResources = requestedCapacity.nodeResources().orElse(NodeResources.fromLegacyName("default"));
List<HostSpec> allocation = new ArrayList<>();
if (groups == 1) {
diff --git a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java
index eae9330a9dd..edc7f8691b2 100644
--- a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java
+++ b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java
@@ -414,16 +414,16 @@ public class ModelProvisioningTest {
ClusterControllerContainerCluster clusterControllers = cluster.getClusterControllers();
assertEquals(3, clusterControllers.getContainers().size());
assertEquals("bar-controllers", clusterControllers.getName());
- assertEquals("node-1-3-9-54", clusterControllers.getContainers().get(0).getHostName());
- assertEquals("node-1-3-9-51", clusterControllers.getContainers().get(1).getHostName());
- assertEquals("node-1-3-9-48", clusterControllers.getContainers().get(2).getHostName());
+ assertEquals("default54", clusterControllers.getContainers().get(0).getHostName());
+ assertEquals("default51", clusterControllers.getContainers().get(1).getHostName());
+ assertEquals("default48", clusterControllers.getContainers().get(2).getHostName());
assertEquals(0, cluster.getRootGroup().getNodes().size());
assertEquals(9, cluster.getRootGroup().getSubgroups().size());
assertThat(cluster.getRootGroup().getSubgroups().get(0).getIndex(), is("0"));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().size(), is(3));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getDistributionKey(), is(0));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getConfigId(), is("bar/storage/0"));
- assertEquals("node-1-3-9-54", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getHostName());
+ assertEquals("default54", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getHostName());
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(1).getDistributionKey(), is(1));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(1).getConfigId(), is("bar/storage/1"));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(2).getDistributionKey(), is(2));
@@ -432,13 +432,13 @@ public class ModelProvisioningTest {
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().size(), is(3));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getDistributionKey(), is(3));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getConfigId(), is("bar/storage/3"));
- assertEquals("node-1-3-9-51", cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getHostName());
+ assertEquals("default51", cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getHostName());
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(1).getDistributionKey(), is(4));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(1).getConfigId(), is("bar/storage/4"));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(2).getDistributionKey(), is(5));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(2).getConfigId(), is("bar/storage/5"));
// ...
- assertEquals("node-1-3-9-48", cluster.getRootGroup().getSubgroups().get(2).getNodes().get(0).getHostName());
+ assertEquals("default48", cluster.getRootGroup().getSubgroups().get(2).getNodes().get(0).getHostName());
// ...
assertThat(cluster.getRootGroup().getSubgroups().get(8).getIndex(), is("8"));
assertThat(cluster.getRootGroup().getSubgroups().get(8).getNodes().size(), is(3));
@@ -453,23 +453,23 @@ public class ModelProvisioningTest {
clusterControllers = cluster.getClusterControllers();
assertEquals(3, clusterControllers.getContainers().size());
assertEquals("baz-controllers", clusterControllers.getName());
- assertEquals("node-1-3-9-27", clusterControllers.getContainers().get(0).getHostName());
- assertEquals("node-1-3-9-26", clusterControllers.getContainers().get(1).getHostName());
- assertEquals("node-1-3-9-25", clusterControllers.getContainers().get(2).getHostName());
+ assertEquals("default27", clusterControllers.getContainers().get(0).getHostName());
+ assertEquals("default26", clusterControllers.getContainers().get(1).getHostName());
+ assertEquals("default25", clusterControllers.getContainers().get(2).getHostName());
assertEquals(0, cluster.getRootGroup().getNodes().size());
assertEquals(27, cluster.getRootGroup().getSubgroups().size());
assertThat(cluster.getRootGroup().getSubgroups().get(0).getIndex(), is("0"));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().size(), is(1));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getDistributionKey(), is(0));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getConfigId(), is("baz/storage/0"));
- assertEquals("node-1-3-9-27", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getHostName());
+ assertEquals("default27", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getHostName());
assertThat(cluster.getRootGroup().getSubgroups().get(1).getIndex(), is("1"));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().size(), is(1));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getDistributionKey(), is(1));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getConfigId(), is("baz/storage/1"));
- assertEquals("node-1-3-9-26", cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getHostName());
+ assertEquals("default26", cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getHostName());
// ...
- assertEquals("node-1-3-9-25", cluster.getRootGroup().getSubgroups().get(2).getNodes().get(0).getHostName());
+ assertEquals("default25", cluster.getRootGroup().getSubgroups().get(2).getNodes().get(0).getHostName());
// ...
assertThat(cluster.getRootGroup().getSubgroups().get(26).getIndex(), is("26"));
assertThat(cluster.getRootGroup().getSubgroups().get(26).getNodes().size(), is(1));
@@ -506,9 +506,9 @@ public class ModelProvisioningTest {
ClusterControllerContainerCluster clusterControllers = cluster.getClusterControllers();
assertEquals(3, clusterControllers.getContainers().size());
assertEquals("bar-controllers", clusterControllers.getName());
- assertEquals("node-1-3-9-08", clusterControllers.getContainers().get(0).getHostName());
- assertEquals("node-1-3-9-07", clusterControllers.getContainers().get(1).getHostName());
- assertEquals("node-1-3-9-06", clusterControllers.getContainers().get(2).getHostName());
+ assertEquals("default08", clusterControllers.getContainers().get(0).getHostName());
+ assertEquals("default07", clusterControllers.getContainers().get(1).getHostName());
+ assertEquals("default06", clusterControllers.getContainers().get(2).getHostName());
assertEquals(0, cluster.getRootGroup().getNodes().size());
assertEquals(8, cluster.getRootGroup().getSubgroups().size());
assertEquals(8, cluster.distributionBits());
@@ -517,19 +517,19 @@ public class ModelProvisioningTest {
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().size(), is(1));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getDistributionKey(), is(0));
assertThat(cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getConfigId(), is("bar/storage/0"));
- assertEquals("node-1-3-9-08", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getHostName());
+ assertEquals("default08", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getHostName());
// second group
assertThat(cluster.getRootGroup().getSubgroups().get(1).getIndex(), is("1"));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().size(), is(1));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getDistributionKey(), is(1));
assertThat(cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getConfigId(), is("bar/storage/1"));
- assertEquals("node-1-3-9-07", cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getHostName());
+ assertEquals("default07", cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getHostName());
// ... last group
assertThat(cluster.getRootGroup().getSubgroups().get(7).getIndex(), is("7"));
assertThat(cluster.getRootGroup().getSubgroups().get(7).getNodes().size(), is(1));
assertThat(cluster.getRootGroup().getSubgroups().get(7).getNodes().get(0).getDistributionKey(), is(7));
assertThat(cluster.getRootGroup().getSubgroups().get(7).getNodes().get(0).getConfigId(), is("bar/storage/7"));
- assertEquals("node-1-3-9-01", cluster.getRootGroup().getSubgroups().get(7).getNodes().get(0).getHostName());
+ assertEquals("default01", cluster.getRootGroup().getSubgroups().get(7).getNodes().get(0).getHostName());
}
@Test
@@ -563,15 +563,15 @@ public class ModelProvisioningTest {
assertEquals( 8, cluster.distributionBits());
assertEquals("We get the closest odd number", 5, clusterControllers.getContainers().size());
assertEquals("bar-controllers", clusterControllers.getName());
- assertEquals("node-1-3-9-09", clusterControllers.getContainers().get(0).getHostName());
- assertEquals("node-1-3-9-08", clusterControllers.getContainers().get(1).getHostName());
- assertEquals("node-1-3-9-06", clusterControllers.getContainers().get(2).getHostName());
- assertEquals("node-1-3-9-05", clusterControllers.getContainers().get(3).getHostName());
- assertEquals("node-1-3-9-03", clusterControllers.getContainers().get(4).getHostName());
- assertEquals("node-1-3-9-09", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getHostName());
- assertEquals("node-1-3-9-08", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(1).getHostName());
- assertEquals("node-1-3-9-06", cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getHostName());
- assertEquals("node-1-3-9-03", cluster.getRootGroup().getSubgroups().get(2).getNodes().get(0).getHostName());
+ assertEquals("default09", clusterControllers.getContainers().get(0).getHostName());
+ assertEquals("default08", clusterControllers.getContainers().get(1).getHostName());
+ assertEquals("default06", clusterControllers.getContainers().get(2).getHostName());
+ assertEquals("default05", clusterControllers.getContainers().get(3).getHostName());
+ assertEquals("default03", clusterControllers.getContainers().get(4).getHostName());
+ assertEquals("default09", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(0).getHostName());
+ assertEquals("default08", cluster.getRootGroup().getSubgroups().get(0).getNodes().get(1).getHostName());
+ assertEquals("default06", cluster.getRootGroup().getSubgroups().get(1).getNodes().get(0).getHostName());
+ assertEquals("default03", cluster.getRootGroup().getSubgroups().get(2).getNodes().get(0).getHostName());
}
@Test
@@ -603,9 +603,9 @@ public class ModelProvisioningTest {
ClusterControllerContainerCluster clusterControllers = cluster.getClusterControllers();
assertEquals("We get the closest odd number", 3, clusterControllers.getContainers().size());
assertEquals("bar-controllers", clusterControllers.getName());
- assertEquals("node-1-3-9-08", clusterControllers.getContainers().get(0).getHostName());
- assertEquals("node-1-3-9-06", clusterControllers.getContainers().get(1).getHostName());
- assertEquals("node-1-3-9-04", clusterControllers.getContainers().get(2).getHostName());
+ assertEquals("default08", clusterControllers.getContainers().get(0).getHostName());
+ assertEquals("default06", clusterControllers.getContainers().get(1).getHostName());
+ assertEquals("default04", clusterControllers.getContainers().get(2).getHostName());
}
@Test
@@ -662,7 +662,7 @@ public class ModelProvisioningTest {
int numberOfHosts = 19;
VespaModelTester tester = new VespaModelTester();
tester.addHosts(numberOfHosts);
- VespaModel model = tester.createModel(services, true, "node-1-3-9-09", "node-1-3-9-06", "node-1-3-9-03");
+ VespaModel model = tester.createModel(services, true, "default09", "default06", "default03");
assertThat(model.getRoot().getHostSystem().getHosts().size(), is(numberOfHosts));
// Check content clusters
@@ -670,9 +670,9 @@ public class ModelProvisioningTest {
ClusterControllerContainerCluster clusterControllers = cluster.getClusterControllers();
assertEquals(3, clusterControllers.getContainers().size());
assertEquals("bar-controllers", clusterControllers.getName());
- assertEquals("Skipping retired default09", "node-1-3-9-08", clusterControllers.getContainers().get(0).getHostName());
- assertEquals("Skipping retired default06", "node-1-3-9-05", clusterControllers.getContainers().get(1).getHostName());
- assertEquals("Skipping retired default03", "node-1-3-9-02", clusterControllers.getContainers().get(2).getHostName());
+ assertEquals("Skipping retired default09", "default08", clusterControllers.getContainers().get(0).getHostName());
+ assertEquals("Skipping retired default06", "default05", clusterControllers.getContainers().get(1).getHostName());
+ assertEquals("Skipping retired default03", "default02", clusterControllers.getContainers().get(2).getHostName());
}
@Test
@@ -689,15 +689,15 @@ public class ModelProvisioningTest {
int numberOfHosts = 10;
VespaModelTester tester = new VespaModelTester();
tester.addHosts(numberOfHosts);
- VespaModel model = tester.createModel(services, true, "node-1-3-9-09");
+ VespaModel model = tester.createModel(services, true, "default09");
assertThat(model.getRoot().getHostSystem().getHosts().size(), is(numberOfHosts));
// Check slobroks clusters
assertEquals("Includes retired node", 1+3, model.getAdmin().getSlobroks().size());
- assertEquals("node-1-3-9-01", model.getAdmin().getSlobroks().get(0).getHostName());
- assertEquals("node-1-3-9-02", model.getAdmin().getSlobroks().get(1).getHostName());
- assertEquals("node-1-3-9-10", model.getAdmin().getSlobroks().get(2).getHostName());
- assertEquals("Included in addition because it is retired", "node-1-3-9-09", model.getAdmin().getSlobroks().get(3).getHostName());
+ assertEquals("default01", model.getAdmin().getSlobroks().get(0).getHostName());
+ assertEquals("default02", model.getAdmin().getSlobroks().get(1).getHostName());
+ assertEquals("default10", model.getAdmin().getSlobroks().get(2).getHostName());
+ assertEquals("Included in addition because it is retired", "default09", model.getAdmin().getSlobroks().get(3).getHostName());
}
@Test
@@ -714,16 +714,16 @@ public class ModelProvisioningTest {
int numberOfHosts = 10;
VespaModelTester tester = new VespaModelTester();
tester.addHosts(numberOfHosts);
- VespaModel model = tester.createModel(services, true, "node-1-3-9-09", "node-1-3-9-08");
+ VespaModel model = tester.createModel(services, true, "default09", "default08");
assertEquals(numberOfHosts, model.getRoot().getHostSystem().getHosts().size());
// Check slobroks clusters
assertEquals("Includes retired node", 3+2, model.getAdmin().getSlobroks().size());
- assertEquals("node-1-3-9-01", model.getAdmin().getSlobroks().get(0).getHostName());
- assertEquals("node-1-3-9-02", model.getAdmin().getSlobroks().get(1).getHostName());
- assertEquals("node-1-3-9-10", model.getAdmin().getSlobroks().get(2).getHostName());
- assertEquals("Included in addition because it is retired", "node-1-3-9-08", model.getAdmin().getSlobroks().get(3).getHostName());
- assertEquals("Included in addition because it is retired", "node-1-3-9-09", model.getAdmin().getSlobroks().get(4).getHostName());
+ assertEquals("default01", model.getAdmin().getSlobroks().get(0).getHostName());
+ assertEquals("default02", model.getAdmin().getSlobroks().get(1).getHostName());
+ assertEquals("default10", model.getAdmin().getSlobroks().get(2).getHostName());
+ assertEquals("Included in addition because it is retired", "default08", model.getAdmin().getSlobroks().get(3).getHostName());
+ assertEquals("Included in addition because it is retired", "default09", model.getAdmin().getSlobroks().get(4).getHostName());
}
@Test
@@ -743,19 +743,29 @@ public class ModelProvisioningTest {
int numberOfHosts = 13;
VespaModelTester tester = new VespaModelTester();
tester.addHosts(numberOfHosts);
- VespaModel model = tester.createModel(services, true, "node-1-3-9-12", "node-1-3-9-03", "node-1-3-9-02");
+ VespaModel model = tester.createModel(services, true, "default12", "default03", "default02");
assertThat(model.getRoot().getHostSystem().getHosts().size(), is(numberOfHosts));
// Check slobroks clusters
// ... from cluster default
assertEquals("Includes retired node", 3+3, model.getAdmin().getSlobroks().size());
- assertEquals("node-1-3-9-04", model.getAdmin().getSlobroks().get(0).getHostName());
- assertEquals("node-1-3-9-13", model.getAdmin().getSlobroks().get(1).getHostName());
- assertEquals("Included in addition because it is retired", "node-1-3-9-12", model.getAdmin().getSlobroks().get(2).getHostName());
+ assertEquals("default04", model.getAdmin().getSlobroks().get(0).getHostName());
+ assertEquals("default13", model.getAdmin().getSlobroks().get(1).getHostName());
+ assertEquals("Included in addition because it is retired", "default12", model.getAdmin().getSlobroks().get(2).getHostName());
// ... from cluster bar
- assertEquals("node-1-3-9-01", model.getAdmin().getSlobroks().get(3).getHostName());
- assertEquals("Included in addition because it is retired", "node-1-3-9-02", model.getAdmin().getSlobroks().get(4).getHostName());
- assertEquals("Included in addition because it is retired", "node-1-3-9-03", model.getAdmin().getSlobroks().get(5).getHostName());
+ assertEquals("default01", model.getAdmin().getSlobroks().get(3).getHostName());
+ assertEquals("Included in addition because it is retired", "default02", model.getAdmin().getSlobroks().get(4).getHostName());
+ assertEquals("Included in addition because it is retired", "default03", model.getAdmin().getSlobroks().get(5).getHostName());
+ }
+
+ private Set<String> getClusterHostnames(VespaModel model, String clusterId) {
+ return model.getHosts().stream()
+ .filter(host -> host.getServices().stream()
+ .anyMatch(serviceInfo -> Objects.equals(
+ serviceInfo.getProperty("clustername"),
+ Optional.of(clusterId))))
+ .map(HostInfo::getHostname)
+ .collect(Collectors.toSet());
}
@Test
@@ -886,10 +896,10 @@ public class ModelProvisioningTest {
ClusterControllerContainerCluster clusterControllers = cluster.getClusterControllers();
assertEquals(4, clusterControllers.getContainers().size());
assertEquals("bar-controllers", clusterControllers.getName());
- assertEquals("node-1-3-9-04", clusterControllers.getContainers().get(0).getHostName());
- assertEquals("node-1-3-9-03", clusterControllers.getContainers().get(1).getHostName());
- assertEquals("node-1-3-9-02", clusterControllers.getContainers().get(2).getHostName());
- assertEquals("node-1-3-9-01", clusterControllers.getContainers().get(3).getHostName());
+ assertEquals("default04", clusterControllers.getContainers().get(0).getHostName());
+ assertEquals("default03", clusterControllers.getContainers().get(1).getHostName());
+ assertEquals("default02", clusterControllers.getContainers().get(2).getHostName());
+ assertEquals("default01", clusterControllers.getContainers().get(3).getHostName());
}
@Test
@@ -1055,7 +1065,7 @@ public class ModelProvisioningTest {
ClusterControllerContainerCluster clusterControllers = cluster.getClusterControllers();
assertEquals(1, clusterControllers.getContainers().size());
assertEquals("bar-controllers", clusterControllers.getName());
- assertEquals("node-1-3-9-01", clusterControllers.getContainers().get(0).getHostName());
+ assertEquals("default01", clusterControllers.getContainers().get(0).getHostName());
assertEquals(1, cluster.redundancy().effectiveInitialRedundancy()); // Reduced from 3*3
assertEquals(1, cluster.redundancy().effectiveFinalRedundancy()); // Reduced from 3*4
assertEquals(1, cluster.redundancy().effectiveReadyCopies()); // Reduced from 3*3
@@ -1127,6 +1137,47 @@ public class ModelProvisioningTest {
}
@Test
+ public void testRequestingSpecificFlavors() {
+ String services =
+ "<?xml version='1.0' encoding='utf-8' ?>\n" +
+ "<services>" +
+ " <admin version='4.0'>" +
+ " <logservers><nodes count='1' dedicated='true' flavor='logserver-flavor'/></logservers>" +
+ " <slobroks><nodes count='2' dedicated='true' flavor='slobrok-flavor'/></slobroks>" +
+ " </admin>" +
+ " <container version='1.0' id='container'>" +
+ " <nodes count='4' flavor='container-flavor'/>" +
+ " </container>" +
+ " <content version='1.0' id='foo'>" +
+ " <documents>" +
+ " <document type='type1' mode='index'/>" +
+ " </documents>" +
+ " <controllers><nodes count='2' dedicated='true' flavor='controller-foo-flavor'/></controllers>" +
+ " <nodes count='5' flavor='content-foo-flavor'/>" +
+ " </content>" +
+ " <content version='1.0' id='bar'>" +
+ " <documents>" +
+ " <document type='type1' mode='index'/>" +
+ " </documents>" +
+ " <controllers><nodes count='3' dedicated='true' flavor='controller-bar-flavor'/></controllers>" +
+ " <nodes count='6' flavor='content-bar-flavor'/>" +
+ " </content>" +
+ "</services>";
+
+ int totalHosts = 23;
+ VespaModelTester tester = new VespaModelTester();
+ tester.addHosts("logserver-flavor", 1);
+ tester.addHosts("slobrok-flavor", 2);
+ tester.addHosts("container-flavor", 4);
+ tester.addHosts("controller-foo-flavor", 2);
+ tester.addHosts("content-foo-flavor", 5);
+ tester.addHosts("controller-bar-flavor", 3);
+ tester.addHosts("content-bar-flavor", 6);
+ VespaModel model = tester.createModel(services, true, 0); // fails unless the right flavors+counts are requested
+ assertEquals(totalHosts, model.getRoot().getHostSystem().getHosts().size());
+ }
+
+ @Test
public void testRequestingSpecificNodeResources() {
String services =
"<?xml version='1.0' encoding='utf-8' ?>" +
@@ -1705,21 +1756,19 @@ public class ModelProvisioningTest {
}
@Test
- public void require_that_proton_config_is_tuned_based_on_node_resources() {
+ public void require_that_proton_config_is_tuned_based_on_node_flavor() {
String services = joinLines("<?xml version='1.0' encoding='utf-8' ?>",
"<services>",
" <content version='1.0' id='test'>",
" <documents>",
" <document type='type1' mode='index'/>",
" </documents>",
- " <nodes count='2'>",
- " <resources vcpu='1' memory='3Gb' disk='9Gb' disk-speed='slow'/>",
- " </nodes>",
+ " <nodes count='2' flavor='content-test-flavor'/>",
" </content>",
"</services>");
VespaModelTester tester = new VespaModelTester();
- tester.addHosts(new NodeResources(1, 3, 9, NodeResources.DiskSpeed.slow), 2);
+ tester.addHosts(createFlavorFromDiskSetting("content-test-flavor", false), 2);
VespaModel model = tester.createModel(services, true, 0);
ContentSearchCluster cluster = model.getContentClusters().get("test").getSearch();
assertEquals(2, cluster.getSearchNodes().size());
@@ -1741,7 +1790,7 @@ public class ModelProvisioningTest {
}
@Test
- public void require_that_config_override_and_explicit_proton_tuning_and_resource_limits_have_precedence_over_default_node_resource_tuning() {
+ public void require_that_config_override_and_explicit_proton_tuning_and_resource_limits_have_precedence_over_default_node_flavor_tuning() {
String services = joinLines("<?xml version='1.0' encoding='utf-8' ?>",
"<services>",
" <content version='1.0' id='test'>",
@@ -1751,9 +1800,7 @@ public class ModelProvisioningTest {
" <documents>",
" <document type='type1' mode='index'/>",
" </documents>",
- " <nodes count='1'>",
- " <resources vcpu='1' memory='128Gb' disk='100Gb'/>",
- " </nodes>",
+ " <nodes count='1' flavor='content-test-flavor'/>",
" <engine>",
" <proton>",
" <resource-limits>",
@@ -1776,8 +1823,8 @@ public class ModelProvisioningTest {
"</services>");
VespaModelTester tester = new VespaModelTester();
- tester.addHosts(new NodeResources(1, 3, 9), 1);
- tester.addHosts(new NodeResources(1, 128, 100), 1);
+ tester.addHosts("default", 1);
+ tester.addHosts(createFlavorFromMemoryAndDisk("content-test-flavor", 128, 100), 1);
VespaModel model = tester.createModel(services, true, 0);
ContentSearchCluster cluster = model.getContentClusters().get("test").getSearch();
ProtonConfig cfg = getProtonConfig(model, cluster.getSearchNodes().get(0).getConfigId());
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java
index 3f82a668fd0..2e5acb9025d 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java
@@ -57,20 +57,20 @@ public class VespaModelTester {
this.configModelRegistry = configModelRegistry;
}
- /** Adds some nodes with resources 1, 3, 9 */
- public Hosts addHosts(int count) { return addHosts(new NodeResources(1, 3, 9), count); }
+ /** Adds some hosts of the 'default' flavor to this system */
+ public Hosts addHosts(int count) { return addHosts("default", count); }
/** Adds some hosts to this system */
public Hosts addHosts(String flavor, int count) {
return addHosts(Optional.empty(), NodeResources.fromLegacyName(flavor), count);
}
- public Hosts addHosts(Flavor flavor, int count) {
- return addHosts(Optional.of(flavor), NodeResources.fromLegacyName(flavor.name()), count);
+ public void addHosts(Flavor flavor, int count) {
+ addHosts(Optional.of(flavor), NodeResources.fromLegacyName(flavor.name()), count);
}
- public Hosts addHosts(NodeResources resources, int count) {
- return addHosts(Optional.of(new Flavor(resources)), resources, count);
+ public void addHosts(NodeResources resources, int count) {
+ addHosts(Optional.of(new Flavor(resources)), resources, count);
}
private Hosts addHosts(Optional<Flavor> flavor, NodeResources resources, int count) {
@@ -80,10 +80,8 @@ public class VespaModelTester {
// Let host names sort in the opposite order of the order the hosts are added
// This allows us to test index vs. name order selection when subsets of hosts are selected from a cluster
// (for e.g cluster controllers and slobrok nodes)
- String hostname = String.format("%s-%02d",
- "node" + "-" + Math.round(resources.vcpu()) +
- "-" + Math.round(resources.memoryGb()) +
- "-" + Math.round(resources.diskGb()),
+ String hostname = String.format("%s%02d",
+ resources.allocateByLegacyName() ? resources.legacyName().get() : resources.toString(),
count - i);
hosts.add(new Host(hostname, ImmutableList.of(), flavor));
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java
index dfa9ab7f6b8..f8535eda44f 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java
@@ -41,7 +41,7 @@ public final class Capacity {
@Deprecated
public Optional<String> flavor() {
if (nodeResources().isEmpty()) return Optional.empty();
- return nodeResources.map(n -> n.toString());
+ return nodeResources.get().legacyName();
}
/** Returns the resources requested for each node, or empty to leave this decision to provisioning */
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java
index 2bc70efbc15..48c84b8ecb7 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java
@@ -3,14 +3,15 @@ package com.yahoo.config.provision;
import com.yahoo.config.provisioning.FlavorsConfig;
-import java.util.Collections;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A host or node flavor.
* *Host* flavors come from a configured set which corresponds to the actual flavors available in a zone.
- * *Node* flavors are simply a wrapper of a NodeResources object.
+ * *Node* flavors are simply a wrapper of a NodeResources object (for now (May 2019) with the exception of some
+ * legacy behavior where nodes are allocated by specifying a physical host flavor directly).
*
* @author bratseth
*/
@@ -19,8 +20,11 @@ public class Flavor {
private boolean configured;
private final String name;
private final int cost;
+ private final boolean isStock;
private final Type type;
private final double bandwidth;
+ private final boolean retired;
+ private List<Flavor> replacesFlavors;
/** The hardware resources of this flavor */
private NodeResources resources;
@@ -30,22 +34,31 @@ public class Flavor {
this.configured = true;
this.name = flavorConfig.name();
this.cost = flavorConfig.cost();
+ this.isStock = flavorConfig.stock();
this.type = Type.valueOf(flavorConfig.environment());
this.resources = new NodeResources(flavorConfig.minCpuCores(),
flavorConfig.minMainMemoryAvailableGb(),
flavorConfig.minDiskAvailableGb(),
flavorConfig.fastDisk() ? NodeResources.DiskSpeed.fast : NodeResources.DiskSpeed.slow);
this.bandwidth = flavorConfig.bandwidth();
+ this.retired = flavorConfig.retired();
+ this.replacesFlavors = new ArrayList<>();
}
/** Creates a *node* flavor from a node resources spec */
public Flavor(NodeResources resources) {
Objects.requireNonNull(resources, "Resources cannot be null");
+ if (resources.allocateByLegacyName())
+ throw new IllegalArgumentException("Can not create flavor '" + resources.legacyName() + "' from a flavor: " +
+ "Non-docker flavors must be of a configured flavor");
this.configured = false;
- this.name = resources.toString();
+ this.name = resources.legacyName().orElse(resources.toString());
this.cost = 0;
+ this.isStock = true;
this.type = Type.DOCKER_CONTAINER;
this.bandwidth = 1;
+ this.retired = false;
+ this.replacesFlavors = List.of();
this.resources = resources;
}
@@ -60,6 +73,8 @@ public class Flavor {
*/
public int cost() { return cost; }
+ public boolean isStock() { return isStock; }
+
/**
* True if this is a configured flavor used for hosts,
* false if it is a virtual flavor created on the fly from node resources
@@ -78,31 +93,65 @@ public class Flavor {
public double getMinCpuCores() { return resources.vcpu(); }
+ /** Returns whether the flavor is retired */
+ public boolean isRetired() {
+ return retired;
+ }
+
public Type getType() { return type; }
/** Convenience, returns getType() == Type.DOCKER_CONTAINER */
public boolean isDocker() { return type == Type.DOCKER_CONTAINER; }
- // TODO: Remove after August 2019
- public String canonicalName() { return name; }
-
- // TODO: Remove after August 2019
- public boolean satisfies(Flavor flavor) { return this.equals(flavor); }
-
- // TODO: Remove after August 2019
- public boolean isStock() { return false; }
-
- // TODO: Remove after August 2019
- public boolean isRetired() { return false; }
+ /**
+ * Returns the canonical name of this flavor - which is the name which should be used as an interface to users.
+ * The canonical name of this flavor is:
+ * <ul>
+ * <li>If it replaces one flavor, the canonical name of the flavor it replaces
+ * <li>If it replaces multiple or no flavors - itself
+ * </ul>
+ *
+ * The logic is that we can use this to capture the gritty details of configurations in exact flavor names
+ * but also encourage users to refer to them by a common name by letting such flavor variants declare that they
+ * replace the canonical name we want. However, if a node replaces multiple names, we have no basis for choosing one
+ * of them as the canonical, so we return the current as canonical.
+ */
+ public String canonicalName() {
+ return isCanonical() ? name : replacesFlavors.get(0).canonicalName();
+ }
+
+ /** Returns whether this is a canonical flavor */
+ public boolean isCanonical() {
+ return replacesFlavors.size() != 1;
+ }
- // TODO: Remove after August 2019
- public boolean isCanonical() { return false; }
+ /**
+ * The flavors this (directly) replaces.
+ * This is immutable if this is frozen, and a mutable list otherwise.
+ */
+ public List<Flavor> replaces() { return replacesFlavors; }
- // TODO: Remove after August 2019
- public List<Flavor> replaces() { return Collections.emptyList(); }
+ /**
+ * Returns whether this flavor satisfies the requested flavor, either directly
+ * (by being the same), or by directly or indirectly replacing it
+ */
+ public boolean satisfies(Flavor flavor) {
+ if (this.equals(flavor)) {
+ return true;
+ }
+ if (this.retired) {
+ return false;
+ }
+ for (Flavor replaces : replacesFlavors)
+ if (replaces.satisfies(flavor))
+ return true;
+ return false;
+ }
- // TODO: Remove after August 2019
- public void freeze() {}
+ /** Irreversibly freezes the content of this */
+ public void freeze() {
+ replacesFlavors = List.copyOf(replacesFlavors);
+ }
@Override
public int hashCode() { return name.hashCode(); }
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java
index a9f031cae70..4d4d3c8cf86 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java
@@ -41,7 +41,10 @@ public class NodeFlavors {
return Optional.of(configuredFlavors.get(name));
NodeResources nodeResources = NodeResources.fromLegacyName(name);
- return Optional.of(new Flavor(nodeResources));
+ if (nodeResources.allocateByLegacyName())
+ return Optional.empty();
+ else
+ return Optional.of(new Flavor(nodeResources));
}
/**
@@ -49,7 +52,8 @@ public class NodeFlavors {
* and cannot be created on the fly.
*/
public Flavor getFlavorOrThrow(String flavorName) {
- return getFlavor(flavorName).orElseThrow(() -> new IllegalArgumentException("Unknown flavor '" + flavorName + "'"));
+ return getFlavor(flavorName).orElseThrow(() -> new IllegalArgumentException("Unknown flavor '" + flavorName +
+ "'. Flavors are " + canonicalFlavorNames()));
}
/** Returns true if this flavor is configured or can be created on the fly */
@@ -57,8 +61,43 @@ public class NodeFlavors {
return getFlavor(flavorName).isPresent();
}
+ private List<String> canonicalFlavorNames() {
+ return configuredFlavors.values().stream().map(Flavor::canonicalName).distinct().sorted().collect(Collectors.toList());
+ }
+
private static Collection<Flavor> toFlavors(FlavorsConfig config) {
- return config.flavor().stream().map(Flavor::new).collect(Collectors.toList());
+ Map<String, Flavor> flavors = new HashMap<>();
+ // First pass, create all flavors, but do not include flavorReplacesConfig.
+ for (FlavorsConfig.Flavor flavorConfig : config.flavor()) {
+ flavors.put(flavorConfig.name(), new Flavor(flavorConfig));
+ }
+ // Second pass, set flavorReplacesConfig to point to correct flavor.
+ for (FlavorsConfig.Flavor flavorConfig : config.flavor()) {
+ Flavor flavor = flavors.get(flavorConfig.name());
+ for (FlavorsConfig.Flavor.Replaces flavorReplacesConfig : flavorConfig.replaces()) {
+ if (! flavors.containsKey(flavorReplacesConfig.name())) {
+ throw new IllegalStateException("Replaces for " + flavor.name() +
+ " pointing to a non existing flavor: " + flavorReplacesConfig.name());
+ }
+ flavor.replaces().add(flavors.get(flavorReplacesConfig.name()));
+ }
+ flavor.freeze();
+ }
+ // Third pass, ensure that retired flavors have a replacement
+ for (Flavor flavor : flavors.values()) {
+ if (flavor.isRetired() && !hasReplacement(flavors.values(), flavor)) {
+ throw new IllegalStateException(
+ String.format("Flavor '%s' is retired, but has no replacement", flavor.name())
+ );
+ }
+ }
+ return flavors.values();
+ }
+
+ private static boolean hasReplacement(Collection<Flavor> flavors, Flavor flavor) {
+ return flavors.stream()
+ .filter(f -> !f.equals(flavor))
+ .anyMatch(f -> f.satisfies(flavor));
}
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
index 8ef48f7048f..7e90767c9c5 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
@@ -1,6 +1,7 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.provision;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -21,6 +22,11 @@ public class NodeResources {
private final double diskGb;
private final DiskSpeed diskSpeed;
+ private final boolean allocateByLegacyName;
+
+ /** The legacy (flavor) name of this, or null if none */
+ private final String legacyName;
+
/** Create node resources requiring fast disk */
public NodeResources(double vcpu, double memoryGb, double diskGb) {
this(vcpu, memoryGb, diskGb, DiskSpeed.fast);
@@ -31,6 +37,18 @@ public class NodeResources {
this.memoryGb = memoryGb;
this.diskGb = diskGb;
this.diskSpeed = diskSpeed;
+ this.allocateByLegacyName = false;
+ this.legacyName = null;
+ }
+
+ private NodeResources(double vcpu, double memoryGb, double diskGb, DiskSpeed diskSpeed,
+ boolean allocateByLegacyName, String legacyName) {
+ this.vcpu = vcpu;
+ this.memoryGb = memoryGb;
+ this.diskGb = diskGb;
+ this.diskSpeed = diskSpeed;
+ this.allocateByLegacyName = allocateByLegacyName;
+ this.legacyName = legacyName;
}
public double vcpu() { return vcpu; }
@@ -64,17 +82,24 @@ public class NodeResources {
combine(this.diskSpeed, other.diskSpeed));
}
- // TODO: Remove after August 2019
+ /**
+ * If this is true, a non-docker legacy name was used to specify this and we'll respect that by mapping directly.
+ * The other getters of this will return 0.
+ */
+ public boolean allocateByLegacyName() { return allocateByLegacyName; }
+
+ /** Returns the legacy name of this, or empty if none. */
public Optional<String> legacyName() {
- return Optional.of(toString());
+ return Optional.ofNullable(legacyName);
}
- // TODO: Remove after August 2019
- public boolean allocateByLegacyName() { return false; }
-
private boolean isInterchangeableWith(NodeResources other) {
+ if (this.allocateByLegacyName != other.allocateByLegacyName) return false;
+ if (this.allocateByLegacyName) return legacyName.equals(other.legacyName);
+
if (this.diskSpeed != DiskSpeed.any && other.diskSpeed != DiskSpeed.any && this.diskSpeed != other.diskSpeed)
return false;
+
return true;
}
@@ -90,26 +115,40 @@ public class NodeResources {
if (o == this) return true;
if ( ! (o instanceof NodeResources)) return false;
NodeResources other = (NodeResources)o;
- if (this.vcpu != other.vcpu) return false;
- if (this.memoryGb != other.memoryGb) return false;
- if (this.diskGb != other.diskGb) return false;
- if (this.diskSpeed != other.diskSpeed) return false;
- return true;
+ if (allocateByLegacyName) {
+ return this.legacyName.equals(other.legacyName);
+ }
+ else {
+ if (this.vcpu != other.vcpu) return false;
+ if (this.memoryGb != other.memoryGb) return false;
+ if (this.diskGb != other.diskGb) return false;
+ if (this.diskSpeed != other.diskSpeed) return false;
+ return true;
+ }
}
@Override
public int hashCode() {
- return (int)(2503 * vcpu + 22123 * memoryGb + 26987 * diskGb + diskSpeed.hashCode());
+ if (allocateByLegacyName)
+ return legacyName.hashCode();
+ else
+ return (int)(2503 * vcpu + 22123 * memoryGb + 26987 * diskGb + diskSpeed.hashCode());
}
@Override
public String toString() {
- return "[vcpu: " + vcpu + ", memory: " + memoryGb + " Gb, disk " + diskGb + " Gb" +
- (diskSpeed != DiskSpeed.fast ? ", disk speed: " + diskSpeed : "") + "]";
+ if (allocateByLegacyName)
+ return "flavor '" + legacyName + "'";
+ else
+ return "[vcpu: " + vcpu + ", memory: " + memoryGb + " Gb, disk " + diskGb + " Gb" +
+ (diskSpeed != DiskSpeed.fast ? ", disk speed: " + diskSpeed : "") + "]";
}
/** Returns true if all the resources of this are the same or larger than the given resources */
public boolean satisfies(NodeResources other) {
+ if (this.allocateByLegacyName || other.allocateByLegacyName) // resources are not available
+ return Objects.equals(this.legacyName, other.legacyName);
+
if (this.vcpu < other.vcpu) return false;
if (this.memoryGb < other.memoryGb) return false;
if (this.diskGb < other.diskGb) return false;
@@ -124,6 +163,9 @@ public class NodeResources {
/** Returns true if all the resources of this are the same as or compatible with the given resources */
public boolean compatibleWith(NodeResources other) {
+ if (this.allocateByLegacyName || other.allocateByLegacyName) // resources are not available
+ return Objects.equals(this.legacyName, other.legacyName);
+
if (this.vcpu != other.vcpu) return false;
if (this.memoryGb != other.memoryGb) return false;
if (this.diskGb != other.diskGb) return false;
@@ -137,20 +179,20 @@ public class NodeResources {
*
* @throws IllegalArgumentException if the given string cannot be parsed as a serial form of this
*/
- public static NodeResources fromLegacyName(String name) {
- if ( ! name.startsWith("d-"))
- throw new IllegalArgumentException("A node specification string must start by 'd-' but was '" + name + "'");
- String[] parts = name.split("-");
- if (parts.length != 4)
- throw new IllegalArgumentException("A node specification string must contain three numbers separated by '-' but was '" + name + "'");
-
- double cpu = Integer.parseInt(parts[1]);
- double mem = Integer.parseInt(parts[2]);
- double dsk = Integer.parseInt(parts[3]);
- if (cpu == 0) cpu = 0.5;
- if (cpu == 2 && mem == 8 ) cpu = 1.5;
- if (cpu == 2 && mem == 12 ) cpu = 2.3;
- return new NodeResources(cpu, mem, dsk, DiskSpeed.fast);
+ public static NodeResources fromLegacyName(String flavorString) {
+ if (flavorString.startsWith("d-")) { // A legacy docker flavor: We still allocate by numbers
+ String[] parts = flavorString.split("-");
+ double cpu = Integer.parseInt(parts[1]);
+ double mem = Integer.parseInt(parts[2]);
+ double dsk = Integer.parseInt(parts[3]);
+ if (cpu == 0) cpu = 0.5;
+ if (cpu == 2 && mem == 8 ) cpu = 1.5;
+ if (cpu == 2 && mem == 12 ) cpu = 2.3;
+ return new NodeResources(cpu, mem, dsk, DiskSpeed.fast, false, flavorString);
+ }
+ else { // Another legacy flavor: Allocate by direct matching
+ return new NodeResources(0, 0, 0, DiskSpeed.fast, true, flavorString);
+ }
}
}
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 9fcee6b60ed..9e01718bfc6 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
@@ -154,6 +154,7 @@ public class AllocatedHostsSerializer {
}
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/config-provisioning/src/main/resources/configdefinitions/flavors.def b/config-provisioning/src/main/resources/configdefinitions/flavors.def
index 131c23054a2..1cfb18d2cd2 100644
--- a/config-provisioning/src/main/resources/configdefinitions/flavors.def
+++ b/config-provisioning/src/main/resources/configdefinitions/flavors.def
@@ -7,14 +7,22 @@ namespace=config.provisioning
# If a certain flavor has no config it is not necessary to list it here to use it.
flavor[].name string
-# NOT USED: TODO: Remove after August 2019
+# Names of other flavors (whether mentioned in this config or not) which this flavor
+# is a replacement for: If one of these flavor names are requested, this flavor may
+# be assigned instead.
+# Replacements are transitive: If flavor a replaces b replaces c, then a request for flavor
+# c may be satisfied by assigning nodes of flavor a.
flavor[].replaces[].name string
# The monthly Total Cost of Ownership (TCO) in USD. Typically calculated as TCO divided by
# the expected lifetime of the node (usually three years).
flavor[].cost int default=0
-# NOT USED: TODO: Remove after August 2019
+# A stock flavor is any flavor which we expect to buy more of in the future.
+# Stock flavors are assigned to applications by cost priority.
+#
+# Non-stock flavors are used for nodes for which a fixed amount has already been purchased
+# for some historical reason. These nodes are assigned to applications by exact match and ignoring cost.
flavor[].stock bool default=true
# The type of node: BARE_METAL, VIRTUAL_MACHINE or DOCKER_CONTAINER
@@ -35,6 +43,6 @@ flavor[].fastDisk bool default=true
# Expected network interface bandwidth available for this flavor, in Mbit/s.
flavor[].bandwidth double default=0.0
-# NOT USED: TODO: Remove after August 2019
+# The flavor is retired and should no longer be used.
flavor[].retired bool default=false
diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/NodeFlavorsTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/NodeFlavorsTest.java
index ec3f73a8194..55ffa821e26 100644
--- a/config-provisioning/src/test/java/com/yahoo/config/provision/NodeFlavorsTest.java
+++ b/config-provisioning/src/test/java/com/yahoo/config/provision/NodeFlavorsTest.java
@@ -2,22 +2,47 @@
package com.yahoo.config.provision;
import com.yahoo.config.provisioning.FlavorsConfig;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import java.util.ArrayList;
import java.util.List;
-import static org.junit.Assert.assertEquals;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
public class NodeFlavorsTest {
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void testReplacesWithBadValue() {
+ FlavorsConfig.Builder builder = new FlavorsConfig.Builder();
+ List<FlavorsConfig.Flavor.Builder> flavorBuilderList = new ArrayList<>();
+ FlavorsConfig.Flavor.Builder flavorBuilder = new FlavorsConfig.Flavor.Builder();
+ FlavorsConfig.Flavor.Replaces.Builder flavorReplacesBuilder = new FlavorsConfig.Flavor.Replaces.Builder();
+ flavorReplacesBuilder.name("non-existing-config");
+ flavorBuilder.name("strawberry").cost(2).replaces.add(flavorReplacesBuilder);
+ flavorBuilderList.add(flavorBuilder);
+ builder.flavor(flavorBuilderList);
+ FlavorsConfig config = new FlavorsConfig(builder);
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Replaces for strawberry pointing to a non existing flavor: non-existing-config");
+ new NodeFlavors(config);
+ }
+
@Test
public void testConfigParsing() {
FlavorsConfig.Builder builder = new FlavorsConfig.Builder();
List<FlavorsConfig.Flavor.Builder> flavorBuilderList = new ArrayList<>();
{
FlavorsConfig.Flavor.Builder flavorBuilder = new FlavorsConfig.Flavor.Builder();
- flavorBuilder.name("strawberry").cost(2);
+ FlavorsConfig.Flavor.Replaces.Builder flavorReplacesBuilder = new FlavorsConfig.Flavor.Replaces.Builder();
+ flavorReplacesBuilder.name("banana");
+ flavorBuilder.name("strawberry").cost(2).replaces.add(flavorReplacesBuilder);
flavorBuilderList.add(flavorBuilder);
}
{
@@ -28,7 +53,28 @@ public class NodeFlavorsTest {
builder.flavor(flavorBuilderList);
FlavorsConfig config = new FlavorsConfig(builder);
NodeFlavors nodeFlavors = new NodeFlavors(config);
- assertEquals(3, nodeFlavors.getFlavor("banana").get().cost());
+ assertThat(nodeFlavors.getFlavor("banana").get().cost(), is(3));
+ }
+
+ @Test
+ public void testRetiredFlavorWithoutReplacement() {
+ FlavorsConfig.Builder builder = new FlavorsConfig.Builder();
+ List<FlavorsConfig.Flavor.Builder> flavorBuilderList = new ArrayList<>();
+ {
+ FlavorsConfig.Flavor.Builder flavorBuilder = new FlavorsConfig.Flavor.Builder();
+ flavorBuilder.name("retired").retired(true);
+ flavorBuilderList.add(flavorBuilder);
+ }
+ {
+ FlavorsConfig.Flavor.Builder flavorBuilder = new FlavorsConfig.Flavor.Builder();
+ flavorBuilder.name("chocolate");
+ flavorBuilderList.add(flavorBuilder);
+ }
+ builder.flavor(flavorBuilderList);
+ FlavorsConfig config = new FlavorsConfig(builder);
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Flavor 'retired' is retired, but has no replacement");
+ new NodeFlavors(config);
}
}
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 d83dea7007b..327f37335bf 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
@@ -182,7 +182,7 @@ public final class Node {
/** Returns a copy of this node which is retired */
public Node retire(Instant retiredAt) {
- if (status.wantToRetire())
+ if (flavor.isRetired() || status.wantToRetire())
return retire(Agent.system, retiredAt);
else
return retire(Agent.application, retiredAt);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java
index 4ec2bac0159..2e17c2d6f67 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java
@@ -49,9 +49,6 @@ public class NodeList implements Iterable<Node> {
/** Returns the subset of nodes having exactly the given resources */
public NodeList resources(NodeResources resources) { return filter(node -> node.flavor().resources().equals(resources)); }
- /** Returns the subset of nodes not having exactly the given resources */
- public NodeList notResources(NodeResources resources) { return filter(node -> ! node.flavor().resources().equals(resources)); }
-
/** Returns the subset of nodes of the given flavor */
public NodeList flavor(String flavor) {
return filter(node -> node.flavor().name().equals(flavor));
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
index 64e2df78642..6cee3005e91 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.NodeFlavors;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
@@ -21,10 +22,12 @@ import java.util.Optional;
public class CapacityPolicies {
private final Zone zone;
+ private final NodeFlavors flavors;
private final FlagSource flagSource;
- public CapacityPolicies(Zone zone, FlagSource flagSource) {
+ public CapacityPolicies(Zone zone, NodeFlavors flavors, FlagSource flagSource) {
this.zone = zone;
+ this.flavors = flavors;
this.flagSource = flagSource;
}
@@ -42,7 +45,9 @@ public class CapacityPolicies {
}
public NodeResources decideNodeResources(Optional<NodeResources> requestedResources, ClusterSpec cluster) {
- NodeResources resources = requestedResources.orElse(defaultNodeResources(cluster.type()));
+ NodeResources resources = specifiedOrDefaultNodeResources(requestedResources, cluster);
+
+ if (resources.allocateByLegacyName()) return resources; // Modification not possible
// Allow slow disks in zones which are not performance sensitive
if (zone.system().isCd() || zone.environment() == Environment.dev || zone.environment() == Environment.test)
@@ -55,6 +60,24 @@ public class CapacityPolicies {
return resources;
}
+ private NodeResources specifiedOrDefaultNodeResources(Optional<NodeResources> requestedResources, ClusterSpec cluster) {
+ if (requestedResources.isPresent() && ! requestedResources.get().allocateByLegacyName())
+ return requestedResources.get();
+
+ if (requestedResources.isEmpty())
+ return defaultNodeResources(cluster.type());
+
+ switch (zone.environment()) {
+ case dev: case test: case staging: return defaultNodeResources(cluster.type());
+ default:
+ flavors.getFlavorOrThrow(requestedResources.get().legacyName().get()); // verify existence
+ // Return this spec containing the legacy flavor name, not the flavor's capacity object
+ // which describes the flavors capacity, as the point of legacy allocation is to match
+ // by name, not by resources
+ return requestedResources.get();
+ }
+ }
+
private NodeResources defaultNodeResources(ClusterSpec.Type clusterType) {
if (clusterType == ClusterSpec.Type.admin)
return nodeResourcesForAdminCluster();
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorConfigBuilder.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorConfigBuilder.java
index 4d3f2308c03..fbf97ba25d9 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorConfigBuilder.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorConfigBuilder.java
@@ -30,6 +30,12 @@ public class FlavorConfigBuilder {
return flavor;
}
+ public void addReplaces(String replaces, FlavorsConfig.Flavor.Builder flavor) {
+ FlavorsConfig.Flavor.Replaces.Builder flavorReplaces = new FlavorsConfig.Flavor.Replaces.Builder();
+ flavorReplaces.name(replaces);
+ flavor.replaces(flavorReplaces);
+ }
+
public void addCost(int cost, FlavorsConfig.Flavor.Builder flavor) {
flavor.cost(cost);
}
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 8b79a303dbd..d4527452e9c 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
@@ -111,6 +111,7 @@ class NodeAllocation {
// conditions on which we want to retire nodes that were allocated previously
if ( violatesParentHostPolicy(this.nodes, offered)) wantToRetireNode = true;
if ( ! hasCompatibleFlavor(offered)) wantToRetireNode = true;
+ if ( offered.flavor().isRetired()) wantToRetireNode = true;
if ( offered.status().wantToRetire()) wantToRetireNode = true;
if ( requestedNodes.isExclusive() &&
! hostsOnly(application.tenant(), offered.parentHostname())) wantToRetireNode = true;
@@ -132,6 +133,9 @@ class NodeAllocation {
++rejectedDueToExclusivity;
continue;
}
+ if (offered.flavor().isRetired()) {
+ continue;
+ }
if (offered.status().wantToRetire()) {
continue;
}
@@ -283,6 +287,7 @@ class NodeAllocation {
.filter(NodeSpec.CountNodeSpec.class::isInstance)
.map(NodeSpec.CountNodeSpec.class::cast)
.map(spec -> new FlavorCount(spec.resources(), spec.fulfilledDeficitCount(acceptedOfRequestedFlavor)))
+ .filter(flavorCount -> ! flavorCount.getFlavor().allocateByLegacyName())
.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 f6554b2dede..6b27662448c 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
@@ -215,7 +215,8 @@ class NodePrioritizer {
private PrioritizableNode toNodePriority(Node node, boolean isSurplusNode, boolean isNewNode) {
PrioritizableNode.Builder builder = new PrioritizableNode.Builder(node)
.withSurplusNode(isSurplusNode)
- .withNewNode(isNewNode);
+ .withNewNode(isNewNode)
+ .withPreferredOnFlavor(preferredOnLegacyFlavor(node));
allNodes.parentOf(node).ifPresent(parent -> {
builder.withParent(parent).withFreeParentCapacity(capacity.freeCapacityOf(parent, false));
@@ -228,6 +229,18 @@ class NodePrioritizer {
return builder.build();
}
+ /** Needed to handle requests for legacy non-docker nodes only */
+ private boolean preferredOnLegacyFlavor(Node node) {
+ if (requestedNodes instanceof NodeSpec.CountNodeSpec) {
+ NodeResources requestedNodeResources = ((NodeSpec.CountNodeSpec)requestedNodes).resources();
+ if (requestedNodeResources.allocateByLegacyName()) {
+ Flavor requestedFlavor = flavors.getFlavorOrThrow(requestedNodeResources.legacyName().get());
+ return ! requestedFlavor.isStock() && node.flavor().equals(requestedFlavor);
+ }
+ }
+ return false;
+ }
+
static boolean isPreferredNodeToBeRelocated(List<Node> nodes, Node node, Node parent) {
NodeList list = new NodeList(nodes);
return list.childrenOf(parent).asList().stream()
@@ -248,7 +261,8 @@ class NodePrioritizer {
}
private boolean isDocker() {
- return resources(requestedNodes) != null;
+ NodeResources flavor = resources(requestedNodes);
+ return (flavor != null) && ! flavor.allocateByLegacyName();
}
private static int compareForRelocation(Node a, Node b) {
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 29e37b76ac3..1cf5cfbb4f3 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
@@ -57,10 +57,10 @@ public class NodeRepositoryProvisioner implements Provisioner {
}
@Inject
- public NodeRepositoryProvisioner(NodeRepository nodeRepository, Zone zone,
+ public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone,
ProvisionServiceProvider provisionServiceProvider, FlagSource flagSource) {
this.nodeRepository = nodeRepository;
- this.capacityPolicies = new CapacityPolicies(zone, flagSource);
+ this.capacityPolicies = new CapacityPolicies(zone, flavors, flagSource);
this.zone = zone;
this.loadBalancerProvisioner = provisionServiceProvider.getLoadBalancerService().map(lbService -> new LoadBalancerProvisioner(nodeRepository, lbService));
this.preparer = new Preparer(nodeRepository,
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 865b643b93b..66a8f2f8f6d 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
@@ -92,13 +92,19 @@ public interface NodeSpec {
@Override
public boolean isCompatible(Flavor flavor, NodeFlavors flavors) {
- if (flavor.isDocker()) { // Docker nodes can satisfy a request for parts of their resources
- if (flavor.resources().compatibleWith(requestedNodeResources))
+ if (requestedNodeResources.allocateByLegacyName() && flavor.isConfigured()) {
+ if (flavor.satisfies(flavors.getFlavorOrThrow(requestedNodeResources.legacyName().get())))
return true;
}
- else { // Other nodes must be matched exactly
- if (requestedNodeResources.equals(flavor.resources()))
- return true;
+ else {
+ if (flavor.isDocker()) { // Docker nodes can satisfy a request for parts of their resources
+ if (flavor.resources().compatibleWith(requestedNodeResources))
+ return true;
+ }
+ else { // Other nodes must be matched exactly
+ if (requestedNodeResources.equals(flavor.resources()))
+ return true;
+ }
}
return requestedFlavorCanBeAchievedByResizing(flavor);
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
index df817ebb8ad..be34e11f585 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
@@ -145,7 +145,7 @@ class NodesResponse extends HttpResponse {
}
object.setString("openStackId", node.id());
object.setString("flavor", node.flavor().name());
- object.setString("canonicalFlavor", node.flavor().name());
+ object.setString("canonicalFlavor", node.flavor().canonicalName());
object.setDouble("minDiskAvailableGb", node.flavor().getMinDiskAvailableGb());
object.setDouble("minMainMemoryAvailableGb", node.flavor().getMinMainMemoryAvailableGb());
object.setDouble("minCpuCores", node.flavor().getMinCpuCores());
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeFlavors.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeFlavors.java
index a87f5af715c..0992fbc75ca 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeFlavors.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeFlavors.java
@@ -24,8 +24,11 @@ public class MockNodeFlavors extends NodeFlavors {
b.addFlavor("docker", 0.2, 0.5, 100, Flavor.Type.DOCKER_CONTAINER);
b.addFlavor("d-2-8-100", 2, 8, 100, Flavor.Type.DOCKER_CONTAINER);
b.addFlavor("v-4-8-100", 4.0, 8.0, 100, Flavor.Type.VIRTUAL_MACHINE);
- b.addFlavor("large-variant", 64, 128, 2000, Flavor.Type.BARE_METAL);
- b.addFlavor("expensive", 6, 12, 500, Flavor.Type.BARE_METAL);
+ FlavorsConfig.Flavor.Builder largeVariant = b.addFlavor("large-variant", 64, 128, 2000, Flavor.Type.BARE_METAL);
+ b.addReplaces("large", largeVariant);
+ FlavorsConfig.Flavor.Builder expensiveFlavor = b.addFlavor("expensive", 6, 12, 500, Flavor.Type.BARE_METAL);
+ b.addReplaces("default", expensiveFlavor);
+ b.addCost(200, expensiveFlavor);
return b.build();
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index 3da1d14541e..a586bfa15c2 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -58,6 +58,7 @@ public class MockNodeRepository extends NodeRepository {
private void populate() {
NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(this,
+ flavors,
Zone.defaultZone(),
new MockProvisionServiceProvider(),
new InMemoryFlagSource());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
index 89c6ed6aa0d..6be03e7969a 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
@@ -49,13 +49,8 @@ import static org.junit.Assert.assertEquals;
public class FailedExpirerTest {
private static final ApplicationId tenantHostApplicationId = ApplicationId.from("vespa", "zone-app", "default");
-
- private static final ClusterSpec tenantHostApplicationClusterSpec =
- ClusterSpec.request(ClusterSpec.Type.container,
- ClusterSpec.Id.from("node-admin"),
- Version.fromString("6.42"),
- false);
-
+ private static final ClusterSpec tenantHostApplicationClusterSpec = ClusterSpec.request(
+ ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin"), Version.fromString("6.42"), false);
private static final Capacity tenantHostApplicationCapacity = Capacity.fromRequiredNodeType(NodeType.host);
@Test
@@ -280,7 +275,7 @@ public class FailedExpirerTest {
new MockNameResolver().mockAnyLookup(),
DockerImage.fromString("docker-image"),
true);
- this.provisioner = new NodeRepositoryProvisioner(nodeRepository, Zone.defaultZone(), new MockProvisionServiceProvider(), new InMemoryFlagSource());
+ this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, Zone.defaultZone(), new MockProvisionServiceProvider(), new InMemoryFlagSource());
this.expirer = new FailedExpirer(nodeRepository, zone, clock, Duration.ofMinutes(30));
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
index f4b36b12bff..4e82bdbfafe 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
@@ -76,7 +76,7 @@ public class NodeFailTester {
curator = new MockCurator();
nodeRepository = new NodeRepository(nodeFlavors, curator, clock, zone, new MockNameResolver().mockAnyLookup(),
DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"), true);
- provisioner = new NodeRepositoryProvisioner(nodeRepository, zone, new MockProvisionServiceProvider(), new InMemoryFlagSource());
+ provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, zone, new MockProvisionServiceProvider(), new InMemoryFlagSource());
hostLivenessTracker = new TestHostLivenessTracker(clock);
orchestrator = new OrchestratorMock();
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java
index 95f62521628..e1ac0430ee4 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java
@@ -58,7 +58,7 @@ public class OperatorChangeApplicationMaintainerTest {
new MockNameResolver().mockAnyLookup(),
DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"),
true);
- this.fixture = new Fixture(zone, nodeRepository);
+ this.fixture = new Fixture(zone, nodeRepository, nodeFlavors);
createReadyNodes(15, this.fixture.nodeResources, nodeRepository);
createHostNodes(2, nodeRepository, nodeFlavors);
@@ -126,12 +126,10 @@ public class OperatorChangeApplicationMaintainerTest {
final int wantedNodesApp1 = 5;
final int wantedNodesApp2 = 7;
- Fixture(Zone zone, NodeRepository nodeRepository) {
+ Fixture(Zone zone, NodeRepository nodeRepository, NodeFlavors flavors) {
this.nodeRepository = nodeRepository;
- NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository,
- zone,
- new MockProvisionServiceProvider(),
- new InMemoryFlagSource());
+ NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(
+ nodeRepository, flavors, zone, new MockProvisionServiceProvider(), new InMemoryFlagSource());
Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
app1, new MockDeployer.ApplicationContext(app1, clusterApp1, Capacity.fromCount(wantedNodesApp1, nodeResources), 1),
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
index 94e43e4f99e..211b4a4472f 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
@@ -64,7 +64,7 @@ public class PeriodicApplicationMaintainerTest {
new MockNameResolver().mockAnyLookup(),
DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"),
true);
- this.fixture = new Fixture(zone, nodeRepository);
+ this.fixture = new Fixture(zone, nodeRepository, nodeFlavors);
createReadyNodes(15, fixture.nodeResources, nodeRepository);
createHostNodes(2, nodeRepository, nodeFlavors);
@@ -252,12 +252,10 @@ public class PeriodicApplicationMaintainerTest {
private final TestablePeriodicApplicationMaintainer maintainer;
- Fixture(Zone zone, NodeRepository nodeRepository) {
+ Fixture(Zone zone, NodeRepository nodeRepository, NodeFlavors flavors) {
this.nodeRepository = nodeRepository;
- NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository,
- zone,
- new MockProvisionServiceProvider(),
- new InMemoryFlagSource());
+ NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(
+ nodeRepository, flavors, zone, new MockProvisionServiceProvider(), new InMemoryFlagSource());
Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
app1, new MockDeployer.ApplicationContext(app1, clusterApp1, Capacity.fromCount(wantedNodesApp1, nodeResources), 1),
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
index 3861e4ff98c..f8efb4fdea1 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
@@ -48,7 +48,7 @@ public class ReservationExpirerTest {
new MockNameResolver().mockAnyLookup(),
DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"),
true);
- NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, Zone.defaultZone(), new MockProvisionServiceProvider(), new InMemoryFlagSource());
+ NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, flavors, Zone.defaultZone(), new MockProvisionServiceProvider(), new InMemoryFlagSource());
List<Node> nodes = new ArrayList<>(2);
nodes.add(nodeRepository.createNode(UUID.randomUUID().toString(), UUID.randomUUID().toString(), Optional.empty(), new Flavor(new NodeResources(2, 8, 50)), NodeType.tenant));
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
index 7e471a81cf8..5d3485ab447 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
@@ -66,7 +66,7 @@ public class RetiredExpirerTest {
private final NodeRepository nodeRepository = new NodeRepository(nodeFlavors, curator, clock, zone,
new MockNameResolver().mockAnyLookup(),
DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"), true);
- private final NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, zone, new MockProvisionServiceProvider(), new InMemoryFlagSource());
+ private final NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, zone, new MockProvisionServiceProvider(), new InMemoryFlagSource());
private final Orchestrator orchestrator = mock(Orchestrator.class);
private static final Duration RETIRED_EXPIRATION = Duration.ofHours(12);
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 2094a68148e..b2966ccb91a 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
@@ -154,7 +154,7 @@ public class SerializationTest {
Node node = nodeSerializer.fromJson(Node.State.provisioned, Utf8.toBytes(nodeData));
- assertEquals("large", node.flavor().name());
+ assertEquals("large", node.flavor().canonicalName());
assertEquals(1, node.status().reboot().wanted());
assertEquals(2, node.status().reboot().current());
assertEquals(3, node.allocation().get().restartGeneration().wanted());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
index eeb90a06951..0ecbf389825 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
@@ -225,7 +225,7 @@ public class DockerProvisioningTest {
NodeList nodes = tester.getNodes(application1, Node.State.active);
assertEquals(1, nodes.size());
- assertEquals("[vcpu: 1.0, memory: 1.0 Gb, disk 1.0 Gb]", nodes.asList().get(0).flavor().name());
+ assertEquals("[vcpu: 1.0, memory: 1.0 Gb, disk 1.0 Gb]", nodes.asList().get(0).flavor().canonicalName());
}
private Set<String> hostsOf(NodeList nodes) {
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 d543856f71c..1f474a8e07e 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
@@ -285,6 +285,22 @@ public class DynamicDockerAllocationTest {
}
@Test
+ public void legacy_bare_metal_allocations_are_not_altered() {
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(SystemName.cd, Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build();
+ tester.makeReadyNodes(5, "host-large", NodeType.tenant);
+ deployZoneApp(tester);
+
+ ApplicationId application = tester.makeApplicationId();
+ ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("1"), false);
+ NodeResources resources = NodeResources.fromLegacyName("host-large");
+
+ List<HostSpec> hosts = tester.prepare(application, cluster, 2, 1, resources);
+ assertEquals(2, hosts.size());
+ assertEquals("host-large", hosts.get(0).flavor().get().name());
+ tester.activate(application, hosts);
+ }
+
+ @Test
public void provisioning_fast_disk_speed_do_not_get_slow_nodes() {
provisionFastAndSlowThenDeploy(NodeResources.DiskSpeed.fast, true);
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java
index 0523a1f7f72..a5d5fb81147 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java
@@ -7,7 +7,6 @@ import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostSpec;
-import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.hosted.provision.Node;
@@ -39,25 +38,25 @@ public class MultigroupProvisioningTest {
ApplicationId application1 = tester.makeApplicationId();
- tester.makeReadyNodes(21, "d-1-3-9");
-
- deploy(application1, 6, 1, "d-1-3-9", tester);
- deploy(application1, 6, 2, "d-1-3-9", tester);
- deploy(application1, 6, 3, "d-1-3-9", tester);
- deploy(application1, 6, 6, "d-1-3-9", tester);
- deploy(application1, 6, 1, "d-1-3-9", tester);
- deploy(application1, 6, 6, "d-1-3-9", tester);
- deploy(application1, 6, 6, "d-1-3-9", tester);
- deploy(application1, 6, 2, "d-1-3-9", tester);
- deploy(application1, 8, 2, "d-1-3-9", tester);
- deploy(application1, 9, 3, "d-1-3-9", tester);
- deploy(application1, 9, 3, "d-1-3-9", tester);
- deploy(application1, 9, 3, "d-1-3-9", tester);
- deploy(application1,12, 4, "d-1-3-9", tester);
- deploy(application1, 8, 4, "d-1-3-9", tester);
- deploy(application1,12, 4, "d-1-3-9", tester);
- deploy(application1, 8, 2, "d-1-3-9", tester);
- deploy(application1, 6, 3, "d-1-3-9", tester);
+ tester.makeReadyNodes(21, "default");
+
+ deploy(application1, 6, 1, tester);
+ deploy(application1, 6, 2, tester);
+ deploy(application1, 6, 3, tester);
+ deploy(application1, 6, 6, tester);
+ deploy(application1, 6, 1, tester);
+ deploy(application1, 6, 6, tester);
+ deploy(application1, 6, 6, tester);
+ deploy(application1, 6, 2, tester);
+ deploy(application1, 8, 2, tester);
+ deploy(application1, 9, 3, tester);
+ deploy(application1, 9, 3, tester);
+ deploy(application1, 9, 3, tester);
+ deploy(application1,12, 4, tester);
+ deploy(application1, 8, 4, tester);
+ deploy(application1,12, 4, tester);
+ deploy(application1, 8, 2, tester);
+ deploy(application1, 6, 3, tester);
}
/**
@@ -71,7 +70,7 @@ public class MultigroupProvisioningTest {
ApplicationId application1 = tester.makeApplicationId();
- tester.makeReadyNodes(21, "d-1-3-9");
+ tester.makeReadyNodes(21, "default");
deploy(application1, 12, 2, tester);
deploy(application1, 9, 3, tester);
@@ -84,12 +83,12 @@ public class MultigroupProvisioningTest {
ApplicationId application1 = tester.makeApplicationId();
- tester.makeReadyNodes(10, "d-1-1-1");
- tester.makeReadyNodes(10, "d-3-3-3");
+ tester.makeReadyNodes(10, "small");
+ tester.makeReadyNodes(10, "large");
- deploy(application1, 8, 1, "d-1-1-1", tester);
- deploy(application1, 8, 1, "d-3-3-3", tester);
- deploy(application1, 8, 8, "d-3-3-3", tester);
+ deploy(application1, 8, 1, "small", tester);
+ deploy(application1, 8, 1, "large", tester);
+ deploy(application1, 8, 8, "large", tester);
}
@Test
@@ -98,10 +97,10 @@ public class MultigroupProvisioningTest {
ApplicationId application1 = tester.makeApplicationId();
- tester.makeReadyNodes(10, "d-1-1-1");
+ tester.makeReadyNodes(10, "small");
- deploy(application1, Capacity.fromNodeCount(1, Optional.of("d-1-1-1"), true, true), 1, tester);
- deploy(application1, Capacity.fromNodeCount(2, Optional.of("d-1-1-1"), true, true), 2, tester);
+ deploy(application1, Capacity.fromNodeCount(1, Optional.of("small"), true, true), 1, tester);
+ deploy(application1, Capacity.fromNodeCount(2, Optional.of("small"), true, true), 2, tester);
}
@Test
@@ -110,11 +109,11 @@ public class MultigroupProvisioningTest {
ApplicationId application1 = tester.makeApplicationId();
- tester.makeReadyNodes(10, "d-1-1-1");
- tester.makeReadyNodes(10, "d-3-3-3");
+ tester.makeReadyNodes(10, "small");
+ tester.makeReadyNodes(10, "large");
- deploy(application1, Capacity.fromNodeCount(1, Optional.of("d-1-1-1"), true, true), 1, tester);
- deploy(application1, Capacity.fromNodeCount(2, Optional.of("d-3-3-3"), true, true), 2, tester);
+ deploy(application1, Capacity.fromNodeCount(1, Optional.of("small"), true, true), 1, tester);
+ deploy(application1, Capacity.fromNodeCount(2, Optional.of("large"), true, true), 2, tester);
}
@Test
@@ -123,11 +122,11 @@ public class MultigroupProvisioningTest {
ApplicationId application1 = tester.makeApplicationId();
- tester.makeReadyNodes(10, "d-1-1-1");
- tester.makeReadyNodes(10, "d-3-3-3");
+ tester.makeReadyNodes(10, "small");
+ tester.makeReadyNodes(10, "large");
- deploy(application1, 8, 1, "d-1-1-1", tester);
- deploy(application1, 8, 1, "d-3-3-3", tester);
+ deploy(application1, 8, 1, "small", tester);
+ deploy(application1, 8, 1, "large", tester);
// Expire small nodes
tester.advanceTime(Duration.ofDays(7));
@@ -136,36 +135,35 @@ public class MultigroupProvisioningTest {
tester.clock(),
Collections.singletonMap(application1,
new MockDeployer.ApplicationContext(application1, cluster(),
- Capacity.fromNodeCount(8, Optional.of("d-3-3-3"), false, true), 1)));
+ Capacity.fromNodeCount(8, Optional.of("large"), false, true), 1)));
new RetiredExpirer(tester.nodeRepository(), tester.orchestrator(), deployer, tester.clock(), Duration.ofDays(30),
Duration.ofHours(12)).run();
- assertEquals(8, tester.getNodes(application1, Node.State.inactive).resources(new NodeResources(1, 1, 1)).size());
- deploy(application1, 8, 8, "d-3-3-3", tester);
+ assertEquals(8, tester.getNodes(application1, Node.State.inactive).flavor("small").size());
+ deploy(application1, 8, 8, "large", tester);
}
private void deploy(ApplicationId application, int nodeCount, int groupCount, String flavor, ProvisioningTester tester) {
deploy(application, Capacity.fromNodeCount(nodeCount, Optional.of(flavor), false, true), groupCount, tester);
}
private void deploy(ApplicationId application, int nodeCount, int groupCount, ProvisioningTester tester) {
- deploy(application, Capacity.fromNodeCount(nodeCount, Optional.of("d-3-3-3"), false, true), groupCount, tester);
+ deploy(application, Capacity.fromNodeCount(nodeCount, Optional.of("default"), false, true), groupCount, tester);
}
+ @SuppressWarnings("deprecation") // TODO: Remove
private void deploy(ApplicationId application, Capacity capacity, int wantedGroups, ProvisioningTester tester) {
int nodeCount = capacity.nodeCount();
- NodeResources nodeResources = capacity.nodeResources().get();
+ String flavor = capacity.flavor().get();
- int previousActiveNodeCount = tester.getNodes(application, Node.State.active).resources(nodeResources).size();
+ int previousActiveNodeCount = tester.getNodes(application, Node.State.active).flavor(flavor).size();
tester.activate(application, prepare(application, capacity, wantedGroups, tester));
- System.out.println("Active nodes ---------------");
- tester.getNodes(application, Node.State.active).forEach(n -> System.out.println(" " + n.hostname() + ": Flavor : " + n.flavor() + " retired " + n.status().wantToRetire()));
assertEquals("Superfluous nodes are retired, but no others - went from " + previousActiveNodeCount + " to " + nodeCount + " nodes",
Math.max(0, previousActiveNodeCount - capacity.nodeCount()),
- tester.getNodes(application, Node.State.active).retired().resources(nodeResources).size());
+ tester.getNodes(application, Node.State.active).retired().flavor(flavor).size());
assertEquals("Other flavors are retired",
- 0, tester.getNodes(application, Node.State.active).nonretired().notResources(nodeResources).size());
+ 0, tester.getNodes(application, Node.State.active).nonretired().notFlavor(capacity.flavor().get()).size());
// Check invariants for all nodes
Set<Integer> allIndexes = new HashSet<>();
@@ -181,7 +179,7 @@ public class MultigroupProvisioningTest {
// Count unretired nodes and groups of the requested flavor
Set<Integer> indexes = new HashSet<>();
Map<ClusterSpec.Group, Integer> nonretiredGroups = new HashMap<>();
- for (Node node : tester.getNodes(application, Node.State.active).nonretired().resources(nodeResources)) {
+ for (Node node : tester.getNodes(application, Node.State.active).nonretired().flavor(flavor)) {
indexes.add(node.allocation().get().membership().index());
ClusterSpec.Group group = node.allocation().get().membership().cluster().group().get();
@@ -196,7 +194,7 @@ public class MultigroupProvisioningTest {
assertEquals("Group size", (long)nodeCount / wantedGroups, (long)groupSize);
Map<ClusterSpec.Group, Integer> allGroups = new HashMap<>();
- for (Node node : tester.getNodes(application, Node.State.active).resources(nodeResources)) {
+ for (Node node : tester.getNodes(application, Node.State.active).flavor(flavor)) {
ClusterSpec.Group group = node.allocation().get().membership().cluster().group().get();
allGroups.put(group, nonretiredGroups.getOrDefault(group, 0) + 1);
}
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 2f97cd13234..7ad8dbbf7bb 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
@@ -290,6 +290,54 @@ public class ProvisioningTest {
}
@Test
+ public void application_deployment_multiple_flavors_with_replacement() {
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
+
+ ApplicationId application1 = tester.makeApplicationId();
+
+ tester.makeReadyNodes(8, "large");
+ tester.makeReadyNodes(8, "large-variant");
+
+ // deploy with flavor which will be fulfilled by some old and new nodes
+ SystemState state1 = prepare(application1, 2, 2, 4, 4,
+ NodeResources.fromLegacyName("old-large1"), tester);
+ tester.activate(application1, state1.allHosts);
+
+ // redeploy with increased sizes, this will map to the remaining old/new nodes
+ SystemState state2 = prepare(application1, 3, 4, 4, 5,
+ NodeResources.fromLegacyName("old-large2"), tester);
+ assertEquals("New nodes are reserved", 4, tester.getNodes(application1, Node.State.reserved).size());
+ tester.activate(application1, state2.allHosts);
+ assertEquals("All nodes are used",
+ 16, tester.getNodes(application1, Node.State.active).size());
+ assertEquals("No nodes are retired",
+ 0, tester.getNodes(application1, Node.State.active).retired().size());
+
+ // This is a noop as we are already using large nodes and nodes which replace large
+ SystemState state3 = prepare(application1, 3, 4, 4, 5,
+ NodeResources.fromLegacyName("large"), tester);
+ assertEquals("Noop", 0, tester.getNodes(application1, Node.State.reserved).size());
+ tester.activate(application1, state3.allHosts);
+
+ try {
+ SystemState state4 = prepare(application1, 3, 4, 4, 5,
+ NodeResources.fromLegacyName("large-variant"), tester);
+ fail("Should fail as we don't have that many large-variant nodes");
+ }
+ catch (OutOfCapacityException expected) {
+ }
+
+ // make enough nodes to complete the switch to large-variant
+ tester.makeReadyNodes(8, "large-variant");
+ SystemState state4 = prepare(application1, 3, 4, 4, 5,
+ NodeResources.fromLegacyName("large-variant"), tester);
+ assertEquals("New 'large-variant' nodes are reserved", 8, tester.getNodes(application1, Node.State.reserved).size());
+ tester.activate(application1, state4.allHosts);
+ // (we can not check for the precise state here without carrying over from earlier as the distribution of
+ // old and new on different clusters is unknown)
+ }
+
+ @Test
public void application_deployment_above_then_at_capacity_limit() {
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
@@ -459,6 +507,47 @@ public class ProvisioningTest {
}
@Test
+ public void out_of_desired_flavor() {
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
+
+ tester.makeReadyNodes(10, "small"); // need 2+2+3+3=10
+ tester.makeReadyNodes( 9, "large"); // need 2+2+3+3=10
+ ApplicationId application = tester.makeApplicationId();
+ try {
+ prepare(application, 2, 2, 3, 3,
+ NodeResources.fromLegacyName("large"), tester);
+ fail("Expected exception");
+ }
+ catch (OutOfCapacityException e) {
+ assertTrue(e.getMessage().startsWith("Could not satisfy request for 3 nodes with flavor 'large'"));
+ }
+ }
+
+ @Test
+ public void out_of_capacity_no_replacements_for_retired_flavor() {
+ String flavorToRetire = "default";
+ String replacementFlavor = "new-default";
+
+ FlavorConfigBuilder b = new FlavorConfigBuilder();
+ b.addFlavor(flavorToRetire, 1., 1., 10, Flavor.Type.BARE_METAL).cost(2).retired(true);
+ FlavorsConfig.Flavor.Builder newDefault = b.addFlavor(replacementFlavor, 2., 2., 20,
+ Flavor.Type.BARE_METAL).cost(2);
+ b.addReplaces(flavorToRetire, newDefault);
+
+ ProvisioningTester tester = new ProvisioningTester.Builder()
+ .zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(b.build()).build();
+ ApplicationId application = tester.makeApplicationId();
+
+ try {
+ prepare(application, 2, 0, 2, 0,
+ NodeResources.fromLegacyName(flavorToRetire), tester);
+ fail("Expected exception");
+ } catch (OutOfCapacityException e) {
+ assertTrue(e.getMessage().startsWith("Could not satisfy request"));
+ }
+ }
+
+ @Test
public void out_of_capacity_all_nodes_want_to_retire() {
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
@@ -477,6 +566,21 @@ public class ProvisioningTest {
}
@Test
+ public void nonexisting_flavor() {
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
+
+ ApplicationId application = tester.makeApplicationId();
+ try {
+ prepare(application, 2, 2, 3, 3,
+ NodeResources.fromLegacyName("nonexisting"), tester);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("Unknown flavor 'nonexisting'. Flavors are [default, dockerLarge, dockerSmall, large, old-large1, old-large2, small, v-4-8-100]", e.getMessage());
+ }
+ }
+
+ @Test
public void highest_node_indexes_are_retired_first() {
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
@@ -506,6 +610,96 @@ public class ProvisioningTest {
}
@Test
+ public void application_deployment_prefers_cheapest_stock_nodes() {
+ assertCorrectBareMetalFlavorPreferences(true);
+ }
+
+ @Test
+ public void application_deployment_prefers_exact_nonstock_nodes() {
+ assertCorrectBareMetalFlavorPreferences(false);
+ }
+
+ @Test
+ public void application_deployment_retires_nodes_having_retired_flavor() {
+ String flavorToRetire = "default";
+ String replacementFlavor = "new-default";
+ ApplicationId application = ApplicationId.from(
+ TenantName.from(UUID.randomUUID().toString()),
+ ApplicationName.from(UUID.randomUUID().toString()),
+ InstanceName.from(UUID.randomUUID().toString()));
+ Curator curator = new MockCurator();
+ NameResolver nameResolver = new MockNameResolver().mockAnyLookup();
+
+ // Deploy with flavor that will eventually be retired
+ {
+ FlavorConfigBuilder b = new FlavorConfigBuilder();
+ b.addFlavor(flavorToRetire, 1., 1., 10, Flavor.Type.BARE_METAL).cost(2);
+
+ ProvisioningTester tester = new ProvisioningTester.Builder()
+ .flavorsConfig(b.build()).curator(curator).nameResolver(nameResolver).build();
+ tester.makeReadyNodes(4, flavorToRetire);
+ SystemState state = prepare(application, 2, 0, 2, 0,
+ NodeResources.fromLegacyName(flavorToRetire), tester);
+ tester.activate(application, state.allHosts);
+ }
+
+ // Re-deploy with same flavor, which is now retired
+ {
+ // Retire "default" flavor and add "new-default" as replacement
+ FlavorConfigBuilder b = new FlavorConfigBuilder();
+ b.addFlavor(flavorToRetire, 1., 1., 10, Flavor.Type.BARE_METAL).cost(2).retired(true);
+ FlavorsConfig.Flavor.Builder newDefault = b.addFlavor(replacementFlavor, 2., 2., 20,
+ Flavor.Type.BARE_METAL).cost(2);
+ b.addReplaces(flavorToRetire, newDefault);
+
+ ProvisioningTester tester = new ProvisioningTester.Builder()
+ .flavorsConfig(b.build()).curator(curator).nameResolver(nameResolver).build();
+
+ // Add nodes with "new-default" flavor
+ tester.makeReadyNodes(4, replacementFlavor);
+
+ SystemState state = prepare(application, 2, 0, 2, 0,
+ NodeResources.fromLegacyName(flavorToRetire), tester);
+
+ tester.activate(application, state.allHosts);
+
+ // Nodes with retired flavor are retired
+ NodeList retired = tester.getNodes(application).retired();
+ assertEquals(4, retired.size());
+ assertTrue("Nodes are retired by system", retired.asList().stream().allMatch(retiredBy(Agent.system)));
+ }
+ }
+
+ @Test
+ public void application_deployment_is_not_given_unallocated_nodes_having_retired_flavor() {
+ String flavorToRetire = "default";
+ String replacementFlavor = "new-default";
+
+ FlavorConfigBuilder b = new FlavorConfigBuilder();
+ b.addFlavor(flavorToRetire, 1., 1., 10, Flavor.Type.BARE_METAL).cost(2).retired(true);
+ FlavorsConfig.Flavor.Builder newDefault = b.addFlavor(replacementFlavor, 2., 2., 20,
+ Flavor.Type.BARE_METAL).cost(2);
+ b.addReplaces(flavorToRetire, newDefault);
+
+ ProvisioningTester tester = new ProvisioningTester.Builder()
+ .zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(b.build()).build();
+ ApplicationId application = tester.makeApplicationId();
+
+ // Add nodes
+ tester.makeReadyNodes(4, flavorToRetire);
+ tester.makeReadyNodes(4, replacementFlavor);
+
+ SystemState state = prepare(application, 2, 0, 2, 0,
+ NodeResources.fromLegacyName(flavorToRetire), tester);
+
+ tester.activate(application, state.allHosts);
+
+ List<Node> nodes = tester.getNodes(application).asList();
+ assertTrue("Allocated nodes have flavor " + replacementFlavor,
+ nodes.stream().allMatch(n -> n.flavor().name().equals(replacementFlavor)));
+ }
+
+ @Test
public void application_deployment_retires_nodes_that_want_to_retire() {
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
@@ -587,6 +781,43 @@ public class ProvisioningTest {
} catch (IllegalArgumentException ignored) {}
}
+ private void assertCorrectBareMetalFlavorPreferences(boolean largeIsStock) {
+ FlavorConfigBuilder b = new FlavorConfigBuilder();
+ b.addFlavor("large", 4., 8., 100, Flavor.Type.BARE_METAL).cost(10).stock(largeIsStock);
+ FlavorsConfig.Flavor.Builder largeVariant = b.addFlavor("large-variant", 3., 9., 101, Flavor.Type.BARE_METAL).cost(9);
+ b.addReplaces("large", largeVariant);
+ FlavorsConfig.Flavor.Builder largeVariantVariant = b.addFlavor("large-variant-variant", 4., 9., 101, Flavor.Type.BARE_METAL).cost(11);
+ b.addReplaces("large-variant", largeVariantVariant);
+
+ ProvisioningTester tester = new ProvisioningTester.Builder()
+ .zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(b.build()).build();
+ tester.makeReadyNodes(6, "large"); //cost = 10
+ tester.makeReadyNodes(6, "large-variant"); //cost = 9
+ tester.makeReadyNodes(6, "large-variant-variant"); //cost = 11
+
+ ApplicationId applicationId = tester.makeApplicationId();
+ ClusterSpec contentClusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.42"), false);
+ ClusterSpec containerClusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer"), Version.fromString("6.42"), false);
+
+ List<HostSpec> containerNodes = tester.prepare(applicationId, containerClusterSpec, 5, 1,
+ NodeResources.fromLegacyName("large"));
+ List<HostSpec> contentNodes = tester.prepare(applicationId, contentClusterSpec, 10, 1,
+ NodeResources.fromLegacyName("large"));
+
+ if (largeIsStock) { // 'large' is replaced by 'large-variant' when possible, as it is cheaper
+ tester.assertNumberOfNodesWithFlavor(containerNodes, "large-variant", 5);
+ tester.assertNumberOfNodesWithFlavor(contentNodes, "large-variant", 1);
+ tester.assertNumberOfNodesWithFlavor(contentNodes, "large", 6);
+ }
+ else { // 'large' is preferred when available, as it is what is exactly specified
+ tester.assertNumberOfNodesWithFlavor(containerNodes, "large", 5);
+ tester.assertNumberOfNodesWithFlavor(contentNodes, "large", 1);
+ tester.assertNumberOfNodesWithFlavor(contentNodes, "large-variant", 6);
+ }
+ // in both cases the most expensive, never exactly specified is least preferred
+ tester.assertNumberOfNodesWithFlavor(contentNodes, "large-variant-variant", 3);
+ }
+
private SystemState prepare(ApplicationId application, int container0Size, int container1Size, int content0Size,
int content1Size, NodeResources flavor, ProvisioningTester tester) {
return prepare(application, container0Size, container1Size, content0Size, content1Size, flavor,
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 942492bb790..9f0b4faff01 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
@@ -90,8 +90,8 @@ public class ProvisioningTester {
DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"), true);
this.orchestrator = orchestrator;
ProvisionServiceProvider provisionServiceProvider = new MockProvisionServiceProvider(loadBalancerService, hostProvisioner);
- this.provisioner = new NodeRepositoryProvisioner(nodeRepository, zone, provisionServiceProvider, flagSource);
- this.capacityPolicies = new CapacityPolicies(zone, new InMemoryFlagSource());
+ this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, zone, provisionServiceProvider, flagSource);
+ this.capacityPolicies = new CapacityPolicies(zone, nodeFlavors, new InMemoryFlagSource());
this.provisionLogger = new NullProvisionLogger();
this.loadBalancerService = loadBalancerService;
}
@@ -103,7 +103,15 @@ public class ProvisioningTester {
b.addFlavor("dockerSmall", 1., 1., 10, Flavor.Type.DOCKER_CONTAINER).cost(1);
b.addFlavor("dockerLarge", 2., 1., 20, Flavor.Type.DOCKER_CONTAINER).cost(3);
b.addFlavor("v-4-8-100", 4., 8., 100, Flavor.Type.VIRTUAL_MACHINE).cost(4);
- b.addFlavor("large", 4., 8., 100, Flavor.Type.BARE_METAL).cost(10);
+ b.addFlavor("old-large1", 2., 4., 100, Flavor.Type.BARE_METAL).cost(6);
+ b.addFlavor("old-large2", 2., 5., 100, Flavor.Type.BARE_METAL).cost(14);
+ FlavorsConfig.Flavor.Builder large = b.addFlavor("large", 4., 8., 100, Flavor.Type.BARE_METAL).cost(10);
+ b.addReplaces("old-large1", large);
+ b.addReplaces("old-large2", large);
+ FlavorsConfig.Flavor.Builder largeVariant = b.addFlavor("large-variant", 3., 9., 101, Flavor.Type.BARE_METAL).cost(9);
+ b.addReplaces("large", largeVariant);
+ FlavorsConfig.Flavor.Builder largeVariantVariant = b.addFlavor("large-variant-variant", 4., 9., 101, Flavor.Type.BARE_METAL).cost(11);
+ b.addReplaces("large-variant", largeVariantVariant);
return b.build();
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node9.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node9.json
index dad1e45c1b8..561cab22f85 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node9.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node9.json
@@ -6,7 +6,7 @@
"hostname": "host9.yahoo.com",
"openStackId": "host9.yahoo.com",
"flavor": "large-variant",
- "canonicalFlavor": "large-variant",
+ "canonicalFlavor": "large",
"minDiskAvailableGb": 2000.0,
"minMainMemoryAvailableGb": 128.0,
"minCpuCores": 64.0,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json
index 7d8b48232b5..28bb960eb14 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json
@@ -6,7 +6,7 @@
"hostname": "parent2.yahoo.com",
"openStackId": "parent2.yahoo.com",
"flavor": "large-variant",
- "canonicalFlavor": "large-variant",
+ "canonicalFlavor": "large",
"minDiskAvailableGb": 2000.0,
"minMainMemoryAvailableGb": 128.0,
"minCpuCores": 64.0,