/** Collect hosts per group */ private Map<Optional<ClusterSpec.Group>, Map<HostResource, ClusterMembership>> collectAllocatedSubgroups(Map<HostResource, ClusterMembership> hostMapping) { Map<Optional<ClusterSpec.Group>, Map<HostResource, ClusterMembership>> hostsPerGroup = new LinkedHashMap<>(); for (Map.Entry<HostResource, ClusterMembership> entry : hostMapping.entrySet()) { Optional<ClusterSpec.Group> group = entry.getValue().cluster().group(); Map<HostResource, ClusterMembership> hostsInGroup = hostsPerGroup.get(group); if (hostsInGroup == null) { hostsInGroup = new LinkedHashMap<>(); hostsPerGroup.put(group, hostsInGroup); } hostsInGroup.put(entry.getKey(), entry.getValue()); } return hostsPerGroup; }
private void validate(Collection<HostSpec> hosts) { for (HostSpec host : hosts) { if ( ! host.membership().isPresent()) throw new IllegalArgumentException("Hosts must be assigned a cluster when activating, but got " + host); if ( ! host.membership().get().cluster().group().isPresent()) throw new IllegalArgumentException("Hosts must be assigned a group when activating, but got " + host); } }
/** Move nodes from unwanted groups to wanted groups to avoid lingering groups consisting of retired nodes */ private void moveToActiveGroup(List<Node> surplusNodes, int wantedGroups, Optional<ClusterSpec.Group> targetGroup) { for (ListIterator<Node> i = surplusNodes.listIterator(); i.hasNext(); ) { Node node = i.next(); ClusterMembership membership = node.allocation().get().membership(); ClusterSpec cluster = membership.cluster(); if (cluster.group().get().index() >= wantedGroups) { ClusterSpec.Group newGroup = targetGroup.orElse(ClusterSpec.Group.from(0)); ClusterMembership newGroupMembership = membership.with(cluster.with(Optional.of(newGroup))); i.set(node.with(node.allocation().get().with(newGroupMembership))); } } }
protected String toStringValue() { return cluster.type().name() + "/" + cluster.id().value() + (cluster.group().isPresent() ? "/" + cluster.group().get().index() : "") + "/" + index + ( cluster.isExclusive() ? "/exclusive" : "") + ( retired ? "/retired" : "") + ( !cluster.rotations().isEmpty() ? "/" + rotationsAsString(cluster.rotations()) : ""); }
/** * Returns whether this node should be accepted into the cluster even if it is not currently desired * (already enough nodes, or wrong flavor). * Such nodes will be marked retired during finalization of the list of accepted nodes. * The conditions for this are * <ul> * <li>This is a content node. These must always be retired before being removed to allow the cluster to * migrate away data. * <li>This is a container node and it is not desired due to having the wrong flavor. In this case this * will (normally) obtain for all the current nodes in the cluster and so retiring before removing must * be used to avoid removing all the current nodes at once, before the newly allocated replacements are * initialized. (In the other case, where a container node is not desired because we have enough nodes we * do want to remove it immediately to get immediate feedback on how the size reduction works out.) * </ul> */ private boolean acceptToRetire(Node node) { if (node.state() != Node.State.active) return false; if (! node.allocation().get().membership().cluster().group().equals(cluster.group())) return false; return (cluster.type() == ClusterSpec.Type.content) || (cluster.type() == ClusterSpec.Type.container && ! hasCompatibleFlavor(node)); }
@Override public List<HostSpec> prepare(ClusterSpec cluster, Capacity requestedCapacity, int groups, ProvisionLogger logger) { if (cluster.group().isPresent() && groups > 1) throw new IllegalArgumentException("Cannot both be specifying a group and ask for groups to be created"); if (requestedCapacity.nodeCount() % groups != 0)
/** * Returns a list of the nodes which are * in groups with index number above or equal the group count */ private List<Node> findNodesInRemovableGroups(ApplicationId application, ClusterSpec requestedCluster, int wantedGroups) { List<Node> surplusNodes = new ArrayList<>(0); for (Node node : nodeRepository.getNodes(application, Node.State.active)) { ClusterSpec nodeCluster = node.allocation().get().membership().cluster(); if ( ! nodeCluster.id().equals(requestedCluster.id())) continue; if ( ! nodeCluster.type().equals(requestedCluster.type())) continue; if (nodeCluster.group().get().index() >= wantedGroups) surplusNodes.add(node); } return surplusNodes; }
private void toSlime(ClusterMembership membership, Cursor object) { object.setString("clustertype", membership.cluster().type().name()); object.setString("clusterid", membership.cluster().id().value()); object.setString("group", String.valueOf(membership.cluster().group().get().index())); object.setLong("index", membership.index()); object.setBool("retired", membership.retired()); }
/** * Ensure sufficient nodes are reserved or active for the given application and cluster * * @return the list of nodes this cluster will have allocated if activated */ // Note: This operation may make persisted changes to the set of reserved and inactive nodes, // but it may not change the set of active nodes, as the active nodes must stay in sync with the // active config model which is changed on activate public List<Node> prepare(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, int wantedGroups) { List<Node> surplusNodes = findNodesInRemovableGroups(application, cluster, wantedGroups); MutableInteger highestIndex = new MutableInteger(findHighestIndex(application, cluster)); List<Node> acceptedNodes = new ArrayList<>(); for (int groupIndex = 0; groupIndex < wantedGroups; groupIndex++) { ClusterSpec clusterGroup = cluster.with(Optional.of(ClusterSpec.Group.from(groupIndex))); List<Node> accepted = groupPreparer.prepare(application, clusterGroup, requestedNodes.fraction(wantedGroups), surplusNodes, highestIndex, spareCount); replace(acceptedNodes, accepted); } moveToActiveGroup(surplusNodes, wantedGroups, cluster.group()); replace(acceptedNodes, retire(surplusNodes)); return acceptedNodes; }
public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity requestedCapacity, int wantedGroups, ProvisionLogger logger) { if (cluster.group().isPresent()) throw new IllegalArgumentException("Node requests cannot specify a group"); if (requestedCapacity.nodeCount() > 0 && requestedCapacity.nodeCount() % wantedGroups != 0) throw new IllegalArgumentException("Requested " + requestedCapacity.nodeCount() + " nodes in " + wantedGroups + " groups, " +
if ( ! offered.allocation().get().owner().equals(application)) continue; // wrong application if ( ! membership.cluster().equalsIgnoringGroupAndVespaVersion(cluster)) continue; // wrong cluster id/type if ((! offeredPriority.isSurplusNode || saturated()) && ! membership.cluster().group().equals(cluster.group())) continue; // wrong group and we can't or have no reason to change it if ( offered.allocation().get().isRemovable()) continue; // don't accept; causes removal if ( indexes.contains(membership.index())) continue; // duplicate index (just to be sure)