diff options
author | Martin Polden <mpolden@mpolden.no> | 2020-09-22 14:26:25 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-22 14:26:25 +0200 |
commit | 8cae83209c036e5d3bac4ef5aca05a78666c0111 (patch) | |
tree | 393713384d6c771b3341625057c67acedad367e1 /node-repository | |
parent | a2291b556e31f07be9d207511a2ae5f7dc4946ec (diff) | |
parent | 14c1f947b2906e8cfa38d98ec48ea44bf5fe9e22 (diff) |
Merge pull request #14452 from vespa-engine/mpolden/exclusive-switch
Add switch field to nodes API
Diffstat (limited to 'node-repository')
39 files changed, 379 insertions, 309 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java index b08dc6bbaf2..3b3711d1081 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java @@ -43,6 +43,7 @@ public final class Node { private final Reports reports; private final Optional<String> modelName; private final Optional<TenantName> reservedTo; + private final Optional<String> switchHostname; /** Record of the last event of each type happening to this node */ private final History history; @@ -54,20 +55,21 @@ public final class Node { public static Node createDockerNode(Set<String> ipAddresses, String hostname, String parentHostname, NodeResources resources, NodeType type) { return new Node("fake-" + hostname, new IP.Config(ipAddresses, Set.of()), hostname, Optional.of(parentHostname), new Flavor(resources), Status.initial(), State.reserved, - Optional.empty(), History.empty(), type, new Reports(), Optional.empty(), Optional.empty()); + Optional.empty(), History.empty(), type, new Reports(), Optional.empty(), Optional.empty(), + Optional.empty()); } /** Creates a node in the initial state (provisioned) */ public static Node create(String openStackId, IP.Config ipConfig, String hostname, Optional<String> parentHostname, - Optional<String> modelName, Flavor flavor, Optional<TenantName> reservedTo, NodeType type) { + Optional<String> modelName, Flavor flavor, Optional<TenantName> reservedTo, NodeType type, Optional<String> switchHostname) { return new Node(openStackId, ipConfig, hostname, parentHostname, flavor, Status.initial(), State.provisioned, - Optional.empty(), History.empty(), type, new Reports(), modelName, reservedTo); + Optional.empty(), History.empty(), type, new Reports(), modelName, reservedTo, switchHostname); } /** Creates a node. See also the {@code create} helper methods. */ public Node(String id, IP.Config ipConfig, String hostname, Optional<String> parentHostname, Flavor flavor, Status status, State state, Optional<Allocation> allocation, History history, NodeType type, - Reports reports, Optional<String> modelName, Optional<TenantName> reservedTo) { + Reports reports, Optional<String> modelName, Optional<TenantName> reservedTo, Optional<String> switchHostname) { this.id = Objects.requireNonNull(id, "A node must have an ID"); this.hostname = requireNonEmptyString(hostname, "A node must have a hostname"); this.ipConfig = Objects.requireNonNull(ipConfig, "A node must a have an IP config"); @@ -81,6 +83,7 @@ public final class Node { this.reports = Objects.requireNonNull(reports, "A null reports is not permitted"); this.modelName = Objects.requireNonNull(modelName, "A null modelName is not permitted"); this.reservedTo = Objects.requireNonNull(reservedTo, "reservedTo cannot be null"); + this.switchHostname = requireNonEmptyString(switchHostname, "switchHostname cannot be null"); if (state == State.active) requireNonEmpty(ipConfig.primary(), "Active node " + hostname + " must have at least one valid IP address"); @@ -88,6 +91,7 @@ public final class Node { if (parentHostname.isPresent()) { if (!ipConfig.pool().asSet().isEmpty()) throw new IllegalArgumentException("A child node cannot have an IP address pool"); if (modelName.isPresent()) throw new IllegalArgumentException("A child node cannot have model name set"); + if (switchHostname.isPresent()) throw new IllegalArgumentException("A child node cannot have switch hostname set"); } if (type != NodeType.host && reservedTo.isPresent()) @@ -151,7 +155,7 @@ public final class Node { /** Returns all the reports on this node. */ public Reports reports() { return reports; } - /** Returns the hardware model of this node */ + /** Returns the hardware model of this node, if any */ public Optional<String> modelName() { return modelName; } /** @@ -160,6 +164,11 @@ public final class Node { */ public Optional<TenantName> reservedTo() { return reservedTo; } + /** Returns the hostname of the switch this node is connected to, if any */ + public Optional<String> switchHostname() { + return switchHostname; + } + /** * Returns a copy of this node with wantToRetire and wantToDeprovision set to the given values and updated history. * @@ -217,42 +226,44 @@ public final class Node { /** Returns a node with the status assigned to the given value */ public Node with(Status status) { - return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, reports, modelName, reservedTo); + return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, + reports, modelName, reservedTo, switchHostname); } /** Returns a node with the type assigned to the given value */ public Node with(NodeType type) { - return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, reports, modelName, reservedTo); + return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, + reports, modelName, reservedTo, switchHostname); } /** Returns a node with the flavor assigned to the given value */ public Node with(Flavor flavor) { - return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo); + return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, + reports, modelName, reservedTo, switchHostname); } /** Returns a copy of this with the reboot generation set to generation */ public Node withReboot(Generation generation) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status.withReboot(generation), state, - allocation, history, type, reports, modelName, reservedTo); + allocation, history, type, reports, modelName, reservedTo, switchHostname); } /** Returns a copy of this with the openStackId set */ public Node withOpenStackId(String openStackId) { return new Node(openStackId, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo); + allocation, history, type, reports, modelName, reservedTo, switchHostname); } /** Returns a copy of this with model name set to given value */ public Node withModelName(String modelName) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, Optional.of(modelName), reservedTo); + allocation, history, type, reports, Optional.of(modelName), reservedTo, switchHostname); } /** Returns a copy of this with model name cleared */ public Node withoutModelName() { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, Optional.empty(), reservedTo); + allocation, history, type, reports, Optional.empty(), reservedTo, switchHostname); } /** Returns a copy of this with a history record saying it was detected to be down at this instant */ @@ -278,39 +289,50 @@ public final class Node { */ public Node with(Allocation allocation) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - Optional.of(allocation), history, type, reports, modelName, reservedTo); + Optional.of(allocation), history, type, reports, modelName, reservedTo, switchHostname); } /** Returns a new Node without an allocation. */ public Node withoutAllocation() { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - Optional.empty(), history, type, reports, modelName, reservedTo); + Optional.empty(), history, type, reports, modelName, reservedTo, switchHostname); } /** Returns a copy of this node with IP config set to the given value. */ public Node with(IP.Config ipConfig) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo); + allocation, history, type, reports, modelName, reservedTo, switchHostname); } /** Returns a copy of this node with the parent hostname assigned to the given value. */ public Node withParentHostname(String parentHostname) { return new Node(id, ipConfig, hostname, Optional.of(parentHostname), flavor, status, state, - allocation, history, type, reports, modelName, reservedTo); + allocation, history, type, reports, modelName, reservedTo, switchHostname); } public Node withReservedTo(TenantName tenant) { if (type != NodeType.host) throw new IllegalArgumentException("Only host nodes can be reserved, " + hostname + " has type " + type); return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, Optional.of(tenant)); + allocation, history, type, reports, modelName, Optional.of(tenant), switchHostname); } /** Returns a copy of this node which is not reserved to a tenant */ public Node withoutReservedTo() { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, Optional.empty()); + allocation, history, type, reports, modelName, Optional.empty(), switchHostname); + } + + /** Returns a copy of this node with switch hostname set to given value */ + public Node withSwitchHostname(String switchHostname) { + return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, + allocation, history, type, reports, modelName, reservedTo, Optional.ofNullable(switchHostname)); + } + + /** Returns a copy of this node with switch hostname unset */ + public Node withoutSwitchHostname() { + return withSwitchHostname(null); } /** Returns a copy of this node with the current reboot generation set to the given number at the given instant */ @@ -343,12 +365,12 @@ public final class Node { /** Returns a copy of this node with the given history. */ public Node with(History history) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo); + allocation, history, type, reports, modelName, reservedTo, switchHostname); } public Node with(Reports reports) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo); + allocation, history, type, reports, modelName, reservedTo, switchHostname); } private static Optional<String> requireNonEmptyString(Optional<String> value, String message) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java index 4ec7ddd04c4..8523db7a970 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java @@ -407,7 +407,7 @@ public class NodeRepository extends AbstractComponent { Flavor flavor, Optional<TenantName> reservedTo, NodeType type) { if (ipConfig.primary().isEmpty()) // TODO: Remove this. Only test code hits this path ipConfig = ipConfig.with(nameResolver.getAllByNameOrThrow(hostname)); - return Node.create(openStackId, ipConfig, hostname, parentHostname, Optional.empty(), flavor, reservedTo, type); + return Node.create(openStackId, ipConfig, hostname, parentHostname, Optional.empty(), flavor, reservedTo, type, Optional.empty()); } public Node createNode(String openStackId, String hostname, Optional<String> parentHostname, Flavor flavor, NodeType type) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java index cc62ae67e84..504854108b6 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java @@ -216,7 +216,8 @@ public class CuratorDatabaseClient { toState, toState.isAllocated() ? node.allocation() : Optional.empty(), node.history().recordStateTransition(node.state(), toState, agent, clock.instant()), - node.type(), node.reports(), node.modelName(), node.reservedTo()); + node.type(), node.reports(), node.modelName(), node.reservedTo(), + node.switchHostname()); writeNode(toState, curatorTransaction, node, newNode); writtenNodes.add(newNode); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java index 5a3584b6ff4..0ce81e49bda 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java @@ -79,6 +79,7 @@ public class NodeSerializer { private static final String reportsKey = "reports"; private static final String modelNameKey = "modelName"; private static final String reservedToKey = "reservedTo"; + private static final String switchHostnameKey = "switchHostname"; // Node resource fields private static final String flavorKey = "flavor"; @@ -143,6 +144,7 @@ public class NodeSerializer { node.status().osVersion().current().ifPresent(version -> object.setString(osVersionKey, version.toString())); node.status().osVersion().wanted().ifPresent(version -> object.setString(wantedOsVersionKey, version.toFullString())); node.status().firmwareVerifiedAt().ifPresent(instant -> object.setLong(firmwareCheckKey, instant.toEpochMilli())); + node.switchHostname().ifPresent(switchHostname -> object.setString(switchHostnameKey, switchHostname)); node.reports().toSlime(object, reportsKey); node.modelName().ifPresent(modelName -> object.setString(modelNameKey, modelName)); node.reservedTo().ifPresent(tenant -> object.setString(reservedToKey, tenant.value())); @@ -212,7 +214,8 @@ public class NodeSerializer { nodeTypeFromString(object.field(nodeTypeKey).asString()), Reports.fromSlime(object.field(reportsKey)), modelNameFromSlime(object), - reservedToFromSlime(object.field(reservedToKey))); + reservedToFromSlime(object.field(reservedToKey)), + switchHostnameFromSlime(object.field(switchHostnameKey))); } private Status statusFromSlime(Inspector object) { @@ -227,6 +230,11 @@ public class NodeSerializer { instantFromSlime(object.field(firmwareCheckKey))); } + private Optional<String> switchHostnameFromSlime(Inspector field) { + if (!field.valid()) return Optional.empty(); + return Optional.of(field.asString()); + } + private Flavor flavorFromSlime(Inspector object) { Inspector resources = object.field(resourcesKey); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java index d3faa4d80f5..75f3c892571 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java @@ -99,13 +99,13 @@ public class GroupPreparer { nodeRepository.addNodes(hosts, Agent.application); // Offer the nodes on the newly provisioned hosts, this should be enough to cover the deficit - List<PrioritizableNode> nodes = provisionedHosts.stream() - .map(provisionedHost -> new PrioritizableNode.Builder(provisionedHost.generateNode()) - .parent(provisionedHost.generateHost()) - .newNode(true) - .build()) - .collect(Collectors.toList()); - allocation.offer(nodes); + List<NodeCandidate> candidates = provisionedHosts.stream() + .map(provisionedHost -> new NodeCandidate.Builder(provisionedHost.generateNode()) + .parent(provisionedHost.generateHost()) + .newNode(true) + .build()) + .collect(Collectors.toList()); + allocation.offer(candidates); } if (! allocation.fulfilled() && requestedNodes.canFail()) diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java index a37da10f5f0..b07ce786685 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java @@ -21,8 +21,9 @@ import java.util.Collection; import java.util.Comparator; import java.util.EnumSet; import java.util.HashSet; -import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; @@ -48,8 +49,8 @@ class NodeAllocation { /** The requested nodes of this list */ private final NodeSpec requestedNodes; - /** The nodes this has accepted so far */ - private final Set<PrioritizableNode> nodes = new LinkedHashSet<>(); + /** The node candidates this has accepted so far, keyed on hostname */ + private final Map<String, NodeCandidate> nodes = new LinkedHashMap<>(); /** The number of already allocated nodes accepted and not retired */ private int accepted = 0; @@ -98,36 +99,36 @@ class NodeAllocation { * @param nodesPrioritized the nodes which are potentially on offer. These may belong to a different application etc. * @return the subset of offeredNodes which was accepted, with the correct allocation assigned */ - List<Node> offer(List<PrioritizableNode> nodesPrioritized) { + List<Node> offer(List<NodeCandidate> nodesPrioritized) { List<Node> accepted = new ArrayList<>(); - for (PrioritizableNode node : nodesPrioritized) { - Node offered = node.node; + for (NodeCandidate candidate : nodesPrioritized) { + Node offered = candidate.node; if (offered.allocation().isPresent()) { Allocation allocation = offered.allocation().get(); ClusterMembership membership = allocation.membership(); if ( ! allocation.owner().equals(application)) continue; // wrong application if ( ! membership.cluster().satisfies(cluster)) continue; // wrong cluster id/type - if ((! node.isSurplusNode || saturated()) && ! membership.cluster().group().equals(cluster.group())) continue; // wrong group and we can't or have no reason to change it + if ((! candidate.isSurplusNode || saturated()) && ! membership.cluster().group().equals(cluster.group())) continue; // wrong group and we can't or have no reason to change it if ( offered.state() == Node.State.active && allocation.isRemovable()) continue; // don't accept; causes removal if ( indexes.contains(membership.index())) continue; // duplicate index (just to be sure) boolean resizeable = false; boolean acceptToRetire = false; if (requestedNodes.considerRetiring()) { - resizeable = node.isResizable; - acceptToRetire = acceptToRetire(node); + resizeable = candidate.isResizable; + acceptToRetire = acceptToRetire(candidate); } - if ((! saturated() && hasCompatibleFlavor(node) && requestedNodes.acceptable(offered)) || acceptToRetire) - accepted.add(acceptNode(node, shouldRetire(node), resizeable)); + if ((! saturated() && hasCompatibleFlavor(candidate) && requestedNodes.acceptable(offered)) || acceptToRetire) + accepted.add(acceptNode(candidate, shouldRetire(candidate), resizeable)); } - else if (! saturated() && hasCompatibleFlavor(node)) { + else if (! saturated() && hasCompatibleFlavor(candidate)) { if ( ! nodeResourceLimits.isWithinRealLimits(offered, cluster)) { ++rejectedDueToInsufficientRealResources; continue; } - if ( violatesParentHostPolicy(this.nodes, offered)) { + if ( violatesParentHostPolicy(offered)) { ++rejectedDueToClashingParentHost; continue; } @@ -142,29 +143,29 @@ class NodeAllocation { if (offered.status().wantToRetire()) { continue; } - node.node = offered.allocate(application, - ClusterMembership.from(cluster, highestIndex.add(1)), - requestedNodes.resources().orElse(node.node.resources()), - nodeRepository.clock().instant()); - accepted.add(acceptNode(node, false, false)); + candidate = candidate.withNode(offered.allocate(application, + ClusterMembership.from(cluster, highestIndex.add(1)), + requestedNodes.resources().orElse(candidate.node.resources()), + nodeRepository.clock().instant())); + accepted.add(acceptNode(candidate, false, false)); } } return accepted; } - private boolean shouldRetire(PrioritizableNode node) { + private boolean shouldRetire(NodeCandidate candidate) { if ( ! requestedNodes.considerRetiring()) return false; - if ( ! nodeResourceLimits.isWithinRealLimits(node.node, cluster)) return true; - if (violatesParentHostPolicy(this.nodes, node.node)) return true; - if ( ! hasCompatibleFlavor(node)) return true; - if (node.node.status().wantToRetire()) return true; - if (requestedNodes.isExclusive() && ! hostsOnly(application, node.node.parentHostname())) return true; + if ( ! nodeResourceLimits.isWithinRealLimits(candidate.node, cluster)) return true; + if (violatesParentHostPolicy(candidate.node)) return true; + if ( ! hasCompatibleFlavor(candidate)) return true; + if (candidate.node.status().wantToRetire()) return true; + if (requestedNodes.isExclusive() && ! hostsOnly(application, candidate.node.parentHostname())) return true; return false; } - private boolean violatesParentHostPolicy(Collection<PrioritizableNode> accepted, Node offered) { - return checkForClashingParentHost() && offeredNodeHasParentHostnameAlreadyAccepted(accepted, offered); + private boolean violatesParentHostPolicy(Node offered) { + return checkForClashingParentHost() && offeredNodeHasParentHostnameAlreadyAccepted(offered); } private boolean checkForClashingParentHost() { @@ -173,8 +174,8 @@ class NodeAllocation { ! application.instance().isTester(); } - private boolean offeredNodeHasParentHostnameAlreadyAccepted(Collection<PrioritizableNode> accepted, Node offered) { - for (PrioritizableNode acceptedNode : accepted) { + private boolean offeredNodeHasParentHostnameAlreadyAccepted(Node offered) { + for (NodeCandidate acceptedNode : nodes.values()) { if (acceptedNode.node.parentHostname().isPresent() && offered.parentHostname().isPresent() && acceptedNode.node.parentHostname().get().equals(offered.parentHostname().get())) { return true; @@ -230,21 +231,21 @@ class NodeAllocation { * 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.) */ - private boolean acceptToRetire(PrioritizableNode node) { - if (node.node.state() != Node.State.active) return false; - if (! node.node.allocation().get().membership().cluster().group().equals(cluster.group())) return false; - if (node.node.allocation().get().membership().retired()) return true; // don't second-guess if already retired + private boolean acceptToRetire(NodeCandidate candidate) { + if (candidate.node.state() != Node.State.active) return false; + if (! candidate.node.allocation().get().membership().cluster().group().equals(cluster.group())) return false; + if (candidate.node.allocation().get().membership().retired()) return true; // don't second-guess if already retired return cluster.type().isContent() || - (cluster.type() == ClusterSpec.Type.container && !hasCompatibleFlavor(node)); + (cluster.type() == ClusterSpec.Type.container && !hasCompatibleFlavor(candidate)); } - private boolean hasCompatibleFlavor(PrioritizableNode node) { - return requestedNodes.isCompatible(node.node.flavor(), nodeRepository.flavors()) || node.isResizable; + private boolean hasCompatibleFlavor(NodeCandidate candidate) { + return requestedNodes.isCompatible(candidate.node.flavor(), nodeRepository.flavors()) || candidate.isResizable; } - private Node acceptNode(PrioritizableNode prioritizableNode, boolean wantToRetire, boolean resizeable) { - Node node = prioritizableNode.node; + private Node acceptNode(NodeCandidate candidate, boolean wantToRetire, boolean resizeable) { + Node node = candidate.node; if (node.allocation().isPresent()) // Record the currently requested resources node = node.with(node.allocation().get().withRequestedResources(requestedNodes.resources().orElse(node.resources()))); @@ -271,13 +272,17 @@ class NodeAllocation { // group may be different node = setCluster(cluster, node); } - prioritizableNode.node = node; + candidate = candidate.withNode(node); indexes.add(node.allocation().get().membership().index()); highestIndex.set(Math.max(highestIndex.get(), node.allocation().get().membership().index())); - nodes.add(prioritizableNode); + put(candidate); return node; } + private void put(NodeCandidate candidate) { + nodes.put(candidate.node.hostname(), candidate); + } + private Node resize(Node node) { NodeResources hostResources = allNodes.parentOf(node).get().flavor().resources(); return node.with(new Flavor(requestedNodes.resources().get() @@ -323,36 +328,39 @@ class NodeAllocation { * @return the final list of nodes */ List<Node> finalNodes() { - int currentRetiredCount = (int) nodes.stream().filter(node -> node.node.allocation().get().membership().retired()).count(); + int currentRetiredCount = (int) nodes.values().stream().filter(node -> node.node.allocation().get().membership().retired()).count(); int deltaRetiredCount = requestedNodes.idealRetiredCount(nodes.size(), currentRetiredCount) - currentRetiredCount; if (deltaRetiredCount > 0) { // retire until deltaRetiredCount is 0 - for (PrioritizableNode node : byRetiringPriority(nodes)) { - if ( ! node.node.allocation().get().membership().retired() && node.node.state() == Node.State.active) { - node.node = node.node.retire(Agent.application, nodeRepository.clock().instant()); + for (NodeCandidate candidate : byRetiringPriority(nodes.values())) { + if ( ! candidate.node.allocation().get().membership().retired() && candidate.node.state() == Node.State.active) { + candidate = candidate.withNode(candidate.node.retire(Agent.application, nodeRepository.clock().instant())); + put(candidate); if (--deltaRetiredCount == 0) break; } } } else if (deltaRetiredCount < 0) { // unretire until deltaRetiredCount is 0 - for (PrioritizableNode node : byUnretiringPriority(nodes)) { - if ( node.node.allocation().get().membership().retired() && hasCompatibleFlavor(node) ) { - if (node.isResizable) - node.node = resize(node.node); - node.node = node.node.unretire(); + for (NodeCandidate candidate : byUnretiringPriority(nodes.values())) { + if ( candidate.node.allocation().get().membership().retired() && hasCompatibleFlavor(candidate) ) { + if (candidate.isResizable) + candidate = candidate.withNode(resize(candidate.node)); + candidate = candidate.withNode(candidate.node.unretire()); + put(candidate); if (++deltaRetiredCount == 0) break; } } } - for (PrioritizableNode node : nodes) { + for (NodeCandidate candidate : nodes.values()) { // Set whether the node is exclusive - Allocation allocation = node.node.allocation().get(); - node.node = node.node.with(allocation.with(allocation.membership() - .with(allocation.membership().cluster().exclusive(requestedNodes.isExclusive())))); + Allocation allocation = candidate.node.allocation().get(); + candidate = candidate.withNode(candidate.node.with(allocation.with(allocation.membership() + .with(allocation.membership().cluster().exclusive(requestedNodes.isExclusive()))))); + put(candidate); } - return nodes.stream().map(n -> n.node).collect(Collectors.toList()); + return nodes.values().stream().map(n -> n.node).collect(Collectors.toList()); } List<Node> reservableNodes() { @@ -361,32 +369,28 @@ class NodeAllocation { return nodesFilter(n -> !n.isNewNode && reservableStates.contains(n.node.state())); } - List<Node> surplusNodes() { - return nodesFilter(n -> n.isSurplusNode); - } - List<Node> newNodes() { return nodesFilter(n -> n.isNewNode); } - private List<Node> nodesFilter(Predicate<PrioritizableNode> predicate) { - return nodes.stream() + private List<Node> nodesFilter(Predicate<NodeCandidate> predicate) { + return nodes.values().stream() .filter(predicate) .map(n -> n.node) .collect(Collectors.toList()); } /** Prefer to retire nodes we want the least */ - private List<PrioritizableNode> byRetiringPriority(Set<PrioritizableNode> nodes) { - return nodes.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList()); + private List<NodeCandidate> byRetiringPriority(Collection<NodeCandidate> candidates) { + return candidates.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList()); } /** Prefer to unretire nodes we don't want to retire, and otherwise those with lower index */ - private List<PrioritizableNode> byUnretiringPriority(Set<PrioritizableNode> nodes) { - return nodes.stream() - .sorted(Comparator.comparing((PrioritizableNode n) -> n.node.status().wantToRetire()) - .thenComparing(n -> n.node.allocation().get().membership().index())) - .collect(Collectors.toList()); + private List<NodeCandidate> byUnretiringPriority(Collection<NodeCandidate> candidates) { + return candidates.stream() + .sorted(Comparator.comparing((NodeCandidate n) -> n.node.status().wantToRetire()) + .thenComparing(n -> n.node.allocation().get().membership().index())) + .collect(Collectors.toList()); } public String outOfCapacityDetails() { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java index 9955d75a742..651ab9b1e09 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java @@ -8,11 +8,11 @@ import java.util.List; import java.util.Optional; /** - * A node with additional information required to prioritize it for allocation. + * A node candidate containing the details required to prioritize it for allocation. This is immutable. * * @author smorgrav */ -class PrioritizableNode implements Comparable<PrioritizableNode> { +class NodeCandidate implements Comparable<NodeCandidate> { /** List of host states ordered by preference (ascending) */ private static final List<Node.State> HOST_STATE_PRIORITY = @@ -21,8 +21,7 @@ class PrioritizableNode implements Comparable<PrioritizableNode> { private static final NodeResources zeroResources = new NodeResources(0, 0, 0, 0, NodeResources.DiskSpeed.any, NodeResources.StorageType.any); - // TODO: Make immutable - Node node; + final Node node; /** The free capacity on the parent of this node, before adding this node to it */ private final NodeResources freeParentCapacity; @@ -42,7 +41,7 @@ class PrioritizableNode implements Comparable<PrioritizableNode> { /** This node can be resized to the new NodeResources */ final boolean isResizable; - PrioritizableNode(Node node, NodeResources freeParentCapacity, Optional<Node> parent, boolean violatesSpares, boolean isSurplusNode, boolean isNewNode, boolean isResizeable) { + NodeCandidate(Node node, NodeResources freeParentCapacity, Optional<Node> parent, boolean violatesSpares, boolean isSurplusNode, boolean isNewNode, boolean isResizeable) { if (isResizeable && isNewNode) throw new IllegalArgumentException("A new node cannot be resizable"); @@ -56,12 +55,12 @@ class PrioritizableNode implements Comparable<PrioritizableNode> { } /** - * Compares two prioritizable nodes + * Compare this candidate to another * * @return negative if first priority is higher than second node */ @Override - public int compareTo(PrioritizableNode other) { + public int compareTo(NodeCandidate other) { // First always pick nodes without violation above nodes with violations if (!this.violatesSpares && other.violatesSpares) return -1; if (!other.violatesSpares && this.violatesSpares) return 1; @@ -138,7 +137,12 @@ class PrioritizableNode implements Comparable<PrioritizableNode> { /** Returns the allocation skew of the parent of this after adding this node to it */ double skewWithThis() { return skewWith(node.resources()); } - private boolean lessThanHalfTheHost(PrioritizableNode node) { + /** Returns a copy of this with node set to given value */ + NodeCandidate withNode(Node node) { + return new NodeCandidate(node, freeParentCapacity, parent, violatesSpares, isSurplusNode, isNewNode, isResizable); + } + + private boolean lessThanHalfTheHost(NodeCandidate node) { var n = node.node.resources(); var h = node.parent.get().resources(); if (h.vcpu() < n.vcpu() * 2) return false; @@ -172,8 +176,8 @@ class PrioritizableNode implements Comparable<PrioritizableNode> { @Override public boolean equals(Object other) { if (other == this) return true; - if ( ! (other instanceof PrioritizableNode)) return false; - return this.node.equals(((PrioritizableNode)other).node); + if ( ! (other instanceof NodeCandidate)) return false; + return this.node.equals(((NodeCandidate)other).node); } static class Builder { @@ -222,8 +226,8 @@ class PrioritizableNode implements Comparable<PrioritizableNode> { return this; } - PrioritizableNode build() { - return new PrioritizableNode(node, freeParentCapacity, parent, violatesSpares, isSurplusNode, isNewNode, isResizable); + NodeCandidate build() { + return new NodeCandidate(node, freeParentCapacity, parent, violatesSpares, isSurplusNode, isNewNode, isResizable); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java index 3dc7eefa277..226079e43fe 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java @@ -5,29 +5,28 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; - -import java.util.ArrayList; -import java.util.logging.Level; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.IP; +import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; /** * Builds up data structures necessary for node prioritization. It wraps each node - * up in a PrioritizableNode object with attributes used in sorting. + * up in a {@link NodeCandidate} object with attributes used in sorting. * - * The actual sorting/prioritization is implemented in the PrioritizableNode class as a compare method. + * The prioritization logic is implemented by {@link NodeCandidate}. * * @author smorgrav */ @@ -35,7 +34,7 @@ public class NodePrioritizer { private final static Logger log = Logger.getLogger(NodePrioritizer.class.getName()); - private final Map<Node, PrioritizableNode> nodes = new HashMap<>(); + private final Map<Node, NodeCandidate> nodes = new HashMap<>(); private final LockedNodeList allNodes; private final HostCapacity capacity; private final NodeSpec requestedNodes; @@ -81,8 +80,8 @@ public class NodePrioritizer { this.isDocker = resources(requestedNodes) != null; } - /** Returns the list of nodes sorted by PrioritizableNode::compare */ - List<PrioritizableNode> prioritize() { + /** Returns the list of nodes sorted by {@link NodeCandidate#compareTo(NodeCandidate)} */ + List<NodeCandidate> prioritize() { return nodes.values().stream().sorted().collect(Collectors.toList()); } @@ -92,7 +91,7 @@ public class NodePrioritizer { */ void addSurplusNodes(List<Node> surplusNodes) { for (Node node : surplusNodes) { - PrioritizableNode nodePri = toPrioritizable(node, true, false); + NodeCandidate nodePri = candidateFrom(node, true, false); if (!nodePri.violatesSpares || isAllocatingForReplacement) { nodes.put(node, nodePri); } @@ -143,7 +142,7 @@ public class NodePrioritizer { resources(requestedNodes).with(host.flavor().resources().diskSpeed()) .with(host.flavor().resources().storageType()), NodeType.tenant); - PrioritizableNode nodePri = toPrioritizable(newNode, false, true); + NodeCandidate nodePri = candidateFrom(newNode, false, true); if ( ! nodePri.violatesSpares || isAllocatingForReplacement) { log.log(Level.FINE, "Adding new Docker node " + newNode); nodes.put(newNode, nodePri); @@ -159,7 +158,7 @@ public class NodePrioritizer { .filter(node -> legalStates.contains(node.state())) .filter(node -> node.allocation().isPresent()) .filter(node -> node.allocation().get().owner().equals(application)) - .map(node -> toPrioritizable(node, false, false)) + .map(node -> candidateFrom(node, false, false)) .forEach(prioritizableNode -> nodes.put(prioritizableNode.node, prioritizableNode)); } @@ -168,20 +167,17 @@ public class NodePrioritizer { allNodes.asList().stream() .filter(node -> node.type() == requestedNodes.type()) .filter(node -> node.state() == Node.State.ready) - .map(node -> toPrioritizable(node, false, false)) + .map(node -> candidateFrom(node, false, false)) .filter(n -> !n.violatesSpares || isAllocatingForReplacement) - .forEach(prioritizableNode -> nodes.put(prioritizableNode.node, prioritizableNode)); + .forEach(candidate -> nodes.put(candidate.node, candidate)); } - public List<PrioritizableNode> nodes() { return new ArrayList<>(nodes.values()); } + public List<NodeCandidate> nodes() { return new ArrayList<>(nodes.values()); } - /** - * Convert a list of nodes to a list of node priorities. This includes finding, calculating - * parameters to the priority sorting procedure. - */ - private PrioritizableNode toPrioritizable(Node node, boolean isSurplusNode, boolean isNewNode) { - PrioritizableNode.Builder builder = new PrioritizableNode.Builder(node).surplusNode(isSurplusNode) - .newNode(isNewNode); + /** Create a candidate from given node */ + private NodeCandidate candidateFrom(Node node, boolean isSurplusNode, boolean isNewNode) { + NodeCandidate.Builder builder = new NodeCandidate.Builder(node).surplusNode(isSurplusNode) + .newNode(isNewNode); allNodes.parentOf(node).ifPresent(parent -> { NodeResources parentCapacity = capacity.freeCapacityOf(parent, false); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java index 151fcb4233f..298404a4cb4 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java @@ -38,7 +38,7 @@ public class ProvisionedHost { /** Generate {@link Node} instance representing the provisioned physical host */ public Node generateHost() { - var node = Node.create(id, IP.Config.EMPTY, hostHostname, Optional.empty(), Optional.empty(), hostFlavor, Optional.empty(), NodeType.host); + var node = Node.create(id, IP.Config.EMPTY, hostHostname, Optional.empty(), Optional.empty(), hostFlavor, Optional.empty(), NodeType.host, Optional.empty()); return node.with(node.status().withOsVersion(OsVersion.EMPTY.withCurrent(Optional.of(osVersion)))); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java index 19276a81ef8..834bed00bd0 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java @@ -149,6 +149,8 @@ public class NodePatcher { return patchRequiredDiskSpeed(node, asString(value)); case "reservedTo": return value.type() == Type.NIX ? node.withoutReservedTo() : node.withReservedTo(TenantName.from(value.asString())); + case "switchHostname": + return value.type() == Type.NIX ? node.withoutSwitchHostname() : node.withSwitchHostname(value.asString()); default : throw new IllegalArgumentException("Could not apply field '" + name + "' on a node: No such modifiable field"); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java index 8e3ae6358df..0137bee5fbd 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java @@ -189,6 +189,7 @@ class NodesResponse extends HttpResponse { ipAddressesToSlime(node.ipConfig().pool().asSet(), object.setArray("additionalIpAddresses")); node.reports().toSlime(object, "reports"); node.modelName().ifPresent(modelName -> object.setString("modelName", modelName)); + node.switchHostname().ifPresent(switchHostname -> object.setString("switchHostname", switchHostname)); } private void toSlime(ApplicationId id, Cursor object) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java index 5080dafe2a5..1891b9d2f82 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java @@ -261,8 +261,13 @@ public class NodesV2ApiHandler extends LoggingRequestHandler { modelName, flavorFromSlime(inspector), reservedToFromSlime(inspector.field("reservedTo")), - nodeTypeFromSlime(inspector.field("type")) - ); + nodeTypeFromSlime(inspector.field("type")), + switchHostnameFromSlime(inspector.field("switchHostname"))); + } + + private Optional<String> switchHostnameFromSlime(Inspector field) { + if (!field.valid()) return Optional.empty(); + return Optional.of(field.asString()); } private Flavor flavorFromSlime(Inspector inspector) { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java index 0a127eacae1..cc755c01405 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java @@ -32,7 +32,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import static com.yahoo.config.provision.NodeResources.StorageType.local; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -51,7 +50,7 @@ class AutoscalingTester { public AutoscalingTester(NodeResources hostResources, HostResourcesCalculator resourcesCalculator) { this(new Zone(Environment.prod, RegionName.from("us-east")), List.of(new Flavor("hostFlavor", hostResources)), resourcesCalculator); provisioningTester.makeReadyNodes(20, "hostFlavor", NodeType.host, 8); - provisioningTester.deployZoneApp(); + provisioningTester.activateTenantHosts(); } public AutoscalingTester(Zone zone, List<Flavor> flavors) { @@ -87,14 +86,14 @@ class AutoscalingTester { List<HostSpec> hosts = provisioningTester.prepare(application, cluster, Capacity.from(new ClusterResources(nodes, groups, resources))); for (HostSpec host : hosts) makeReady(host.hostname()); - provisioningTester.deployZoneApp(); + provisioningTester.activateTenantHosts(); provisioningTester.activate(application, hosts); return hosts; } public void makeReady(String hostname) { Node node = nodeRepository().getNode(hostname).get(); - nodeRepository().write(node.with(new IP.Config(Set.of("::" + 0 + ":0"), Set.of())), nodeRepository().lock(node)); + provisioningTester.patchNode(node, (n) -> n.with(new IP.Config(Set.of("::" + 0 + ":0"), Set.of()))); Node host = nodeRepository().getNode(node.parentHostname().get()).get(); host = host.with(new IP.Config(Set.of("::" + 0 + ":0"), Set.of("::" + 0 + ":2"))); if (host.state() == Node.State.provisioned) diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java index 976aeb2346a..eaad4526591 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java @@ -6,10 +6,7 @@ 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.config.provision.NodeType; import com.yahoo.test.ManualClock; -import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodeRepositoryTester; import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester; import org.junit.Test; @@ -19,14 +16,17 @@ import java.util.List; import static org.junit.Assert.assertEquals; +/** + * @author bratseth + */ public class NodeMetricsDbTest { @Test public void testNodeMetricsDb() { ProvisioningTester tester = new ProvisioningTester.Builder().build(); - tester.makeReadyHosts(10, new NodeResources(10, 100, 1000, 10)).deployZoneApp(); + tester.makeReadyHosts(10, new NodeResources(10, 100, 1000, 10)) + .activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); - tester.deployZoneApp(); var hosts = tester.activate(app1, ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("7.0").build(), diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java index d418d818ef3..fea3e8da70a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java @@ -5,7 +5,6 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.NodeResources; -import com.yahoo.vdslib.state.NodeState; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester; import com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock; @@ -29,7 +28,7 @@ public class NodeMetricsFetcherTest { NodeMetricsFetcher fetcher = new NodeMetricsFetcher(tester.nodeRepository(), orchestrator, httpClient); tester.makeReadyNodes(4, resources); // Creates (in order) host-1.yahoo.com, host-2.yahoo.com, host-3.yahoo.com, host-4.yahoo.com - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application1 = tester.makeApplicationId(); ApplicationId application2 = tester.makeApplicationId(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java index 88195cf0ed9..833daebc37a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java @@ -65,7 +65,7 @@ public class AutoscalingMaintainerTest { assertTrue(deployer.lastDeployTime(app2).isEmpty()); tester.makeReadyNodes(20, "flt", NodeType.host, 8); - tester.deployZoneApp(); + tester.activateTenantHosts(); tester.deploy(app1, cluster1, Capacity.from(new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)), new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)), diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java index 45da1f1d3ee..9978f37e835 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java @@ -179,7 +179,7 @@ public class DynamicProvisioningMaintainerTest { tester.maintainer.maintain(); // Resume provisioning of new hosts List<Node> provisioned = tester.nodeRepository.list().state(Node.State.provisioned).asList(); tester.nodeRepository.setReady(provisioned, Agent.system, this.getClass().getSimpleName()); - tester.provisioningTester.deployZoneApp(); + tester.provisioningTester.activateTenantHosts(); // Allocating nodes to a host does not result in provisioning of additional capacity ApplicationId application = tester.provisioningTester.makeApplicationId(); @@ -273,7 +273,8 @@ public class DynamicProvisioningMaintainerTest { false)); var ipConfig = new IP.Config(state == Node.State.active ? Set.of("::1") : Set.of(), Set.of()); return new Node("fake-id-" + hostname, ipConfig, hostname, parentHostname, flavor, Status.initial(), - state, allocation, History.empty(), nodeType, new Reports(), Optional.empty(), Optional.empty()); + state, allocation, History.empty(), nodeType, new Reports(), Optional.empty(), Optional.empty(), + Optional.empty()); } private long provisionedHostsMatching(NodeResources resources) { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java index 3d17cbf0217..056fe041377 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java @@ -133,7 +133,7 @@ public class InactiveAndFailedExpirerTest { // Flag one node for retirement and redeploy { Node toRetire = tester.getNodes(applicationId, Node.State.active).asList().get(0); - tester.patchNode(toRetire.withWantToRetire(true, Agent.operator, tester.clock().instant())); + tester.patchNode(toRetire, (node) -> node.withWantToRetire(true, Agent.operator, tester.clock().instant())); List<HostSpec> hostSpecs = tester.prepare(applicationId, cluster, Capacity.from(new ClusterResources(2, 1, nodeResources))); tester.activate(applicationId, new HashSet<>(hostSpecs)); } @@ -203,9 +203,7 @@ public class InactiveAndFailedExpirerTest { assertEquals(2, inactiveNodes.size()); // Nodes marked for deprovisioning are moved to parked - tester.nodeRepository().write(inactiveNodes.stream() - .map(node -> node.withWantToRetire(true, true, Agent.system, tester.clock().instant())) - .collect(Collectors.toList()), () -> {}); + tester.patchNodes(inactiveNodes, (node) -> node.withWantToRetire(true, true, Agent.system, tester.clock().instant())); tester.advanceTime(Duration.ofMinutes(11)); new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10), new TestMetric()).run(); assertEquals(2, tester.nodeRepository().getNodes(Node.State.parked).size()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java index 20c9d24d1b6..6897a293525 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java @@ -163,7 +163,7 @@ public class MetricsReporterTest { Set<String> ipAddressPool = Set.of("::2", "::3", "::4", "::5"); Node dockerHost = Node.create("openStackId1", new IP.Config(Set.of("::1"), ipAddressPool), "dockerHost", - Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host); + Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host, Optional.empty()); nodeRepository.addNodes(List.of(dockerHost), Agent.system); nodeRepository.dirtyRecursively("dockerHost", Agent.system, getClass().getSimpleName()); nodeRepository.setReady("dockerHost", Agent.system, getClass().getSimpleName()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java index 218812f9a3d..f795dbaaa1c 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java @@ -97,13 +97,7 @@ public class OsUpgradeActivatorTest { } private void completeUpgradeOf(List<Node> nodes) { - for (var node : nodes) { - try (var lock = tester.nodeRepository().lock(node)) { - node = tester.nodeRepository().getNode(node.hostname()).get(); - node = node.with(node.status().withVespaVersion(node.allocation().get().membership().cluster().vespaVersion())); - tester.nodeRepository().write(node, lock); - } - } + tester.patchNodes(nodes, (node) -> node.with(node.status().withVespaVersion(node.allocation().get().membership().cluster().vespaVersion()))); } private Stream<Node> streamUpdatedNodes(List<Node> nodes) { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java index 05e5b4829e9..65b79c2df04 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java @@ -54,7 +54,7 @@ public class RebalancerTest { // --- Making a more suitable node configuration available causes rebalancing Node newCpuHost = tester.makeReadyNode("cpu"); - tester.deployZoneApp(); + tester.activateTenantHosts(); tester.maintain(); assertTrue("Rebalancer retired the node we wanted to move away from", tester.isNodeRetired(cpuSkewedNode)); @@ -74,7 +74,7 @@ public class RebalancerTest { // --- Adding a more suitable node reconfiguration causes no action as the system is not stable Node memSkewedNode = tester.getNode(memoryApp); Node newMemHost = tester.makeReadyNode("mem"); - tester.deployZoneApp(); + tester.activateTenantHosts(); tester.maintain(); assertFalse("No rebalancing happens because cpuSkewedNode is still retired", tester.isNodeRetired(memSkewedNode)); @@ -117,7 +117,7 @@ public class RebalancerTest { // --- Making a more suitable node configuration available causes rebalancing Node newCpuHost = tester.makeReadyNode("cpu"); - tester.deployZoneApp(); + tester.activateTenantHosts(); tester.deployApp(cpuApp, false /* skip advancing clock after deployment */); tester.maintain(); @@ -152,7 +152,7 @@ public class RebalancerTest { deployer = new MockDeployer(tester.provisioner(), tester.clock(), apps); rebalancer = new Rebalancer(deployer, tester.nodeRepository(), metric, tester.clock(), Duration.ofMinutes(1)); tester.makeReadyNodes(3, "flat", NodeType.host, 8); - tester.deployZoneApp(); + tester.activateTenantHosts(); } void maintain() { rebalancer.maintain(); } @@ -163,7 +163,7 @@ public class RebalancerTest { NodeRepository nodeRepository() { return tester.nodeRepository(); } - void deployZoneApp() { tester.deployZoneApp(); } + void activateTenantHosts() { tester.activateTenantHosts(); } void deployApp(ApplicationId id) { deployApp(id, true); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java index 31a9fcb8999..bd2afb5d1c8 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java @@ -47,7 +47,7 @@ public class ScalingSuggestionsMaintainerTest { NodeMetricsDb nodeMetricsDb = new NodeMetricsDb(tester.nodeRepository()); tester.makeReadyNodes(20, "flt", NodeType.host, 8); - tester.deployZoneApp(); + tester.activateTenantHosts(); tester.deploy(app1, cluster1, Capacity.from(new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)), new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)), diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/node/IPTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/node/IPTest.java index fcf2ab5a52d..4602a8f3560 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/node/IPTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/node/IPTest.java @@ -179,7 +179,7 @@ public class IPTest { private static Node createNode(Set<String> ipAddresses) { return Node.create("id1", new IP.Config(Set.of("127.0.0.1"), ipAddresses), "host1", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("default"), - Optional.empty(), NodeType.host); + Optional.empty(), NodeType.host, Optional.empty()); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java index 6a41e766ace..715ecdb5949 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; @@ -273,20 +272,11 @@ public class OsVersionsTest { } private void setWantedVersion(List<Node> nodes, Version wantedVersion) { - writeNode(nodes, node -> node.with(node.status().withOsVersion(node.status().osVersion().withWanted(Optional.of(wantedVersion))))); + tester.patchNodes(nodes, node -> node.with(node.status().withOsVersion(node.status().osVersion().withWanted(Optional.of(wantedVersion))))); } private void setCurrentVersion(List<Node> nodes, Version currentVersion) { - writeNode(nodes, node -> node.with(node.status().withOsVersion(node.status().osVersion().withCurrent(Optional.of(currentVersion))))); - } - - private void writeNode(List<Node> nodes, UnaryOperator<Node> updateFunc) { - for (var node : nodes) { - try (var lock = tester.nodeRepository().lock(node)) { - node = tester.nodeRepository().getNode(node.hostname()).get(); - tester.nodeRepository().write(updateFunc.apply(node), lock); - } - } + tester.patchNodes(nodes, node -> node.with(node.status().withOsVersion(node.status().osVersion().withCurrent(Optional.of(currentVersion))))); } private void completeUpgradeOf(List<Node> nodes) { @@ -294,7 +284,8 @@ public class OsVersionsTest { } private void completeUpgradeOf(List<Node> nodes, NodeType nodeType) { - writeNode(nodes, (node) -> { + // Complete upgrade by deprovisioning stale hosts and provisioning new ones + tester.patchNodes(nodes, (node) -> { Optional<Version> wantedOsVersion = node.status().osVersion().wanted(); if (node.status().wantToDeprovision()) { // Complete upgrade by deprovisioning stale hosts and provisioning new ones diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java index dbbad0b8982..97c9bef0dd0 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java @@ -57,7 +57,7 @@ public class NodeSerializerTest { private final ManualClock clock = new ManualClock(); @Test - public void testProvisionedNodeSerialization() { + public void provisioned_node_serialization() { Node node = createNode(); Node copy = nodeSerializer.fromJson(Node.State.provisioned, nodeSerializer.toJson(node)); @@ -69,7 +69,7 @@ public class NodeSerializerTest { } @Test - public void testReservedNodeSerialization() { + public void reserved_node_serialization() { Node node = createNode(); NodeResources requestedResources = new NodeResources(1.2, 3.4, 5.6, 7.8, NodeResources.DiskSpeed.any); @@ -110,7 +110,7 @@ public class NodeSerializerTest { } @Test - public void testRebootAndRestartAndTypeNoCurrentValuesSerialization() { + public void reboot_and_restart_and_type_no_current_values_serialization() { String nodeData = "{\n" + " \"type\" : \"tenant\",\n" + @@ -158,7 +158,7 @@ public class NodeSerializerTest { } @Test - public void testRetiredNodeSerialization() { + public void retired_node_serialization() { Node node = createNode(); clock.advance(Duration.ofMinutes(3)); @@ -185,7 +185,7 @@ public class NodeSerializerTest { } @Test - public void testAssimilatedDeserialization() { + public void assimilated_node_deserialization() { Node node = nodeSerializer.fromJson(Node.State.active, ("{\n" + " \"type\": \"tenant\",\n" + " \"hostname\": \"assimilate2.vespahosted.yahoo.tld\",\n" + @@ -211,7 +211,7 @@ public class NodeSerializerTest { } @Test - public void testSetFailCount() { + public void fail_count() { Node node = createNode(); node = node.allocate(ApplicationId.from(TenantName.from("myTenant"), ApplicationName.from("myApplication"), @@ -229,7 +229,7 @@ public class NodeSerializerTest { @Test public void serialize_parentHostname() { final String parentHostname = "parent.yahoo.com"; - Node node = Node.create("myId", new IP.Config(Set.of("127.0.0.1"), Set.of()), "myHostname", Optional.of(parentHostname), Optional.empty(), nodeFlavors.getFlavorOrThrow("default"), Optional.empty(), NodeType.tenant); + Node node = Node.create("myId", new IP.Config(Set.of("127.0.0.1"), Set.of()), "myHostname", Optional.of(parentHostname), Optional.empty(), nodeFlavors.getFlavorOrThrow("default"), Optional.empty(), NodeType.tenant, Optional.empty()); Node deserializedNode = nodeSerializer.fromJson(State.provisioned, nodeSerializer.toJson(node)); assertEquals(parentHostname, deserializedNode.parentHostname().get()); @@ -392,7 +392,7 @@ public class NodeSerializerTest { } @Test - public void testNodeWithNetworkPorts() { + public void network_ports_serialization() { Node node = createNode(); List<NetworkPorts.Allocation> list = new ArrayList<>(); list.add(new NetworkPorts.Allocation(8080, "container", "default/0", "http")); @@ -416,6 +416,15 @@ public class NodeSerializerTest { assertEquals(list, listCopy); } + @Test + public void switch_hostname_serialization() { + Node node = nodeSerializer.fromJson(State.active, nodeSerializer.toJson(createNode())); + assertFalse(node.switchHostname().isPresent()); + String switchHostname = "switch0.example.com"; + node = node.withSwitchHostname(switchHostname); + node = nodeSerializer.fromJson(State.active, nodeSerializer.toJson(node)); + assertEquals(switchHostname, node.switchHostname().get()); + } private byte[] createNodeJson(String hostname, String... ipAddress) { String ipAddressJsonPart = ""; @@ -441,8 +450,8 @@ public class NodeSerializerTest { Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("default"), - Optional.empty(), NodeType.tenant - ); + Optional.empty(), NodeType.tenant, + Optional.empty()); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java index d165f865432..505e53c9195 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java @@ -165,7 +165,7 @@ public class AclProvisioningTest { public void trusted_nodes_for_application_with_load_balancer() { // Provision hosts and containers var hosts = tester.makeReadyNodes(2, "default", NodeType.host); - tester.deployZoneApp(); + tester.activateTenantHosts(); for (var host : hosts) { tester.makeReadyVirtualDockerNodes(2, new NodeResources(2, 8, 50, 1), host.hostname()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java index b113c281289..051420d694d 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java @@ -80,7 +80,8 @@ public class AllocationSimulator { var ipConfig = new IP.Config(Set.of("127.0.0.1"), parent.isPresent() ? Set.of() : getAdditionalIP()); return new Node("fake", ipConfig, hostname, parent, flavor, Status.initial(), parent.isPresent() ? Node.State.ready : Node.State.active, allocation(tenant, flavor), History.empty(), - parent.isPresent() ? NodeType.tenant : NodeType.host, new Reports(), Optional.empty(), Optional.empty()); + parent.isPresent() ? NodeType.tenant : NodeType.host, new Reports(), Optional.empty(), Optional.empty(), + Optional.empty()); } private Set<String> getAdditionalIP() { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java index cd6ae587b04..d5437296620 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java @@ -27,7 +27,7 @@ public class DockerImagesTest { // Host uses tenant default image (for preload purposes) var defaultImage = DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"); var hosts = tester.makeReadyNodes(2, "default", NodeType.host); - tester.deployZoneApp(); + tester.activateTenantHosts(); for (var host : hosts) { assertEquals(defaultImage, tester.nodeRepository().dockerImages().dockerImageFor(host.type())); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningCompleteHostCalculatorTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningCompleteHostCalculatorTest.java index 24cdc5c8fd0..2588818a9d3 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningCompleteHostCalculatorTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningCompleteHostCalculatorTest.java @@ -30,7 +30,7 @@ public class DockerProvisioningCompleteHostCalculatorTest { .resourcesCalculator(new CompleteResourcesCalculator(hostFlavor)) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(9, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(9, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java index e566172b524..6ae78f9019c 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java @@ -20,7 +20,6 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; -import com.yahoo.vespa.hosted.provision.NodeRepository; import org.junit.Test; import java.util.HashSet; @@ -123,7 +122,7 @@ public class DockerProvisioningTest { tester.makeReadyNodes(10, resources, Optional.of(tenant1), NodeType.host, 1); tester.makeReadyNodes(10, resources, Optional.empty(), NodeType.host, 1); - tester.deployZoneApp(); + tester.activateTenantHosts(); Version wantedVespaVersion = Version.fromString("6.39"); List<HostSpec> nodes = tester.prepare(application2_1, @@ -310,7 +309,7 @@ public class DockerProvisioningTest { .resourcesCalculator(3, 0) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(2, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(2, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -330,7 +329,7 @@ public class DockerProvisioningTest { .resourcesCalculator(3, 0) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(9, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(9, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -365,7 +364,7 @@ public class DockerProvisioningTest { .resourcesCalculator(3, 0) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(2, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(2, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -387,7 +386,7 @@ public class DockerProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .flavors(List.of(new Flavor(r))) .build(); - tester.makeReadyHosts(5, r).deployZoneApp(); + tester.makeReadyHosts(5, r).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.container, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -420,7 +419,7 @@ public class DockerProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .flavors(List.of(new Flavor(r))) .build(); - tester.makeReadyHosts(4, r).deployZoneApp(); + tester.makeReadyHosts(4, r).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(clusterType, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java index 29e371dd937..7e416bfc397 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java @@ -66,7 +66,7 @@ public class DynamicDockerAllocationTest { .spareCount(spareCount) .build(); tester.makeReadyNodes(4, "host-small", NodeType.host, 32); - tester.deployZoneApp(); + tester.activateTenantHosts(); List<Node> dockerHosts = tester.nodeRepository().getNodes(NodeType.host, Node.State.active); NodeResources flavor = new NodeResources(1, 4, 100, 1); @@ -109,7 +109,7 @@ public class DynamicDockerAllocationTest { public void relocate_failed_nodes() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(5, "host-small", NodeType.host, 32); - tester.deployZoneApp(); + tester.activateTenantHosts(); List<Node> dockerHosts = tester.nodeRepository().getNodes(NodeType.host, Node.State.active); NodeResources resources = new NodeResources(1, 4, 100, 0.3); @@ -158,7 +158,7 @@ public class DynamicDockerAllocationTest { tester.makeReadyNodes(3, "flt", NodeType.host, 8); // cpu: 30, mem: 30 tester.makeReadyNodes(3, "cpu", NodeType.host, 8); // cpu: 40, mem: 20 tester.makeReadyNodes(3, "mem", NodeType.host, 8); // cpu: 20, mem: 40 - tester.deployZoneApp(); + tester.activateTenantHosts(); NodeResources fltResources = new NodeResources(6, 6, 10, 0.1); NodeResources cpuResources = new NodeResources(8, 4, 10, 0.1); NodeResources memResources = new NodeResources(4, 8, 10, 0.1); @@ -201,7 +201,7 @@ public class DynamicDockerAllocationTest { public void do_not_relocate_nodes_from_spare_if_no_where_to_relocate_them() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(2, "host-small", NodeType.host, 32); - tester.deployZoneApp(); + tester.activateTenantHosts(); List<Node> dockerHosts = tester.nodeRepository().getNodes(NodeType.host, Node.State.active); NodeResources flavor = new NodeResources(1, 4, 100, 1); @@ -228,7 +228,7 @@ public class DynamicDockerAllocationTest { public void multiple_groups_are_on_separate_parent_hosts() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(5, "host-small", NodeType.host, 32); - tester.deployZoneApp(); + tester.activateTenantHosts(); //Deploy an application having 6 nodes (3 nodes in 2 groups). We only have 5 docker hosts available ApplicationId application1 = tester.makeApplicationId(); @@ -249,7 +249,7 @@ public class DynamicDockerAllocationTest { // Setup test ApplicationId application1 = tester.makeApplicationId(); tester.makeReadyNodes(5, "host-small", NodeType.host, 32); - tester.deployZoneApp(); + tester.activateTenantHosts(); NodeResources flavor = new NodeResources(1, 4, 100, 1); // Deploy initial state (can max deploy 3 nodes due to redundancy requirements) @@ -278,7 +278,7 @@ public class DynamicDockerAllocationTest { public void non_prod_zones_do_not_have_spares() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.perf, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(3, "host-small", NodeType.host, 32); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application1 = tester.makeApplicationId(); List<HostSpec> hosts = tester.prepare(application1, clusterSpec("myContent.t1.a1"), 3, 1, new NodeResources(1, 4, 100, 1)); tester.activate(application1, ImmutableSet.copyOf(hosts)); @@ -291,7 +291,7 @@ public class DynamicDockerAllocationTest { public void cd_uses_slow_disk_nodes_for_docker_hosts() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(SystemName.cd, Environment.test, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(4, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.slow)), NodeType.host, 10, true); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application1 = tester.makeApplicationId(); List<HostSpec> hosts = tester.prepare(application1, clusterSpec("myContent.t1.a1"), 3, 1, new NodeResources(1, 4, 100, 1)); tester.activate(application1, ImmutableSet.copyOf(hosts)); @@ -311,7 +311,7 @@ public class DynamicDockerAllocationTest { public void provision_dual_stack_containers() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(2, "host-large", NodeType.host, 10, true); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); List<HostSpec> hosts = tester.prepare(application, clusterSpec("myContent.t1.a1"), 2, 1, new NodeResources(1, 4, 100, 1)); @@ -342,7 +342,7 @@ public class DynamicDockerAllocationTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.fast)), NodeType.host, 10, true); tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.slow)), NodeType.host, 10, true); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("1").build(); @@ -359,7 +359,7 @@ public class DynamicDockerAllocationTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.fast)), NodeType.host, 10, true); tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.slow)), NodeType.host, 10, true); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("1").build(); @@ -381,7 +381,7 @@ public class DynamicDockerAllocationTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.dev, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.fast)), NodeType.host, 10, true); tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 8, 120, 1, NodeResources.DiskSpeed.slow)), NodeType.host, 10, true); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("1").build(); @@ -399,7 +399,7 @@ public class DynamicDockerAllocationTest { public void testSwitchingFromLegacyFlavorSyntaxToResourcesDoesNotCauseReallocation() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build(); tester.makeReadyNodes(2, new Flavor(new NodeResources(5, 20, 1400, 3)), NodeType.host, 10, true); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("1").build(); @@ -425,7 +425,7 @@ public class DynamicDockerAllocationTest { private void addAndAssignNode(ApplicationId id, String hostname, String parentHostname, ClusterSpec clusterSpec, NodeResources flavor, int index, ProvisioningTester tester) { Node node1a = Node.create("open1", new IP.Config(Set.of("127.0.233." + index), Set.of()), hostname, - Optional.of(parentHostname), Optional.empty(), new Flavor(flavor), Optional.empty(), NodeType.tenant + Optional.of(parentHostname), Optional.empty(), new Flavor(flavor), Optional.empty(), NodeType.tenant, Optional.empty() ); ClusterMembership clusterMembership1 = ClusterMembership.from( clusterSpec.with(Optional.of(ClusterSpec.Group.from(0))), index); // Need to add group here so that group is serialized in node allocation diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java index db6d75d724e..57c7c46c2d9 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java @@ -81,7 +81,7 @@ public class DynamicDockerProvisionTest { @Test public void does_not_allocate_to_available_empty_hosts() { tester.makeReadyNodes(3, "small", NodeType.host, 10); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); NodeResources flavor = new NodeResources(1, 4, 10, 1); @@ -109,7 +109,7 @@ public class DynamicDockerProvisionTest { tester.nodeRepository().setReady(List.of(host), Agent.system, getClass().getSimpleName()); nameResolver.addRecord(hostname + "-2", "::" + i + ":2"); } - tester.deployZoneApp(); + tester.activateTenantHosts(); mockHostProvisioner(hostProvisioner, tester.nodeRepository().flavors().getFlavorOrThrow("small")); tester.prepare(application, clusterSpec("another-id"), 2, 1, flavor); @@ -128,15 +128,14 @@ public class DynamicDockerProvisionTest { NodeResources resources = new NodeResources(10, 10, 10, 10); ApplicationId app = tester.makeApplicationId(); - Function<Node, Node> retireNode = node -> - tester.nodeRepository().write(node.withWantToRetire(true, Agent.system, Instant.now()), () -> {}); + Function<Node, Node> retireNode = node -> tester.patchNode(node, (n) -> n.withWantToRetire(true, Agent.system, Instant.now())); Function<Integer, Node> getNodeInGroup = group -> tester.nodeRepository().getNodes(app).stream() .filter(node -> node.allocation().get().membership().cluster().group().get().index() == group) .findAny().orElseThrow(); // Allocate 10 hosts tester.makeReadyNodes(10, resources, NodeType.host, 1); - tester.deployZoneApp(); + tester.activateTenantHosts(); // Prepare & activate an application with 8 nodes and 2 groups tester.activate(app, tester.prepare(app, clusterSpec("content"), 8, 2, resources)); @@ -170,7 +169,7 @@ public class DynamicDockerProvisionTest { .resourcesCalculator(memoryTax, 0) .build(); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -216,7 +215,7 @@ public class DynamicDockerProvisionTest { .resourcesCalculator(memoryTax, 0) .build(); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -291,7 +290,7 @@ public class DynamicDockerProvisionTest { .resourcesCalculator(memoryTax, 0) .build(); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -326,7 +325,7 @@ public class DynamicDockerProvisionTest { .resourcesCalculator(memoryTax, localDiskTax) .build(); - tester.deployZoneApp(); + tester.activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacityTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacityTest.java index da78aff493e..f831f2d501b 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacityTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacityTest.java @@ -44,9 +44,9 @@ public class HostCapacityTest { NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("host", "docker", "docker2"); // Create three docker hosts - host1 = Node.create("host1", new IP.Config(Set.of("::1"), generateIPs(2, 4)), "host1", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host); - host2 = Node.create("host2", new IP.Config(Set.of("::11"), generateIPs(12, 3)), "host2", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host); - host3 = Node.create("host3", new IP.Config(Set.of("::21"), generateIPs(22, 1)), "host3", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host); + host1 = Node.create("host1", new IP.Config(Set.of("::1"), generateIPs(2, 4)), "host1", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host, Optional.empty()); + host2 = Node.create("host2", new IP.Config(Set.of("::11"), generateIPs(12, 3)), "host2", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host, Optional.empty()); + host3 = Node.create("host3", new IP.Config(Set.of("::21"), generateIPs(22, 1)), "host3", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host, Optional.empty()); // Add two containers to host1 var nodeA = Node.createDockerNode(Set.of("::2"), "nodeA", "host1", resources1, NodeType.tenant); @@ -111,7 +111,7 @@ public class HostCapacityTest { // Dev host can assign both configserver and tenant containers. var nodeFlavors = FlavorConfigBuilder.createDummies("devhost", "container"); - var devHost = Node.create("devhost", new IP.Config(Set.of("::1"), generateIPs(2, 10)), "devhost", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("devhost"), Optional.empty(), NodeType.devhost); + var devHost = Node.create("devhost", new IP.Config(Set.of("::1"), generateIPs(2, 10)), "devhost", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("devhost"), Optional.empty(), NodeType.devhost, Optional.empty()); var cfg = Node.createDockerNode(Set.of("::2"), "cfg", "devhost", resources1, NodeType.config); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java index 6e609d13d3b..09d1600e1d7 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java @@ -134,7 +134,7 @@ public class MultigroupProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(6, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(6, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -160,7 +160,7 @@ public class MultigroupProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(6, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(6, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -190,7 +190,7 @@ public class MultigroupProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(12, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(12, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNodeTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidateTest.java index 3865baa51c1..ec001556f58 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNodeTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidateTest.java @@ -22,29 +22,29 @@ import static org.junit.Assert.assertEquals; /** * @author bratseth */ -public class PrioritizableNodeTest { +public class NodeCandidateTest { @Test public void test_order() { - List<PrioritizableNode> expected = List.of( - new PrioritizableNode(node("01", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.empty(), false, true, false, false), - new PrioritizableNode(node("02", Node.State.active), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, false, false), - new PrioritizableNode(node("04", Node.State.reserved), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, false, false), - new PrioritizableNode(node("03", Node.State.inactive), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, false, false), - new PrioritizableNode(node("05", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.of(node("host1", Node.State.active)), true, false, true, false), - new PrioritizableNode(node("06", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.of(node("host1", Node.State.ready)), true, false, true, false), - new PrioritizableNode(node("07", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.of(node("host1", Node.State.provisioned)), true, false, true, false), - new PrioritizableNode(node("08", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.of(node("host1", Node.State.failed)), true, false, true, false), - new PrioritizableNode(node("09", Node.State.ready), new NodeResources(1, 1, 1, 1), Optional.empty(), true, false, true, false), - new PrioritizableNode(node("10", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, true, false), - new PrioritizableNode(node("11", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, true, false) + List<NodeCandidate> expected = List.of( + new NodeCandidate(node("01", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.empty(), false, true, false, false), + new NodeCandidate(node("02", Node.State.active), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, false, false), + new NodeCandidate(node("04", Node.State.reserved), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, false, false), + new NodeCandidate(node("03", Node.State.inactive), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, false, false), + new NodeCandidate(node("05", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.of(node("host1", Node.State.active)), true, false, true, false), + new NodeCandidate(node("06", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.of(node("host1", Node.State.ready)), true, false, true, false), + new NodeCandidate(node("07", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.of(node("host1", Node.State.provisioned)), true, false, true, false), + new NodeCandidate(node("08", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.of(node("host1", Node.State.failed)), true, false, true, false), + new NodeCandidate(node("09", Node.State.ready), new NodeResources(1, 1, 1, 1), Optional.empty(), true, false, true, false), + new NodeCandidate(node("10", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, true, false), + new NodeCandidate(node("11", Node.State.ready), new NodeResources(2, 2, 2, 2), Optional.empty(), true, false, true, false) ); assertOrder(expected); } @Test public void testOrderingByAllocationSkew1() { - List<PrioritizableNode> expected = List.of( + List<NodeCandidate> expected = List.of( node("1", node(4, 4), host(20, 20), host(40, 40)), node("2", node(4, 4), host(21, 20), host(40, 40)), node("3", node(4, 4), host(22, 20), host(40, 40)), @@ -59,7 +59,7 @@ public class PrioritizableNodeTest { // The same as testOrderingByAllocationSkew1, but deviating from mean (20) in the other direction. // Since we don't choose the node with the lowest skew, but with the largest skew *reduction* // this causes the opposite order. - List<PrioritizableNode> expected = List.of( + List<NodeCandidate> expected = List.of( node("4", node(4, 4), host(19, 18), host(40, 40)), node("3", node(4, 4), host(18, 20), host(40, 40)), node("2", node(4, 4), host(19, 20), host(40, 40)), @@ -72,7 +72,7 @@ public class PrioritizableNodeTest { @Test public void testOrderingByAllocationSkew3() { // The same as testOrderingByAllocationSkew1, but allocating skewed towards cpu - List<PrioritizableNode> expected = List.of( + List<NodeCandidate> expected = List.of( node("1", node(4, 2), host(20, 20), host(40, 40)), node("2", node(4, 2), host(21, 20), host(40, 40)), node("4", node(4, 2), host(21, 22), host(40, 40)), @@ -85,7 +85,7 @@ public class PrioritizableNodeTest { @Test public void testOrderingByAllocationSkew4() { // The same as testOrderingByAllocationSkew1, but allocating skewed towards memory - List<PrioritizableNode> expected = List.of( + List<NodeCandidate> expected = List.of( node("5", node(2, 10), host(21, 21), host(40, 80)), node("3", node(2, 10), host(22, 20), host(40, 40)), node("2", node(2, 10), host(21, 20), host(40, 40)), @@ -99,7 +99,7 @@ public class PrioritizableNodeTest { public void testOrderingByAllocationSkew5() { // node1 is skewed towards cpu (without this allocation), allocation is skewed towards memory, therefore // node 1 is preferred (even though it is still most skewed) - List<PrioritizableNode> expected = List.of( + List<NodeCandidate> expected = List.of( node("1", node(1, 5), host(21, 10), host(40, 40)), node("2", node(1, 5), host(21, 20), host(40, 40)), node("3", node(1, 5), host(20, 20), host(40, 40)), @@ -108,8 +108,8 @@ public class PrioritizableNodeTest { assertOrder(expected); } - private void assertOrder(List<PrioritizableNode> expected) { - List<PrioritizableNode> copy = new ArrayList<>(expected); + private void assertOrder(List<NodeCandidate> expected) { + List<NodeCandidate> copy = new ArrayList<>(expected); Collections.shuffle(copy); Collections.sort(copy); assertEquals(expected, copy); @@ -127,23 +127,23 @@ public class PrioritizableNodeTest { return new Node(hostname, new IP.Config(Set.of("::1"), Set.of()), hostname, Optional.empty(), new Flavor(new NodeResources(2, 2, 2, 2)), Status.initial(), state, Optional.empty(), History.empty(), NodeType.tenant, new Reports(), - Optional.empty(), Optional.empty()); + Optional.empty(), Optional.empty(), Optional.empty()); } - private static PrioritizableNode node(String hostname, - NodeResources nodeResources, - NodeResources allocatedHostResources, // allocated before adding nodeResources - NodeResources totalHostResources) { + private static NodeCandidate node(String hostname, + NodeResources nodeResources, + NodeResources allocatedHostResources, // allocated before adding nodeResources + NodeResources totalHostResources) { Node node = new Node(hostname, new IP.Config(Set.of("::1"), Set.of()), hostname, Optional.of(hostname + "parent"), new Flavor(nodeResources), Status.initial(), Node.State.ready, Optional.empty(), History.empty(), NodeType.tenant, - new Reports(), Optional.empty(), Optional.empty()); + new Reports(), Optional.empty(), Optional.empty(), Optional.empty()); Node parent = new Node(hostname + "parent", new IP.Config(Set.of("::1"), Set.of()), hostname, Optional.empty(), new Flavor(totalHostResources), Status.initial(), Node.State.ready, Optional.empty(), History.empty(), NodeType.host, - new Reports(), Optional.empty(), Optional.empty()); - return new PrioritizableNode(node, totalHostResources.subtract(allocatedHostResources), Optional.of(parent), - false, false, true, false); + new Reports(), Optional.empty(), Optional.empty(), Optional.empty()); + return new NodeCandidate(node, totalHostResources.subtract(allocatedHostResources), Optional.of(parent), + false, false, true, false); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java index ff2f0ffca96..6cf5a2c8342 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java @@ -62,7 +62,7 @@ public class ProvisioningTest { ApplicationId application1 = tester.makeApplicationId(); ApplicationId application2 = tester.makeApplicationId(); - tester.makeReadyHosts(21, defaultResources).deployZoneApp(); + tester.makeReadyHosts(21, defaultResources).activateTenantHosts(); // deploy SystemState state1 = prepare(application1, 2, 2, 3, 3, defaultResources, tester); @@ -196,7 +196,7 @@ public class ProvisioningTest { ApplicationId application1 = tester.makeApplicationId(); tester.makeReadyHosts(24, defaultResources); - tester.deployZoneApp(); + tester.activateTenantHosts(); // deploy SystemState state1 = prepare(application1, 2, 2, 3, 3, defaultResources, tester); @@ -267,7 +267,7 @@ public class ProvisioningTest { ApplicationId application1 = tester.makeApplicationId(); tester.makeReadyHosts(12, small); - tester.deployZoneApp(); + tester.activateTenantHosts(); // deploy SystemState state1 = prepare(application1, 2, 2, 4, 4, small, tester); @@ -278,7 +278,7 @@ public class ProvisioningTest { tester.activate(application1, state2.allHosts); tester.makeReadyHosts(16, large); - tester.deployZoneApp(); + tester.activateTenantHosts(); // redeploy with increased sizes and new flavor SystemState state3 = prepare(application1, 3, 4, 4, 5, large, tester); @@ -303,7 +303,7 @@ public class ProvisioningTest { tester.makeReadyHosts(12, small); tester.makeReadyHosts(12, large); - tester.deployZoneApp(); + tester.activateTenantHosts(); // deploy SystemState state1 = prepare(application1, 2, 2, 4, 4, small, tester); @@ -319,7 +319,7 @@ public class ProvisioningTest { ApplicationId application1 = tester.makeApplicationId(); - tester.makeReadyHosts(5, defaultResources).deployZoneApp(); + tester.makeReadyHosts(5, defaultResources).activateTenantHosts(); // deploy SystemState state1 = prepare(application1, 2, 0, 3, 0, defaultResources, tester); @@ -354,7 +354,7 @@ public class ProvisioningTest { @Test public void requested_resources_info_is_retained() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); - tester.makeReadyHosts(13, defaultResources).deployZoneApp(); + tester.makeReadyHosts(13, defaultResources).activateTenantHosts(); tester.prepareAndActivateInfraApplication(tester.makeApplicationId(), NodeType.host); ApplicationId application = tester.makeApplicationId(); @@ -398,7 +398,7 @@ public class ProvisioningTest { public void deploy_specific_vespa_version() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.dev, RegionName.from("us-east"))).build(); - tester.makeReadyHosts(4, defaultResources).deployZoneApp(); + tester.makeReadyHosts(4, defaultResources).activateTenantHosts(); tester.prepareAndActivateInfraApplication(tester.makeApplicationId(), NodeType.host); ApplicationId application = tester.makeApplicationId(); @@ -411,7 +411,7 @@ public class ProvisioningTest { public void deploy_specific_vespa_version_and_docker_image() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.dev, RegionName.from("us-east"))).build(); - tester.makeReadyHosts(4, defaultResources).deployZoneApp(); + tester.makeReadyHosts(4, defaultResources).activateTenantHosts(); tester.prepareAndActivateInfraApplication(tester.makeApplicationId(), NodeType.host); ApplicationId application = tester.makeApplicationId(); @@ -426,7 +426,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.test, RegionName.from("us-east"))).build(); ApplicationId application = tester.makeApplicationId(); - tester.makeReadyHosts(4, defaultResources).deployZoneApp(); + tester.makeReadyHosts(4, defaultResources).activateTenantHosts(); SystemState state = prepare(application, 2, 2, 3, 3, defaultResources, tester); assertEquals(4, state.allHosts.size()); tester.activate(application, state.allHosts); @@ -438,7 +438,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(4, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(4, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -456,7 +456,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .flavors(List.of(hostFlavor)) .build(); - tester.makeReadyHosts(31, hostFlavor.resources()).deployZoneApp(); + tester.makeReadyHosts(31, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = tester.makeApplicationId("app1"); ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); @@ -516,7 +516,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); ApplicationId application = tester.makeApplicationId(); - tester.makeReadyHosts(10, defaultResources).deployZoneApp(); + tester.makeReadyHosts(10, defaultResources).activateTenantHosts(); prepare(application, 1, 2, 3, 3, defaultResources, tester); } @@ -525,7 +525,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); ApplicationId application = tester.makeApplicationId(); - tester.makeReadyHosts(10, defaultResources).deployZoneApp(); + tester.makeReadyHosts(10, defaultResources).activateTenantHosts(); try { prepare(application, 2, 2, 3, 3, new NodeResources(2, 2, 10, 2), tester); @@ -540,7 +540,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); ApplicationId application = tester.makeApplicationId(); - tester.makeReadyHosts(10, defaultResources).deployZoneApp(); + tester.makeReadyHosts(10, defaultResources).activateTenantHosts(); try { prepare(application, 2, 2, 3, 3, new NodeResources(0.4, 4, 10, 2), tester); @@ -573,7 +573,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.test, RegionName.from("us-east"))).build(); ApplicationId application = tester.makeApplicationId(); - tester.makeReadyHosts(4, large).deployZoneApp(); + tester.makeReadyHosts(4, large).activateTenantHosts(); SystemState state = prepare(application, 2, 2, 3, 3, large, tester); assertEquals(4, state.allHosts.size()); tester.activate(application, state.allHosts); @@ -584,7 +584,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.staging, RegionName.from("us-east"))).build(); ApplicationId application = tester.makeApplicationId(); - tester.makeReadyHosts(14, defaultResources).deployZoneApp(); + tester.makeReadyHosts(14, defaultResources).activateTenantHosts(); SystemState state = prepare(application, 1, 1, 1, 64, defaultResources, tester); // becomes 1, 1, 1, 1, 6 assertEquals(9, state.allHosts.size()); tester.activate(application, state.allHosts); @@ -594,7 +594,7 @@ public class ProvisioningTest { public void activate_after_reservation_timeout() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); - tester.makeReadyHosts(10, defaultResources).deployZoneApp(); + tester.makeReadyHosts(10, defaultResources).activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); SystemState state = prepare(application, 2, 2, 3, 3, defaultResources, tester); @@ -616,7 +616,7 @@ public class ProvisioningTest { public void out_of_capacity() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); - tester.makeReadyHosts(9, defaultResources).deployZoneApp(); // need 2+2+3+3=10 + tester.makeReadyHosts(9, defaultResources).activateTenantHosts(); // need 2+2+3+3=10 ApplicationId application = tester.makeApplicationId(); try { prepare(application, 2, 2, 3, 3, defaultResources, tester); @@ -633,7 +633,7 @@ public class ProvisioningTest { Environment.prod, RegionName.from("us-east"))).build(); - tester.makeReadyHosts(13, defaultResources).deployZoneApp(); + tester.makeReadyHosts(13, defaultResources).activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); try { prepare(application, 2, 2, 6, 3, defaultResources, tester); @@ -651,7 +651,7 @@ public class ProvisioningTest { Environment.prod, RegionName.from("us-east"))).build(); - tester.makeReadyHosts(13, defaultResources).deployZoneApp(); + tester.makeReadyHosts(13, defaultResources).activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); prepare(application, 2, 2, 6, 3, defaultResources, tester); } @@ -659,7 +659,7 @@ public class ProvisioningTest { @Test public void out_of_capacity_but_cannot_fail() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); - tester.makeReadyHosts(4, defaultResources).deployZoneApp(); + tester.makeReadyHosts(4, defaultResources).activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("music")).vespaVersion("4.5.6").build(); tester.prepare(application, cluster, Capacity.from(new ClusterResources(5, 1, NodeResources.unspecified()), false, false)); @@ -673,7 +673,7 @@ public class ProvisioningTest { ApplicationId application = tester.makeApplicationId(); // Flag all nodes for retirement List<Node> readyNodes = tester.makeReadyNodes(5, defaultResources); - readyNodes.forEach(node -> tester.patchNode(node.withWantToRetire(true, Agent.system, tester.clock().instant()))); + tester.patchNodes(readyNodes, (node) -> node.withWantToRetire(true, Agent.system, tester.clock().instant())); try { prepare(application, 2, 0, 2, 0, defaultResources, tester); @@ -693,7 +693,7 @@ public class ProvisioningTest { ApplicationId application = tester.makeApplicationId(); // Create 10 nodes - tester.makeReadyHosts(10, defaultResources).deployZoneApp(); + tester.makeReadyHosts(10, defaultResources).activateTenantHosts(); // Allocate 5 nodes ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("music")).vespaVersion("4.5.6").build(); tester.activate(application, tester.prepare(application, cluster, capacity)); @@ -701,7 +701,7 @@ public class ProvisioningTest { assertEquals(0, NodeList.copyOf(tester.nodeRepository().getNodes(application, Node.State.active)).retired().size()); // Mark the nodes as want to retire - tester.nodeRepository().getNodes(application, Node.State.active).forEach(node -> tester.patchNode(node.withWantToRetire(true, Agent.system, tester.clock().instant()))); + tester.nodeRepository().getNodes(application, Node.State.active).forEach(node -> tester.patchNode(node, (n) -> n.withWantToRetire(true, Agent.system, tester.clock().instant()))); // redeploy without allow failing tester.activate(application, tester.prepare(application, cluster, capacityFORCED)); @@ -724,7 +724,7 @@ public class ProvisioningTest { ApplicationId application1 = tester.makeApplicationId(); - tester.makeReadyHosts(14, defaultResources).deployZoneApp(); + tester.makeReadyHosts(14, defaultResources).activateTenantHosts(); // deploy SystemState state1 = prepare(application1, 3, 3, 4, 4, defaultResources, tester); @@ -751,7 +751,7 @@ public class ProvisioningTest { public void node_on_spare_host_retired_first() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) .spareCount(1).build(); - tester.makeReadyHosts(7, defaultResources).deployZoneApp(); + tester.makeReadyHosts(7, defaultResources).activateTenantHosts(); ApplicationId application = tester.makeApplicationId(); ClusterSpec spec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("content1")).vespaVersion("7.1.2").build(); @@ -774,7 +774,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); ApplicationId application = tester.makeApplicationId(); - tester.makeReadyHosts(10, defaultResources).deployZoneApp(); + tester.makeReadyHosts(10, defaultResources).activateTenantHosts(); // Deploy application { @@ -786,7 +786,7 @@ public class ProvisioningTest { // Retire some nodes and redeploy { List<Node> nodesToRetire = tester.getNodes(application, Node.State.active).asList().subList(0, 2); - nodesToRetire.forEach(node -> tester.patchNode(node.withWantToRetire(true, Agent.system, tester.clock().instant()))); + tester.patchNodes(nodesToRetire, (node) -> node.withWantToRetire(true, Agent.system, tester.clock().instant())); SystemState state = prepare(application, 2, 0, 2, 0, defaultResources, tester); tester.activate(application, state.allHosts); @@ -802,7 +802,7 @@ public class ProvisioningTest { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); ApplicationId application = tester.makeApplicationId(); - tester.makeReadyHosts(2, defaultResources).deployZoneApp(); + tester.makeReadyHosts(2, defaultResources).activateTenantHosts(); // Deploy fails with out of capacity try { @@ -813,7 +813,7 @@ public class ProvisioningTest { tester.getNodes(application, Node.State.reserved).size()); // Enough nodes become available - tester.makeReadyHosts(2, defaultResources).deployZoneApp(); + tester.makeReadyHosts(2, defaultResources).activateTenantHosts(); // Deploy is retried after a few minutes tester.clock().advance(Duration.ofMinutes(2)); @@ -883,7 +883,7 @@ public class ProvisioningTest { var tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); var application = tester.makeApplicationId(); - tester.makeReadyHosts(4, defaultResources).deployZoneApp(); + tester.makeReadyHosts(4, defaultResources).activateTenantHosts(); // Application allocates two content nodes initially, with cluster type content ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("music")).vespaVersion("1.2.3").build(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java index d56cae799b2..4c8d5caad43 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java @@ -53,6 +53,7 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Function; +import java.util.function.UnaryOperator; import java.util.logging.Level; import java.util.stream.Collectors; @@ -139,7 +140,22 @@ public class ProvisioningTester { public CapacityPolicies capacityPolicies() { return capacityPolicies; } public NodeList getNodes(ApplicationId id, Node.State ... inState) { return NodeList.copyOf(nodeRepository.getNodes(id, inState)); } - public void patchNode(Node node) { nodeRepository.write(node, () -> {}); } + public Node patchNode(Node node, UnaryOperator<Node> patcher) { + return patchNodes(List.of(node), patcher).get(0); + } + + public List<Node> patchNodes(List<Node> nodes, UnaryOperator<Node> patcher) { + List<Node> updated = new ArrayList<>(); + for (var node : nodes) { + try (var lock = nodeRepository.lock(node)) { + node = nodeRepository.getNode(node.hostname()).get(); + node = patcher.apply(node); + nodeRepository.write(node, lock); + updated.add(node); + } + } + return updated; + } public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, int nodeCount, int groups, NodeResources resources) { return prepare(application, cluster, nodeCount, groups, false, resources); @@ -320,7 +336,7 @@ public class ProvisioningTester { return makeReadyNodes(n, flavor, NodeType.tenant); } - /** Call deployZoneApp() after this before deploying applications */ + /** Call {@link this#activateTenantHosts()} after this before deploying applications */ public ProvisioningTester makeReadyHosts(int n, NodeResources resources) { makeReadyNodes(n, resources, NodeType.host, 5); return this; @@ -494,7 +510,7 @@ public class ProvisioningTester { return nodes; } - public void deployZoneApp() { + public void activateTenantHosts() { ApplicationId applicationId = makeApplicationId(); List<HostSpec> list = prepare(applicationId, ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin")).vespaVersion("6.42").build(), diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java index 51261c29a71..0ef7071b095 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java @@ -129,9 +129,7 @@ public class VirtualNodeProvisioningTest { assertDistinctParentHosts(nodes, ClusterSpec.Type.container, containerNodeCount); assertDistinctParentHosts(nodes, ClusterSpec.Type.content, contentNodeCount); - for (Node n : nodes) { - tester.patchNode(n.withParentHostname("clashing")); - } + tester.patchNodes(nodes, (n) -> n.withParentHostname("clashing")); containerHosts = prepare(containerClusterSpec, containerNodeCount, groups); contentHosts = prepare(contentClusterSpec, contentNodeCount, groups); activate(containerHosts, contentHosts); @@ -160,9 +158,7 @@ public class VirtualNodeProvisioningTest { assertDistinctParentHosts(nodes, ClusterSpec.Type.container, containerNodeCount); assertDistinctParentHosts(nodes, ClusterSpec.Type.content, contentNodeCount); - for (Node n : nodes) { - tester.patchNode(n.withParentHostname("clashing")); - } + tester.patchNodes(nodes, (n) -> n.withParentHostname("clashing")); OutOfCapacityException expected = null; try { containerHosts = prepare(containerClusterSpec, containerNodeCount, groups); @@ -216,9 +212,9 @@ public class VirtualNodeProvisioningTest { assertEquals(3, nodes.size()); // Set indistinct parents - tester.patchNode(nodes.get(0).withParentHostname("parentHost1")); - tester.patchNode(nodes.get(1).withParentHostname("parentHost1")); - tester.patchNode(nodes.get(2).withParentHostname("parentHost2")); + tester.patchNode(nodes.get(0), (n) -> n.withParentHostname("parentHost1")); + tester.patchNode(nodes.get(1), (n) -> n.withParentHostname("parentHost1")); + tester.patchNode(nodes.get(2), (n) -> n.withParentHostname("parentHost2")); nodes = getNodes(applicationId); assertEquals(3, nodes.stream().filter(n -> n.parentHostname().isPresent()).count()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java index 2cd2fe1fc28..1a354686ae4 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java @@ -333,7 +333,7 @@ public class NodesV2ApiTest { // Node types running a single container can share their IP address with child node tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node", - "[" + asNodeJson("cfghost42.yahoo.com", NodeType.confighost, "default", Optional.empty(), "127.0.42.1") + "]", + "[" + asNodeJson("cfghost42.yahoo.com", NodeType.confighost, "default", Optional.empty(), Optional.empty(), "127.0.42.1") + "]", Request.Method.POST), 200, "{\"message\":\"Added 1 nodes to the provisioned state\"}"); tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node", @@ -349,7 +349,7 @@ public class NodesV2ApiTest { // ... nor with child node on different host tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node", - "[" + asNodeJson("cfghost43.yahoo.com", NodeType.confighost, "default", Optional.empty(), "127.0.43.1") + "]", + "[" + asNodeJson("cfghost43.yahoo.com", NodeType.confighost, "default", Optional.empty(), Optional.empty(), "127.0.43.1") + "]", Request.Method.POST), 200, "{\"message\":\"Added 1 nodes to the provisioned state\"}"); tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node/cfg42.yahoo.com", @@ -939,6 +939,30 @@ public class NodesV2ApiTest { "\"resources\":{\"vcpu\":56.0,\"memoryGb\":34.0,\"diskGb\":12.0,\"bandwidthGbps\":78.0,\"diskSpeed\":\"fast\",\"storageType\":\"remote\"}"); } + @Test + public void test_node_switch_hostname() throws Exception { + String hostname = "host42.yahoo.com"; + // Add host with switch hostname + String json = asNodeJson(hostname, NodeType.host, "default", Optional.empty(), Optional.of("switch0"), "127.0.42.1", "::42:1"); + assertResponse(new Request("http://localhost:8080/nodes/v2/node", + ("[" + json + "]").getBytes(StandardCharsets.UTF_8), + Request.Method.POST), + "{\"message\":\"Added 1 nodes to the provisioned state\"}"); + tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/" + hostname), "\"switchHostname\":\"switch0\""); + + // Update switch hostname + json = "{\"switchHostname\":\"switch1\"}"; + assertResponse(new Request("http://localhost:8080/nodes/v2/node/" + hostname, json.getBytes(StandardCharsets.UTF_8), Request.Method.PATCH), + "{\"message\":\"Updated host42.yahoo.com\"}"); + tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/" + hostname), "\"switchHostname\":\"switch1\""); + + // Clear switch hostname + json = "{\"switchHostname\":null}"; + assertResponse(new Request("http://localhost:8080/nodes/v2/node/" + hostname, json.getBytes(StandardCharsets.UTF_8), Request.Method.PATCH), + "{\"message\":\"Updated host42.yahoo.com\"}"); + tester.assertPartialResponse(new Request("http://localhost:8080/nodes/v2/node/" + hostname), "switchHostname", false); + } + private static String asDockerNodeJson(String hostname, String parentHostname, String... ipAddress) { return asDockerNodeJson(hostname, NodeType.tenant, parentHostname, ipAddress); } @@ -958,14 +982,15 @@ public class NodesV2ApiTest { } private static String asHostJson(String hostname, String flavor, Optional<TenantName> reservedTo, String... ipAddress) { - return asNodeJson(hostname, NodeType.host, flavor, reservedTo, ipAddress); + return asNodeJson(hostname, NodeType.host, flavor, reservedTo, Optional.empty(), ipAddress); } - private static String asNodeJson(String hostname, NodeType nodeType, String flavor, Optional<TenantName> reservedTo, String... ipAddress) { + private static String asNodeJson(String hostname, NodeType nodeType, String flavor, Optional<TenantName> reservedTo, Optional<String> switchHostname, String... ipAddress) { return "{\"hostname\":\"" + hostname + "\", \"openStackId\":\"" + hostname + "\"," + createIpAddresses(ipAddress) + "\"flavor\":\"" + flavor + "\"" + (reservedTo.isPresent() ? ", \"reservedTo\":\"" + reservedTo.get().value() + "\"" : "") + + (switchHostname.isPresent() ? ", \"switchHostname\":\"" + switchHostname.get() + "\"" : "") + ", \"type\":\"" + nodeType + "\"}"; } @@ -989,8 +1014,8 @@ public class NodesV2ApiTest { tester.assertFile(request, file); } - private void assertResponse(Request request, String file) throws IOException { - tester.assertResponse(request, file); + private void assertResponse(Request request, String response) throws IOException { + tester.assertResponse(request, response); } } |