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()) : ""); }
/** 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))); } } }
public ClusterSpec exclusive(boolean exclusive) { return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, rotations); }
private int totalAllocatedTo(ClusterSpec cluster) { int count = 0; for (Map.Entry<ClusterSpec, List<HostSpec>> allocation : allocations.entrySet()) { if ( ! allocation.getKey().type().equals(cluster.type())) continue; if ( ! allocation.getKey().id().equals(cluster.id())) continue; count += allocation.getValue().size(); } return count; }
/** * 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; }
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, " + NodeSpec requestedNodes; if ( requestedCapacity.type() == NodeType.tenant) { int nodeCount = application.instance().isTester() ? 1 : capacityPolicies.decideSize(requestedCapacity, cluster.type()); Flavor flavor = capacityPolicies.decideFlavor(requestedCapacity, cluster); log.log(LogLevel.DEBUG, () -> "Decided flavor for requested tenant nodes: " + flavor); boolean exclusive = capacityPolicies.decideExclusivity(cluster.isExclusive());
"applicationId", applicationId.serializedForm().replace(':', '.'), "app", toApp(applicationId), "clustertype", allocation.get().membership().cluster().type().name(), "clusterid", allocation.get().membership().cluster().id().value()); metric.set("wantToRestart", wantToRestart ? 1 : 0, context); Version wantedVersion = allocation.get().membership().cluster().vespaVersion(); double wantedVersionNumber = getVersionAsNumber(wantedVersion); metric.set("wantedVespaVersion", wantedVersionNumber, context);
/** * 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)); }
NodePrioritizer(List<Node> allNodes, ApplicationId appId, ClusterSpec clusterSpec, NodeSpec nodeSpec, int spares, NameResolver nameResolver) { this.allNodes = Collections.unmodifiableList(allNodes); this.requestedNodes = nodeSpec; this.clusterSpec = clusterSpec; this.appId = appId; this.nameResolver = nameResolver; this.spareHosts = findSpareHosts(allNodes, spares); this.capacity = new DockerHostCapacity(allNodes); long nofFailedNodes = allNodes.stream() .filter(node -> node.state().equals(Node.State.failed)) .filter(node -> node.allocation().isPresent()) .filter(node -> node.allocation().get().owner().equals(appId)) .filter(node -> node.allocation().get().membership().cluster().id().equals(clusterSpec.id())) .count(); long nofNodesInCluster = allNodes.stream() .filter(node -> node.allocation().isPresent()) .filter(node -> node.allocation().get().owner().equals(appId)) .filter(node -> node.allocation().get().membership().cluster().id().equals(clusterSpec.id())) .count(); this.isAllocatingForReplacement = isReplacement(nofNodesInCluster, nofFailedNodes); this.isDocker = isDocker(); }
/** * Provision load balancer(s) for given application. * * If the application has multiple container clusters, one load balancer will be provisioned for each cluster. */ public Map<LoadBalancerId, LoadBalancer> provision(ApplicationId application) { try (Mutex applicationLock = nodeRepository.lock(application)) { try (Mutex loadBalancersLock = db.lockLoadBalancers()) { Map<LoadBalancerId, LoadBalancer> loadBalancers = new LinkedHashMap<>(); for (Map.Entry<ClusterSpec, List<Node>> kv : activeContainers(application).entrySet()) { LoadBalancer loadBalancer = create(application, kv.getKey().id(), kv.getValue()) .with(kv.getKey().rotations()); loadBalancers.put(loadBalancer.id(), loadBalancer); db.writeLoadBalancer(loadBalancer); } return Collections.unmodifiableMap(loadBalancers); } } }
/** 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; }
ClusterMembership membership = offered.allocation().get().membership(); 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)
public Map<HostResource, ClusterMembership> provision(HostSystem hostSystem, ClusterSpec.Type clusterType, ClusterSpec.Id clusterId, DeployLogger logger, Set<RotationName> rotations) { ClusterSpec cluster = ClusterSpec.request(clusterType, clusterId, version, exclusive, rotations); return hostSystem.allocateHosts(cluster, Capacity.fromNodeCount(count, flavor, required, canFail), groups, logger); }
private void toSlime(HostSpec host, Cursor cursor) { cursor.setString(hostSpecHostName, host.hostname()); host.membership().ifPresent(membership -> { cursor.setString(hostSpecMembership, membership.stringValue()); cursor.setString(hostSpecVespaVersion, membership.cluster().vespaVersion().toFullString()); }); host.flavor().ifPresent(flavor -> cursor.setString(hostSpecFlavor, flavor.name())); host.version().ifPresent(version -> cursor.setString(hostSpecCurrentVespaVersion, version.toFullString())); }
/** * If a parent host is given, and it hosts another tenant with an application which requires exclusive access * to the physical host, then we cannot host this application on it. */ private boolean exclusiveTo(TenantName tenant, Optional<String> parentHostname) { if ( ! parentHostname.isPresent()) return true; for (Node nodeOnHost : nodeRepository.list().childrenOf(parentHostname.get())) { if ( ! nodeOnHost.allocation().isPresent()) continue; if ( nodeOnHost.allocation().get().membership().cluster().isExclusive() && ! nodeOnHost.allocation().get().owner().tenant().equals(tenant)) return false; } return true; }
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()); }
/** Returns true if this filter matches the given host properties */ public boolean matches(String hostname, String flavor, Optional<ClusterMembership> membership) { if ( ! hostnames.isEmpty() && ! hostnames.contains(hostname)) return false; if ( ! flavors.isEmpty() && ! flavors.contains(flavor)) return false; if ( ! clusterTypes.isEmpty() && ! (membership.isPresent() && clusterTypes.contains(membership.get().cluster().type()))) return false; if ( ! clusterIds.isEmpty() && ! (membership.isPresent() && clusterIds.contains(membership.get().cluster().id()))) return false; return true; }
@Override protected void maintain() { List<Node> containerNodes = getExpiredNodes(containerExpiry) .stream() .filter(node -> node.allocation().isPresent() && node.allocation().get().membership().cluster().type() == ClusterSpec.Type.container) .collect(Collectors.toList()); List<Node> remainingNodes = getExpiredNodes(defaultExpiry); remainingNodes.removeAll(containerNodes); recycle(containerNodes); recycle(remainingNodes); }
Map<ClusterSpec.Id, Set<Node>> nodesByCluster = getNodesBelongingToApplication(allNodes, applicationId).stream() .collect(Collectors.groupingBy( node -> node.allocation().get().membership().cluster().id(), Collectors.toSet())); Map<ClusterSpec.Id, Set<Node>> retireableNodesByCluster = nodesByCluster.entrySet().stream()