diff options
author | Jon Bratseth <bratseth@gmail.com> | 2023-01-22 14:34:57 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@gmail.com> | 2023-01-22 14:34:57 +0100 |
commit | 1d5806f068309e18b70fee03b7a22e111c180607 (patch) | |
tree | 83eddc958301445e5d9b13ea602f963a39a7f5e2 /config-model | |
parent | 1f2865c97efe67f6f54151c26990226063c85d26 (diff) |
Improve error messages
Diffstat (limited to 'config-model')
8 files changed, 125 insertions, 79 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java index 7a9ee51c29b..352e08af4a3 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java @@ -121,15 +121,24 @@ public class NodesSpecification { } private static ResourceConstraints toResourceConstraints(ModelElement nodesElement) { - var nodes = IntRange.from(nodesElement.stringAttribute("count", "")); - var groups = IntRange.from(nodesElement.stringAttribute("groups", "")); - var groupSize = IntRange.from(nodesElement.stringAttribute("group-size", "")); + var nodes = rangeFrom(nodesElement, "count"); + var groups = rangeFrom(nodesElement, "groups"); + var groupSize = rangeFrom(nodesElement, "group-size"); int defaultMaxGroups = groupSize.isEmpty() ? 1 : nodes.to().orElse(1); // Don't constrain the number of groups if group size is set var min = new ClusterResources(nodes.from().orElse(1), groups.from().orElse(1), nodeResources(nodesElement).getFirst()); var max = new ClusterResources(nodes.to().orElse(1), groups.to().orElse(defaultMaxGroups), nodeResources(nodesElement).getSecond()); return new ResourceConstraints(min, max, groupSize); } + private static IntRange rangeFrom(ModelElement element, String name) { + try { + return IntRange.from(element.stringAttribute(name, "")); + } + catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Illegal " + name + " value", e); + } + } + private record ResourceConstraints(ClusterResources min, ClusterResources max, IntRange groupSize) {} /** Returns the ID of the cluster referencing this node specification, if any */ @@ -153,18 +162,6 @@ public class NodesSpecification { } /** - * Returns a requirement for dedicated nodes taken from the <code>nodes</code> element - * contained in the given parent element, or empty if the parent element is null, or the nodes elements - * is not present. - */ - public static Optional<NodesSpecification> fromParent(ModelElement parentElement, ConfigModelContext context) { - if (parentElement == null) return Optional.empty(); - ModelElement nodesElement = parentElement.child("nodes"); - if (nodesElement == null) return Optional.empty(); - return Optional.of(from(nodesElement, context)); - } - - /** * Returns a requirement for non-dedicated or dedicated nodes taken from the <code>nodes</code> element * contained in the given parent element, or empty if the parent element is null, or the nodes elements * is not present. diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 7b1876db457..62a3d004ac0 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -947,16 +947,21 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } private List<ApplicationContainer> createNodesFromNodeCount(ApplicationContainerCluster cluster, Element containerElement, Element nodesElement, ConfigModelContext context) { - NodesSpecification nodesSpecification = NodesSpecification.from(new ModelElement(nodesElement), context); - ClusterSpec.Id clusterId = ClusterSpec.Id.from(cluster.name()); - ZoneEndpoint zoneEndpoint = zoneEndpoint(context, clusterId); - Map<HostResource, ClusterMembership> hosts = nodesSpecification.provision(cluster.getRoot().hostSystem(), - ClusterSpec.Type.container, - clusterId, - zoneEndpoint, - log, - getZooKeeper(containerElement) != null); - return createNodesFromHosts(hosts, cluster, context.getDeployState()); + try { + NodesSpecification nodesSpecification = NodesSpecification.from(new ModelElement(nodesElement), context); + ClusterSpec.Id clusterId = ClusterSpec.Id.from(cluster.name()); + ZoneEndpoint zoneEndpoint = zoneEndpoint(context, clusterId); + Map<HostResource, ClusterMembership> hosts = nodesSpecification.provision(cluster.getRoot().hostSystem(), + ClusterSpec.Type.container, + clusterId, + zoneEndpoint, + log, + getZooKeeper(containerElement) != null); + return createNodesFromHosts(hosts, cluster, context.getDeployState()); + } + catch (IllegalArgumentException e) { + throw new IllegalArgumentException("In " + cluster, e); + } } private List<ApplicationContainer> createNodesFromNodeType(ApplicationContainerCluster cluster, Element nodesElement, ConfigModelContext context) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionValidator.java index dbe813bfb2d..b4a83efd2aa 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionValidator.java @@ -30,17 +30,18 @@ public class IndexedHierarchicDistributionValidator { public void validate() { validateThatWeHaveOneGroupLevel(); validateThatLeafGroupsHasEqualNumberOfNodes(); - validateThatLeafGroupsCountIsAFactorOfRedundancy(clusterName, redundancy.effectiveFinalRedundancy(), rootGroup.getSubgroups().size()); + validateThatLeafGroupsCountIsAFactorOfRedundancy(redundancy.effectiveFinalRedundancy(), rootGroup.getSubgroups().size()); validateThatRedundancyPerGroupIsEqual(); - validateThatReadyCopiesIsCompatibleWithRedundancy(clusterName, redundancy.effectiveFinalRedundancy(), redundancy.effectiveReadyCopies(), rootGroup.getSubgroups().size()); + validateThatReadyCopiesIsCompatibleWithRedundancy(redundancy.effectiveFinalRedundancy(), redundancy.effectiveReadyCopies(), rootGroup.getSubgroups().size()); } private void validateThatWeHaveOneGroupLevel() { for (StorageGroup group : rootGroup.getSubgroups()) { if (group.getSubgroups().size() > 0) { - throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected all groups under root group '" + - rootGroup.getName() + "' to be leaf groups only containing nodes, but sub group '" + group.getName() + "' contains " + - group.getSubgroups().size() + " sub groups."); + throw new IllegalArgumentException("Expected all groups under root group '" + + rootGroup.getName() + "' to be leaf groups only containing nodes, but sub group '" + + group.getName() + "' contains " + + group.getSubgroups().size() + " sub groups"); } } } @@ -56,18 +57,19 @@ public class IndexedHierarchicDistributionValidator { } if (group.getNodes().size() != previousGroup.getNodes().size()) - throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected leaf groups to contain an equal number of nodes, but leaf group '" + - previousGroup.getName() + "' contains " + previousGroup.getNodes().size() + " node(s) while leaf group '" + - group.getName() + "' contains " + group.getNodes().size() + " node(s)."); + throw new IllegalArgumentException("Expected leaf groups to contain an equal number of nodes, but leaf group '" + + previousGroup.getName() + "' contains " + previousGroup.getNodes().size() + + " node(s) while leaf group '" + group.getName() + + "' contains " + group.getNodes().size() + " node(s)"); previousGroup = group; } } - static public void validateThatLeafGroupsCountIsAFactorOfRedundancy(String clusterName, int totalRedundancy, int subGroups) { + static public void validateThatLeafGroupsCountIsAFactorOfRedundancy(int totalRedundancy, int subGroups) { if (totalRedundancy % subGroups != 0) { - throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected number of leaf groups (" + + throw new IllegalArgumentException("Expected number of leaf groups (" + subGroups + ") to be a factor of redundancy (" + - totalRedundancy + "), but it is not."); + totalRedundancy + "), but it is not"); } } @@ -75,9 +77,10 @@ public class IndexedHierarchicDistributionValidator { int redundancyPerGroup = redundancy.effectiveFinalRedundancy() / rootGroup.getSubgroups().size(); String expPartitions = createDistributionPartitions(redundancyPerGroup, rootGroup.getSubgroups().size()); if (!rootGroup.getPartitions().get().equals(expPartitions)) { - throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected redundancy per leaf group to be " + + throw new IllegalArgumentException("Expected redundancy per leaf group to be " + redundancyPerGroup + ", but it is not according to distribution partitions '" + - rootGroup.getPartitions().get() + "'. Expected distribution partitions should be '" + expPartitions + "'."); + rootGroup.getPartitions().get() + + "'. Expected distribution partitions should be '" + expPartitions + "'"); } } @@ -91,20 +94,17 @@ public class IndexedHierarchicDistributionValidator { return sb.toString(); } - static public void validateThatReadyCopiesIsCompatibleWithRedundancy(String clusterName, int totalRedundancy, int totalReadyCopies, int groupCount) { + static public void validateThatReadyCopiesIsCompatibleWithRedundancy(int totalRedundancy, int totalReadyCopies, int groupCount) { if (totalRedundancy % groupCount != 0) { - throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected equal redundancy per group."); + throw new IllegalArgumentException("Expected equal redundancy per group"); } if (totalReadyCopies % groupCount != 0) { - throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected equal amount of ready copies per group, but " + + throw new IllegalArgumentException("Expected equal amount of ready copies per group, but " + totalReadyCopies + " ready copies is specified with " + groupCount + " groups"); } if (totalReadyCopies == 0) { - throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Warning. No ready copies configured. At least one is required."); + throw new IllegalArgumentException("No ready copies configured. At least 1 is required."); } } - static private String getErrorMsgPrefix(String clusterName) { - return "In indexed content cluster '" + clusterName + "' using hierarchic distribution: "; - } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java b/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java index aef00be5ea9..36090aa7349 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java @@ -204,30 +204,35 @@ public class StorageGroup { } public StorageGroup buildRootGroup(DeployState deployState, RedundancyBuilder redundancyBuilder, ContentCluster owner) { - if (owner.isHosted()) - validateRedundancyAndGroups(deployState.zone().environment()); + try { + if (owner.isHosted()) + validateRedundancyAndGroups(deployState.zone().environment()); - Optional<ModelElement> group = Optional.ofNullable(clusterElement.child("group")); - Optional<ModelElement> nodes = getNodes(clusterElement); + Optional<ModelElement> group = Optional.ofNullable(clusterElement.child("group")); + Optional<ModelElement> nodes = getNodes(clusterElement); - if (group.isPresent() && nodes.isPresent()) - throw new IllegalArgumentException("Both <group> and <nodes> is specified: Only one of these tags can be used in the same configuration"); - if (group.isPresent() && (group.get().integerAttribute("distribution-key") != null)) { - deployState.getDeployLogger().logApplicationPackage(Level.INFO, "'distribution-key' attribute on a content cluster's root group is ignored"); - } + if (group.isPresent() && nodes.isPresent()) + throw new IllegalArgumentException("Both <group> and <nodes> is specified: Only one of these tags can be used in the same configuration"); + if (group.isPresent() && (group.get().integerAttribute("distribution-key") != null)) { + deployState.getDeployLogger().logApplicationPackage(Level.INFO, "'distribution-key' attribute on a content cluster's root group is ignored"); + } - GroupBuilder groupBuilder = collectGroup(owner.isHosted(), group, nodes, null, null); - StorageGroup storageGroup = owner.isHosted() - ? groupBuilder.buildHosted(deployState, owner, Optional.empty()) - : groupBuilder.buildNonHosted(deployState, owner, Optional.empty()); + GroupBuilder groupBuilder = collectGroup(owner.isHosted(), group, nodes, null, null); + StorageGroup storageGroup = owner.isHosted() + ? groupBuilder.buildHosted(deployState, owner, Optional.empty()) + : groupBuilder.buildNonHosted(deployState, owner, Optional.empty()); - Redundancy redundancy = redundancyBuilder.build(owner.getName(), owner.isHosted(), storageGroup.subgroups.size(), - storageGroup.getNumberOfLeafGroups(), storageGroup.countNodes(false)); - owner.setRedundancy(redundancy); - if (storageGroup.partitions.isEmpty() && (redundancy.groups() > 1)) { - storageGroup.partitions = Optional.of(computePartitions(redundancy.finalRedundancy(), redundancy.groups())); + Redundancy redundancy = redundancyBuilder.build(owner.isHosted(), storageGroup.subgroups.size(), + storageGroup.getNumberOfLeafGroups(), storageGroup.countNodes(false)); + owner.setRedundancy(redundancy); + if (storageGroup.partitions.isEmpty() && (redundancy.groups() > 1)) { + storageGroup.partitions = Optional.of(computePartitions(redundancy.finalRedundancy(), redundancy.groups())); + } + return storageGroup; + } + catch (IllegalArgumentException e) { + throw new IllegalArgumentException("In " + owner, e); } - return storageGroup; } private void validateRedundancyAndGroups(Environment environment) { @@ -242,11 +247,11 @@ public class StorageGroup { // Allow dev deployment of self-hosted app (w/o count attribute): absent count => 1 node if (!nodesSpec.hasCountAttribute() && environment == Environment.dev) return; - int minNodesPerGroup = (int)Math.ceil((double)nodesSpec.minResources().nodes() / nodesSpec.minResources().groups()); + int minNodesPerGroup = (int) Math.ceil((double) nodesSpec.minResources().nodes() / nodesSpec.minResources().groups()); if (minNodesPerGroup < redundancy) { - throw new IllegalArgumentException("Cluster '" + clusterElement.stringAttribute("id") + "' " + - "specifies redundancy " + redundancy + ", but it cannot be higher than " + + throw new IllegalArgumentException("This cluster specifies redundancy " + redundancy + + ", but this cannot be higher than " + "the minimum nodes per group, which is " + minNodesPerGroup); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/RedundancyBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/RedundancyBuilder.java index 1948cc1bd71..853a90cd8fa 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/RedundancyBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/RedundancyBuilder.java @@ -37,14 +37,15 @@ public class RedundancyBuilder { } } } - public Redundancy build(String clusterName, boolean isHosted, int subGroups, int leafGroups, int totalNodes) { + public Redundancy build(boolean isHosted, int subGroups, int leafGroups, int totalNodes) { if (isHosted) { return new Redundancy(initialRedundancy, finalRedundancy, readyCopies, leafGroups, totalNodes); } else { subGroups = Math.max(1, subGroups); - IndexedHierarchicDistributionValidator.validateThatLeafGroupsCountIsAFactorOfRedundancy(clusterName, finalRedundancy, subGroups); - IndexedHierarchicDistributionValidator.validateThatReadyCopiesIsCompatibleWithRedundancy(clusterName, finalRedundancy, readyCopies, subGroups); - return new Redundancy(initialRedundancy/subGroups, finalRedundancy/subGroups, readyCopies/subGroups, subGroups, totalNodes); + IndexedHierarchicDistributionValidator.validateThatLeafGroupsCountIsAFactorOfRedundancy(finalRedundancy, subGroups); + IndexedHierarchicDistributionValidator.validateThatReadyCopiesIsCompatibleWithRedundancy(finalRedundancy, readyCopies, subGroups); + return new Redundancy(initialRedundancy/subGroups, finalRedundancy/subGroups, + readyCopies/subGroups, subGroups, totalNodes); } } 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 452a9fcdf87..6f8547c3701 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 @@ -684,6 +684,37 @@ public class ModelProvisioningTest { } @Test + public void testIllegalGroupSize() { + String services = + "<?xml version='1.0' encoding='utf-8' ?>\n" + + "<services>" + + " <admin version='4.0'/>" + + " <container version='1.0' id='foo'>" + + " <nodes count='2'/>" + + " </container>" + + " <content version='1.0' id='bar'>" + + " <redundancy>2</redundancy>" + + " <documents>" + + " <document type='type1' mode='index'/>" + + " </documents>" + + " <nodes count='5' group-size='[2, --]'/>" + + " </content>" + + "</services>"; + + int numberOfHosts = 10; + VespaModelTester tester = new VespaModelTester(); + tester.addHosts(numberOfHosts); + try { + tester.createModel(services, true); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("In content cluster 'bar': Illegal group-size value: " + + "Expected a number or range on the form [min, max], but got '[2, --]': '--' is not an integer", Exceptions.toMessageString(e)); + } + } + + @Test public void testSlobroksOnContainersIfNoContentClusters() { String services = "<?xml version='1.0' encoding='utf-8' ?>\n" + @@ -1193,7 +1224,8 @@ public class ModelProvisioningTest { fail("Expected exception"); } catch (IllegalArgumentException e) { - assertEquals("Cluster 'bar' specifies redundancy 2, but it cannot be higher than the minimum nodes per group, which is 1", Exceptions.toMessageString(e)); + assertEquals("In content cluster 'bar': This cluster specifies redundancy 2, " + + "but this cannot be higher than the minimum nodes per group, which is 1", Exceptions.toMessageString(e)); } } @@ -1789,7 +1821,9 @@ public class ModelProvisioningTest { VespaModel model = tester.createModel(new Zone(Environment.staging, RegionName.from("us-central-1")), services, true); fail("expected failure"); } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().startsWith("Clusters in hosted environments must have a <nodes count='N'> tag")); + assertEquals("In content cluster 'bar': Clusters in hosted environments must have a <nodes count='N'> tag\n" + + "matching all zones, and having no <node> subtags,\nsee https://cloud.vespa.ai/en/reference/services", + Exceptions.toMessageString(e)); } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java index 07c032a52a5..7fba5ba12e9 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java @@ -6,6 +6,7 @@ import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.search.DispatchGroup; import com.yahoo.vespa.model.search.SearchInterface; import com.yahoo.vespa.model.search.SearchNode; +import com.yahoo.yolean.Exceptions; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -176,7 +177,8 @@ public class IndexedHierarchicDistributionTest { getIllegalMultipleGroupsLevelCluster(); fail("Did not get expected Exception"); } catch (Exception e) { - assertTrue(e.getMessage().contains("sub group 'group0' contains 2 sub groups.")); + assertEquals("Expected all groups under root group 'null' to be leaf groups only containing nodes, but sub group 'group0' contains 2 sub groups", + Exceptions.toMessageString(e)); } } @@ -220,7 +222,8 @@ public class IndexedHierarchicDistributionTest { getTwoGroupsCluster(3, 3, "2|*"); fail("Did not get expected Exception"); } catch (Exception e) { - assertTrue(e.getMessage().contains("Expected number of leaf groups (2) to be a factor of redundancy (3)")); + assertEquals("In content cluster 'mycluster': Expected number of leaf groups (2) to be a factor of redundancy (3), but it is not", + Exceptions.toMessageString(e)); } } @@ -240,7 +243,7 @@ public class IndexedHierarchicDistributionTest { getTwoGroupsCluster(4, 3, "2|*"); fail("Did not get expected Exception"); } catch (Exception e) { - assertTrue(e.getMessage().contains("Expected equal amount of ready copies per group")); + assertEquals("In content cluster 'mycluster': Expected equal amount of ready copies per group, but 3 ready copies is specified with 2 groups", Exceptions.toMessageString(e)); } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java index 9fb4eefba75..57ee15a1dc4 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java @@ -21,6 +21,7 @@ import static com.yahoo.config.model.test.TestUtil.joinLines; import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.content.storagecluster.StorageCluster; import com.yahoo.vespa.model.content.utils.ContentClusterUtils; +import com.yahoo.yolean.Exceptions; import org.junit.jupiter.api.Test; @@ -432,8 +433,8 @@ public class StorageClusterTest { ContentClusterUtils.createCluster(xml, root); fail("Did not fail when having both group and nodes"); } catch (RuntimeException e) { - assertEquals("Both <group> and <nodes> is specified: Only one of these tags can be used in the same configuration", - e.getMessage()); + assertEquals("In content cluster 'storage': Both <group> and <nodes> is specified: Only one of these tags can be used in the same configuration", + Exceptions.toMessageString(e)); } } @@ -507,7 +508,7 @@ public class StorageClusterTest { ContentClusterUtils.createCluster(xml, new MockRoot()); fail("Did not get exception with missing distribution element"); } catch (RuntimeException e) { - assertEquals("'distribution' attribute is required with multiple subgroups", e.getMessage()); + assertEquals("In content cluster 'storage': 'distribution' attribute is required with multiple subgroups", Exceptions.toMessageString(e)); } } } |