From 77120a53191c5ceefa98cbe5d728cebd51901281 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Mon, 23 Jan 2023 23:10:47 +0100 Subject: Validate total max resources --- .../src/main/java/com/yahoo/config/application/api/ValidationId.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'config-model-api/src') 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 b4be99ad20b..71d07d494a0 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,9 +13,8 @@ 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"), // Large reductions in cluster size tensorTypeChange("tensor-type-change"), // Tensor type change - resourcesReduction("resources-reduction"), // Large reductions in node resources (> 50% of the current min resources) + 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) contentClusterRemoval("content-cluster-removal"), // Removal (or id change) of content clusters deploymentRemoval("deployment-removal"), // Removal of production zones from deployment.xml -- cgit v1.2.3 From 5400b26714de2b9edc8960df88cb18807aceb9b8 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Wed, 25 Jan 2023 15:37:28 +0100 Subject: More resource reduction tests --- .../yahoo/config/application/api/ValidationId.java | 1 + .../model/provision/InMemoryProvisioner.java | 38 +++-- .../change/ResourcesReductionValidator.java | 55 ++++-- .../change/ResourcesReductionValidatorTest.java | 189 +++++++++++++++------ .../yahoo/vespa/model/test/VespaModelTester.java | 3 +- .../com/yahoo/config/provision/NodeResources.java | 7 + 6 files changed, 216 insertions(+), 77 deletions(-) (limited to 'config-model-api/src') 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 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> 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 defaultHosts = freeNodes.get(defaultResources); + List 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 allocateHostGroup(ClusterSpec clusterGroup, NodeResources requestedResources, + // Minimal capacity policies + private NodeResources decideResources(NodeResources requestedResources) { + if (requestedResources.isUnspecified()) return defaultNodeResources; + return requestedResources; + } + + private List allocateHostGroup(ClusterSpec clusterGroup, NodeResources requestedResourcesOrUnspecified, int nodesInGroup, int startIndex, boolean canFail) { + var requestedResources = decideResources(requestedResourcesOrUnspecified); List 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 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(), resources.memoryGb(), resources.diskGb()); return "" + - " " + - " 1" + - " " + - " " + - " " + - " " + - " " + - " " + + " " + " " + resourcesStr + " " + - " " + + " " + ""; } + private static String contentServices(int nodes, NodeResources resources) { + String resourcesStr = resources == null ? + "" : + String.format(" ", + resources.vcpu(), resources.memoryGb(), resources.diskGb()); + return "" + + " " + + " 1" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + resourcesStr + + " " + + " " + + ""; + } + private static final String resourcesReductionOverride = "\n" + " resources-reduction\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(); -- cgit v1.2.3