aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@gmail.com>2023-01-25 15:37:28 +0100
committerJon Bratseth <bratseth@gmail.com>2023-01-25 15:46:08 +0100
commit5400b26714de2b9edc8960df88cb18807aceb9b8 (patch)
tree83be1c90a35109f89f16ad48fd0f5dd21c5a4291
parent77120a53191c5ceefa98cbe5d728cebd51901281 (diff)
More resource reduction tests
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java1
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java38
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java55
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java189
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java3
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java7
6 files changed, 216 insertions, 77 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java
index 71d07d494a0..e2e2f317cee 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java
@@ -13,6 +13,7 @@ public enum ValidationId {
indexingChange("indexing-change"), // Changing what tokens are expected and stored in field indexes
indexModeChange("indexing-mode-change"), // Changing the index mode (streaming, indexed, store-only) of documents
fieldTypeChange("field-type-change"), // Field type changes
+ clusterSizeReduction("cluster-size-reduction"), // NOT USED. TODO: Remove on Vespa 9
tensorTypeChange("tensor-type-change"), // Tensor type change
resourcesReduction("resources-reduction"), // Large reductions in node resources (> 50% of the current max total resources)
contentTypeRemoval("schema-removal"), // Removal of a schema (causes deletion of all documents)
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 16de9d3405f..9e48510e704 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
@@ -38,7 +38,9 @@ import java.util.stream.IntStream;
*/
public class InMemoryProvisioner implements HostProvisioner {
- public static final NodeResources defaultResources = new NodeResources(1, 3, 50, 1);
+ public static final NodeResources defaultHostResources = new NodeResources(1, 3, 50, 1);
+
+ private final NodeResources defaultNodeResources;
/**
* If this is true an exception is thrown when all nodes are used.
@@ -74,32 +76,37 @@ public class InMemoryProvisioner implements HostProvisioner {
/** Creates this with a number of nodes with resources 1, 3, 9, 1 */
public InMemoryProvisioner(int nodeCount, boolean sharedHosts) {
- this(nodeCount, defaultResources, sharedHosts);
+ this(nodeCount, defaultHostResources, sharedHosts);
}
/** Creates this with a number of nodes with given resources */
public InMemoryProvisioner(int nodeCount, NodeResources resources, boolean sharedHosts) {
- this(Map.of(resources, createHostInstances(nodeCount)), true, false, false, sharedHosts, 0);
+ this(Map.of(resources, createHostInstances(nodeCount)), true, false, false, sharedHosts, NodeResources.unspecified(), 0);
+ }
+
+ /** Creates this with a number of nodes with given resources */
+ public InMemoryProvisioner(int nodeCount, NodeResources resources, boolean sharedHosts, NodeResources defaultResources) {
+ this(Map.of(resources, createHostInstances(nodeCount)), true, false, false, sharedHosts, defaultResources, 0);
}
/** Creates this with a set of host names of the flavor 'default' */
public InMemoryProvisioner(boolean failOnOutOfCapacity, boolean sharedHosts, String... hosts) {
- this(Map.of(defaultResources, toHostInstances(hosts)), failOnOutOfCapacity, false, false, sharedHosts, 0);
+ this(Map.of(defaultHostResources, toHostInstances(hosts)), failOnOutOfCapacity, false, false, sharedHosts, defaultHostResources, 0);
}
/** Creates this with a set of host names of the flavor 'default' */
public InMemoryProvisioner(boolean failOnOutOfCapacity, boolean sharedHosts, List<String> hosts) {
- this(Map.of(defaultResources, toHostInstances(hosts.toArray(new String[0]))), failOnOutOfCapacity, false, false, sharedHosts, 0);
+ this(Map.of(defaultHostResources, toHostInstances(hosts.toArray(new String[0]))), failOnOutOfCapacity, false, false, sharedHosts, defaultHostResources, 0);
}
/** Creates this with a set of hosts of the flavor 'default' */
public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, boolean sharedHosts, String ... retiredHostNames) {
- this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, false, sharedHosts, 0, retiredHostNames);
+ this(Map.of(defaultHostResources, hosts.asCollection()), failOnOutOfCapacity, false, false, sharedHosts, defaultHostResources, 0, retiredHostNames);
}
/** Creates this with a set of hosts of the flavor 'default' */
public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, boolean sharedHosts, int startIndexForClusters, String ... retiredHostNames) {
- this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, false, sharedHosts, startIndexForClusters, retiredHostNames);
+ this(Map.of(defaultHostResources, hosts.asCollection()), failOnOutOfCapacity, false, false, sharedHosts, defaultHostResources, startIndexForClusters, retiredHostNames);
}
public InMemoryProvisioner(Map<NodeResources, Collection<Host>> hosts,
@@ -107,8 +114,10 @@ public class InMemoryProvisioner implements HostProvisioner {
boolean useMaxResources,
boolean alwaysReturnOneNode,
boolean sharedHosts,
+ NodeResources defaultResources,
int startIndexForClusters,
String ... retiredHostNames) {
+ this.defaultNodeResources = defaultResources;
this.failOnOutOfCapacity = failOnOutOfCapacity;
this.useMaxResources = useMaxResources;
this.alwaysReturnOneNode = alwaysReturnOneNode;
@@ -139,9 +148,9 @@ public class InMemoryProvisioner implements HostProvisioner {
@Override
public HostSpec allocateHost(String alias) {
- List<Host> defaultHosts = freeNodes.get(defaultResources);
+ List<Host> defaultHosts = freeNodes.get(defaultHostResources);
if (defaultHosts.isEmpty()) throw new IllegalArgumentException("No more hosts with default resources available");
- Host newHost = freeNodes.removeValue(defaultResources, 0);
+ Host newHost = freeNodes.removeValue(defaultHostResources, 0);
return new HostSpec(newHost.hostname(), List.of(alias), Optional.empty());
}
@@ -170,7 +179,7 @@ public class InMemoryProvisioner implements HostProvisioner {
int nodes = failOnOutOfCapacity || required
? requested.nodes()
- : Math.min(requested.nodes(), freeNodes.get(defaultResources).size() + totalAllocatedTo(cluster));
+ : Math.min(requested.nodes(), freeNodes.get(defaultHostResources).size() + totalAllocatedTo(cluster));
if (alwaysReturnOneNode)
nodes = 1;
@@ -220,8 +229,15 @@ public class InMemoryProvisioner implements HostProvisioner {
host.dockerImageRepo());
}
- private List<HostSpec> allocateHostGroup(ClusterSpec clusterGroup, NodeResources requestedResources,
+ // Minimal capacity policies
+ private NodeResources decideResources(NodeResources requestedResources) {
+ if (requestedResources.isUnspecified()) return defaultNodeResources;
+ return requestedResources;
+ }
+
+ private List<HostSpec> allocateHostGroup(ClusterSpec clusterGroup, NodeResources requestedResourcesOrUnspecified,
int nodesInGroup, int startIndex, boolean canFail) {
+ var requestedResources = decideResources(requestedResourcesOrUnspecified);
List<HostSpec> allocation = allocations.getOrDefault(clusterGroup, new ArrayList<>());
allocations.put(clusterGroup, allocation);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java
index e06c14f40c9..c1d5620e020 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java
@@ -4,7 +4,7 @@ package com.yahoo.vespa.model.application.validation.change;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.model.api.ConfigChangeAction;
import com.yahoo.config.model.deploy.DeployState;
-import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.model.VespaModel;
@@ -21,22 +21,22 @@ public class ResourcesReductionValidator implements ChangeValidator {
@Override
public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, DeployState deployState) {
for (var clusterId : current.allClusters()) {
- Capacity currentCapacity = current.provisioned().all().get(clusterId);
- Capacity nextCapacity = next.provisioned().all().get(clusterId);
- if (currentCapacity == null || nextCapacity == null) continue;
- validate(currentCapacity, nextCapacity, clusterId, deployState);
+ if (next.allClusters().contains(clusterId))
+ validate(clusterId, current, next, deployState);
}
return List.of();
}
- private void validate(Capacity current,
- Capacity next,
- ClusterSpec.Id clusterId,
+ private void validate(ClusterSpec.Id clusterId,
+ VespaModel currentModel,
+ VespaModel nextModel,
DeployState deployState) {
- if (current.maxResources().nodeResources().isUnspecified() || next.maxResources().nodeResources().isUnspecified()) {
- // Unspecified resources; compared node count
- int currentNodes = current.maxResources().nodes();
- int nextNodes = next.maxResources().nodes();
+ ClusterResources current = withNodeResources(currentModel.provisioned().all().get(clusterId).maxResources(), clusterId, currentModel);
+ ClusterResources next = withNodeResources(nextModel.provisioned().all().get(clusterId).maxResources(), clusterId, nextModel);
+ if (current.nodeResources().isUnspecified() || next.nodeResources().isUnspecified()) {
+ // Self-hosted - unspecified resources; compare node count
+ int currentNodes = current.nodes();
+ int nextNodes = next.nodes();
if (nextNodes < 0.5 * currentNodes && nextNodes != currentNodes - 1) {
deployState.validationOverrides().invalid(ValidationId.resourcesReduction,
"Size reduction in '" + clusterId.value() + "' is too large: " +
@@ -46,8 +46,8 @@ public class ResourcesReductionValidator implements ChangeValidator {
}
}
else {
- NodeResources currentResources = current.maxResources().totalResources();
- NodeResources nextResources = next.maxResources().totalResources();
+ NodeResources currentResources = current.totalResources();
+ NodeResources nextResources = next.totalResources();
if (nextResources.vcpu() < 0.5 * currentResources.vcpu() ||
nextResources.memoryGb() < 0.5 * currentResources.memoryGb() ||
nextResources.diskGb() < 0.5 * currentResources.diskGb())
@@ -55,10 +55,35 @@ public class ResourcesReductionValidator implements ChangeValidator {
"Resource reduction in '" + clusterId.value() + "' is too large: " +
"To guard against mistakes, the new max resources must be at least 50% of the current " +
"max resources in all dimensions. " +
- "Current: " + currentResources + ", new: " + nextResources,
+ "Current: " + currentResources.withBandwidthGbps(0) + // (don't output bandwidth here)
+ ", new: " + nextResources.withBandwidthGbps(0),
deployState.now());
}
}
+ /**
+ * If the given requested cluster resources does not specify node resources, return them with
+ * the current node resources of the cluster, as that is what unspecified resources actually resolved to.
+ * This will always yield specified node resources on hosted instances and never on self-hosted instances.
+ */
+ private ClusterResources withNodeResources(ClusterResources resources, ClusterSpec.Id id, VespaModel model) {
+ if ( ! resources.nodeResources().isUnspecified()) return resources;
+
+ var containerCluster = model.getContainerClusters().get(id.value());
+ if (containerCluster != null) {
+ if ( ! containerCluster.getContainers().isEmpty())
+ return resources.with(containerCluster.getContainers().get(0).getHostResource().advertisedResources());
+ }
+
+ var contentCluster = model.getContentClusters().get(id.value());
+ if (contentCluster != null) {
+ var searchCluster = contentCluster.getSearch();
+ if ( ! searchCluster.getSearchNodes().isEmpty())
+ return resources.with(searchCluster.getSearchNodes().get(0).getHostResource().advertisedResources());
+ }
+
+ return resources; // only expected for admin clusters
+ }
+
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java
index cf9d2b466b2..4c185e9dfb8 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java
@@ -20,86 +20,151 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
public class ResourcesReductionValidatorTest {
- private final InMemoryProvisioner provisioner = new InMemoryProvisioner(30, new NodeResources(64, 128, 1000, 10), false);
+ private final NodeResources hostResources = new NodeResources(64, 128, 1000, 10);
+ private final InMemoryProvisioner provisioner = new InMemoryProvisioner(30, hostResources, true, InMemoryProvisioner.defaultHostResources);
+ private final InMemoryProvisioner provisionerSelfHosted = new InMemoryProvisioner(30, hostResources, true, NodeResources.unspecified());
+ private final NodeResources defaultResources = InMemoryProvisioner.defaultHostResources;
private final ValidationTester tester = new ValidationTester(provisioner);
@Test
void fail_when_reduction_by_over_50_percent() {
- VespaModel previous = tester.deploy(null, getServices(6, new NodeResources(8, 64, 800, 1)), Environment.prod, null).getFirst();
+ var fromResources = new NodeResources(8, 64, 800, 1);
+ var toResources = new NodeResources(8, 16, 800, 1);
+ VespaModel previous = tester.deploy(null, contentServices(6, fromResources), Environment.prod, null).getFirst();
try {
- tester.deploy(previous, getServices(6, new NodeResources(8, 16, 800, 1)), Environment.prod, null);
+ tester.deploy(previous, contentServices(6, toResources), Environment.prod, null);
fail("Expected exception due to resources reduction");
} catch (IllegalArgumentException expected) {
- assertEquals("resources-reduction: Resource reduction in 'default' is too large: " +
- "To guard against mistakes, the new max resources must be at least 50% " +
- "of the current max resources in all dimensions. " +
- "Current: [vcpu: 48.0, memory: 384.0 Gb, disk 4800.0 Gb, bandwidth: 1.8 Gbps, architecture: x86_64], " +
- "new: [vcpu: 48.0, memory: 96.0 Gb, disk 4800.0 Gb, bandwidth: 1.8 Gbps, architecture: x86_64]. " +
- ValidationOverrides.toAllowMessage(ValidationId.resourcesReduction),
- Exceptions.toMessageString(expected));
+ assertResourceReductionException(expected,
+ fromResources.multipliedBy(6),
+ toResources.multipliedBy(6));
}
}
@Test
void fail_when_reducing_multiple_resources_by_over_50_percent() {
- VespaModel previous = tester.deploy(null, getServices(6,new NodeResources(8, 64, 800, 1)), Environment.prod, null).getFirst();
+ var fromResources = new NodeResources(8, 64, 800, 1);
+ var toResources = new NodeResources(3, 16, 200, 1);
+ VespaModel previous = tester.deploy(null, contentServices(6, fromResources), Environment.prod, null).getFirst();
try {
- tester.deploy(previous, getServices(6, new NodeResources(3, 16, 200, 1)), Environment.prod, null);
+ tester.deploy(previous, contentServices(6, toResources), Environment.prod, null);
fail("Expected exception due to resources reduction");
} catch (IllegalArgumentException expected) {
- assertEquals("resources-reduction: Resource reduction in 'default' is too large: " +
- "To guard against mistakes, the new max resources must be at least 50% " +
- "of the current max resources in all dimensions. " +
- "Current: [vcpu: 48.0, memory: 384.0 Gb, disk 4800.0 Gb, bandwidth: 1.8 Gbps, architecture: x86_64], " +
- "new: [vcpu: 18.0, memory: 96.0 Gb, disk 1200.0 Gb, bandwidth: 1.8 Gbps, architecture: x86_64]. " +
- ValidationOverrides.toAllowMessage(ValidationId.resourcesReduction),
- Exceptions.toMessageString(expected));
+ assertResourceReductionException(expected,
+ fromResources.multipliedBy(6),
+ toResources.multipliedBy(6));
}
}
@Test
void small_resource_decrease_is_allowed() {
- VespaModel previous = tester.deploy(null, getServices(6, new NodeResources(1.5, 64, 800, 1)), Environment.prod, null).getFirst();
- tester.deploy(previous, getServices(6, new NodeResources(.5, 48, 600, 1)), Environment.prod, null);
+ VespaModel previous = tester.deploy(null, contentServices(6, new NodeResources(1.5, 64, 800, 1)), Environment.prod, null).getFirst();
+ tester.deploy(previous, contentServices(6, new NodeResources(.5, 48, 600, 1)), Environment.prod, null);
}
@Test
void reorganizing_resources_is_allowed() {
- VespaModel previous = tester.deploy(null, getServices(12, new NodeResources(2, 10, 100, 1)), Environment.prod, null).getFirst();
- tester.deploy(previous, getServices(4, new NodeResources(6, 30, 300, 1)), Environment.prod, null);
+ VespaModel previous = tester.deploy(null, contentServices(12, new NodeResources(2, 10, 100, 1)), Environment.prod, null).getFirst();
+ tester.deploy(previous, contentServices(4, new NodeResources(6, 30, 300, 1)), Environment.prod, null);
}
@Test
void overriding_resource_decrease() {
- VespaModel previous = tester.deploy(null, getServices(6, new NodeResources(8, 64, 800, 1)), Environment.prod, null).getFirst();
- tester.deploy(previous, getServices(6, new NodeResources(8, 16, 800, 1)), Environment.prod, resourcesReductionOverride); // Allowed due to override
+ VespaModel previous = tester.deploy(null, contentServices(6, new NodeResources(8, 64, 800, 1)), Environment.prod, null).getFirst();
+ tester.deploy(previous, contentServices(6, new NodeResources(8, 16, 800, 1)), Environment.prod, resourcesReductionOverride); // Allowed due to override
}
@Test
- void allowed_to_go_to_not_specifying_resources() {
- VespaModel previous = tester.deploy(null, getServices(6, new NodeResources(1.5, 64, 800, 1)), Environment.prod, null).getFirst();
- tester.deploy(previous, getServices(6, null), Environment.prod, null);
+ void reduction_is_detected_when_going_from_unspecified_resources_container() {
+ NodeResources toResources = defaultResources.withDiskGb(defaultResources.diskGb() / 5);
+ try {
+ VespaModel previous = tester.deploy(null, containerServices(6, null), Environment.prod, null).getFirst();
+ tester.deploy(previous, containerServices(6, toResources), Environment.prod, null);
+ fail("Expected exception due to resources reduction");
+ }
+ catch (IllegalArgumentException expected) {
+ assertResourceReductionException(expected,
+ defaultResources.multipliedBy(6),
+ toResources.multipliedBy(6));
+ }
+ }
+
+ @Test
+ void reduction_is_detected_when_going_to_unspecified_resources_container() {
+ NodeResources fromResources = defaultResources.withVcpu(defaultResources.vcpu() * 3);
+ try {
+ VespaModel previous = tester.deploy(null, containerServices(6, fromResources), Environment.prod, null).getFirst();
+ tester.deploy(previous, containerServices(6, null), Environment.prod, null);
+ fail("Expected exception due to resources reduction");
+ }
+ catch (IllegalArgumentException expected) {
+ assertResourceReductionException(expected,
+ fromResources.multipliedBy(6),
+ defaultResources.multipliedBy(6));
+ }
}
@Test
- void allowed_to_go_from_not_specifying_resources() {
- VespaModel previous = tester.deploy(null, getServices(6, null), Environment.prod, null).getFirst();
- tester.deploy(previous, getServices(6, new NodeResources(1.5, 64, 800, 1)), Environment.prod, null);
+ void reduction_is_detected_when_going_from_unspecified_resources_content() {
+ NodeResources toResources = defaultResources.withDiskGb(defaultResources.diskGb() / 5);
+ try {
+ VespaModel previous = tester.deploy(null, contentServices(6, null), Environment.prod, null).getFirst();
+ tester.deploy(previous, contentServices(6, toResources), Environment.prod, null);
+ fail("Expected exception due to resources reduction");
+ }
+ catch (IllegalArgumentException expected) {
+ assertResourceReductionException(expected,
+ defaultResources.multipliedBy(6),
+ toResources.multipliedBy(6));
+ }
}
@Test
- void testSizeReductionValidation() {
- ValidationTester tester = new ValidationTester(33);
+ void reduction_is_detected_when_going_to_unspecified_resources_content() {
+ NodeResources fromResources = defaultResources.withVcpu(defaultResources.vcpu() * 3);
+ try {
+ VespaModel previous = tester.deploy(null, contentServices(6, fromResources), Environment.prod, null).getFirst();
+ tester.deploy(previous, contentServices(6, null), Environment.prod, null);
+ fail("Expected exception due to resources reduction");
+ }
+ catch (IllegalArgumentException expected) {
+ assertResourceReductionException(expected,
+ fromResources.multipliedBy(6),
+ defaultResources.multipliedBy(6));
+ }
+ }
+
+ @Test
+ void testSizeReductionValidationWithUnspecifiedResourcesHosted() {
+ int fromNodes = 30;
+ int toNodes = 14;
+ try {
+ ValidationTester tester = new ValidationTester(33);
+ VespaModel previous = tester.deploy(null, contentServices(fromNodes, null), Environment.prod, null).getFirst();
+ tester.deploy(previous, contentServices(toNodes, null), Environment.prod, null);
+ fail("Expected exception due to resources reduction");
+ }
+ catch (IllegalArgumentException expected) {
+ assertResourceReductionException(expected,
+ defaultResources.multipliedBy(fromNodes),
+ defaultResources.multipliedBy(toNodes));
+ }
+ }
- VespaModel previous = tester.deploy(null, getServices(30, null), Environment.prod, null).getFirst();
+ /** Emulate a self-hosted setup in only the sense that it does not set node resources on the provisioned hosts. */
+ @Test
+ void testSizeReductionValidationSelfhosted() {
+ var tester = new ValidationTester(provisionerSelfHosted);
+
+ VespaModel previous = tester.deploy(null, contentServices(10, null), Environment.prod, null).getFirst();
try {
- tester.deploy(previous, getServices(14, null), Environment.prod, null);
+ tester.deploy(previous, contentServices(4, null), Environment.prod, null);
fail("Expected exception due to resources reduction");
}
catch (IllegalArgumentException expected) {
assertEquals("resources-reduction: Size reduction in 'default' is too large: " +
"To guard against mistakes, the new max nodes must be at least 50% of the current nodes. " +
- "Current nodes: 30, new nodes: 14. " +
+ "Current nodes: 10, new nodes: 4. " +
ValidationOverrides.toAllowMessage(ValidationId.resourcesReduction),
Exceptions.toMessageString(expected));
}
@@ -109,39 +174,63 @@ public class ResourcesReductionValidatorTest {
void testSizeReductionValidationMinimalDecreaseIsAllowed() {
ValidationTester tester = new ValidationTester(30);
- VespaModel previous = tester.deploy(null, getServices(3, null), Environment.prod, null).getFirst();
- tester.deploy(previous, getServices(2, null), Environment.prod, null);
+ VespaModel previous = tester.deploy(null, contentServices(3, null), Environment.prod, null).getFirst();
+ tester.deploy(previous, contentServices(2, null), Environment.prod, null);
}
@Test
void testOverridingSizeReductionValidation() {
ValidationTester tester = new ValidationTester(33);
- VespaModel previous = tester.deploy(null, getServices(30, null), Environment.prod, null).getFirst();
- tester.deploy(previous, getServices(14, null), Environment.prod, resourcesReductionOverride); // Allowed due to override
+ VespaModel previous = tester.deploy(null, contentServices(30, null), Environment.prod, null).getFirst();
+ tester.deploy(previous, contentServices(14, null), Environment.prod, resourcesReductionOverride); // Allowed due to override
+ }
+
+ private void assertResourceReductionException(Exception e, NodeResources currentResources, NodeResources newResources) {
+ assertEquals("resources-reduction: Resource reduction in 'default' is too large: " +
+ "To guard against mistakes, the new max resources must be at least 50% of the current max " +
+ "resources in all dimensions. " +
+ "Current: " + currentResources.withBandwidthGbps(0) +
+ ", new: " + newResources.withBandwidthGbps(0) + ". " +
+ ValidationOverrides.toAllowMessage(ValidationId.resourcesReduction),
+ Exceptions.toMessageString(e));
}
- private static String getServices(int nodes, NodeResources resources) {
+ private static String containerServices(int nodes, NodeResources resources) {
String resourcesStr = resources == null ?
"" :
String.format(" <resources vcpu='%.0f' memory='%.0fG' disk='%.0fG'/>",
resources.vcpu(), resources.memoryGb(), resources.diskGb());
return "<services version='1.0'>" +
- " <content id='default' version='1.0'>" +
- " <redundancy>1</redundancy>" +
- " <engine>" +
- " <proton/>" +
- " </engine>" +
- " <documents>" +
- " <document type='music' mode='index'/>" +
- " </documents>" +
+ " <container id='default' version='1.0'>" +
" <nodes count='" + nodes + "'>" +
resourcesStr +
" </nodes>" +
- " </content>" +
+ " </container>" +
"</services>";
}
+ private static String contentServices(int nodes, NodeResources resources) {
+ String resourcesStr = resources == null ?
+ "" :
+ String.format(" <resources vcpu='%.0f' memory='%.0fG' disk='%.0fG'/>",
+ resources.vcpu(), resources.memoryGb(), resources.diskGb());
+ return "<services version='1.0'>" +
+ " <content id='default' version='1.0'>" +
+ " <redundancy>1</redundancy>" +
+ " <engine>" +
+ " <proton/>" +
+ " </engine>" +
+ " <documents>" +
+ " <document type='music' mode='index'/>" +
+ " </documents>" +
+ " <nodes count='" + nodes + "'>" +
+ resourcesStr +
+ " </nodes>" +
+ " </content>" +
+ "</services>";
+ }
+
private static final String resourcesReductionOverride =
"<validation-overrides>\n" +
" <allow until='2000-01-03'>resources-reduction</allow>\n" +
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 a31d4cd4e20..48ddf6b8a82 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
@@ -71,7 +71,7 @@ public class VespaModelTester {
}
/** Adds some nodes with resources 1, 3, 10 */
- public Hosts addHosts(int count) { return addHosts(InMemoryProvisioner.defaultResources, count); }
+ public Hosts addHosts(int count) { return addHosts(InMemoryProvisioner.defaultHostResources, count); }
public Hosts addHosts(NodeResources resources, int count) {
return addHosts(Optional.of(new Flavor(resources)), resources, count);
@@ -197,6 +197,7 @@ public class VespaModelTester {
useMaxResources,
alwaysReturnOneNode,
false,
+ NodeResources.unspecified(),
startIndexForClusters,
retiredHostNames);
provisioner.setEnvironment(zone.environment());
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 2f2310c3703..8b2bf9fcbcc 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
@@ -311,6 +311,13 @@ public class NodeResources {
this.gpuResources.plus(other.gpuResources));
}
+ public NodeResources multipliedBy(double factor) {
+ return this.withVcpu(vcpu * factor)
+ .withMemoryGb(memoryGb * factor)
+ .withDiskGb(diskGb * factor)
+ .withBandwidthGbps(bandwidthGbps * factor);
+ }
+
private boolean isInterchangeableWith(NodeResources other) {
ensureSpecified();
other.ensureSpecified();