private GroupBuilder collectGroup(boolean isHosted, Optional<ModelElement> groupElement, Optional<ModelElement> nodesElement, String name, String index) {
            StorageGroup group = new StorageGroup(
                    isHosted, name, index,
                    childAsString(groupElement, "distribution.partitions"),
                    booleanAttributeOr(groupElement, VespaDomBuilder.CPU_SOCKET_AFFINITY_ATTRIB_NAME, false),
                    childAsLong(groupElement, VespaDomBuilder.MMAP_NOCORE_LIMIT),
                    childAsBoolean(groupElement, VespaDomBuilder.CORE_ON_OOM),
                    childAsString(groupElement, VespaDomBuilder.NO_VESPAMALLOC),
                    childAsString(groupElement, VespaDomBuilder.VESPAMALLOC),
                    childAsString(groupElement, VespaDomBuilder.VESPAMALLOC_DEBUG),
                    childAsString(groupElement, VespaDomBuilder.VESPAMALLOC_DEBUG_STACKTRACE));

            List<GroupBuilder> subGroups = groupElement.map(modelElement -> collectSubGroups(isHosted, group, modelElement)).orElseGet(List::of);

            List<XmlNodeBuilder> explicitNodes = new ArrayList<>();
            explicitNodes.addAll(collectExplicitNodes(groupElement));
            explicitNodes.addAll(collectExplicitNodes(nodesElement));

            if (!subGroups.isEmpty() && nodesElement.isPresent())
                throw new IllegalArgumentException("A group can contain either explicit subgroups or a nodes specification, but not both.");

            Optional<NodesSpecification> nodeRequirement;
            if (nodesElement.isPresent() && nodesElement.get().stringAttribute("count") != null ) // request these nodes
                nodeRequirement = Optional.of(NodesSpecification.from(nodesElement.get(), context));
            else if (nodesElement.isPresent() && context.getDeployState().isHosted() && context.getDeployState().zone().environment().isManuallyDeployed() ) // default to 1 node
                nodeRequirement = Optional.of(NodesSpecification.from(nodesElement.get(), context));
            else if (nodesElement.isEmpty() && subGroups.isEmpty() && context.getDeployState().isHosted()) // request one node
                nodeRequirement = Optional.of(NodesSpecification.nonDedicated(1, context));
            else if (nodesElement.isPresent() && nodesElement.get().stringAttribute("count") == null && context.getDeployState().isHosted())
                throw new IllegalArgumentException("""
                                                           Clusters in hosted environments must have a <nodes count='N'> tag
                                                           matching all zones, and having no <node> subtags,
                                                           see https://docs.vespa.ai/en/reference/services.html#nodes""");
            else // Nodes or groups explicitly listed - resolve in GroupBuilder
                nodeRequirement = Optional.empty();

            return new GroupBuilder(group, subGroups, explicitNodes, nodeRequirement);
        }
Creates a content group builder from a group and/or nodes element.
These are the possibilities:
<ul>
<li>group and nodes is present: This is a leaf group specifying a set of nodes</li>
<li>only group is present: This is a nonleaf group</li>
<li>only nodes is present: This is the implicitly specified toplevel leaf group, or a set of groups
                           specified using a group count attribute.
<li>Neither element is present: Create a single node.
</ul>

Note: DO NOT change allocation behaviour to allow version X and Y of the config-model to allocate a different
set of nodes. Such changes must be guarded by a common condition (e.g. feature flag) so the behaviour can be
changed simultaneously for all active config models.
config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java