aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java1
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java9
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java111
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java13
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java23
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java17
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java26
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java36
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java12
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/node/IPTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java8
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java52
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java1
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNodeTest.java22
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java36
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java17
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json1
32 files changed, 298 insertions, 155 deletions
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java
index 3ea3892e398..4b882023e08 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java
@@ -64,6 +64,7 @@ public class IdentityDocumentGeneratorTest {
Optional.empty(),
Optional.empty(),
new MockNodeFlavors().getFlavorOrThrow("default"),
+ Optional.empty(),
NodeType.host);
Node containerNode = Node.createDockerNode(Set.of("::1"),
containerHostname,
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
index a35dfd878c5..2208b470ed8 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
@@ -223,7 +223,14 @@ public class InstanceValidatorTest {
MockNodeFlavors flavors = new MockNodeFlavors();
List<Node> nodeList = new ArrayList<>();
for (int i = 0; i < num; i++) {
- Node node = Node.create("foo" + i, new IP.Config(Set.of("::1" + i, "::2" + i, "::3" + i), Set.of()), "foo" + i, Optional.empty(), Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.tenant);
+ Node node = Node.create("foo" + i,
+ new IP.Config(Set.of("::1" + i, "::2" + i, "::3" + i), Set.of()),
+ "foo" + i,
+ Optional.empty(),
+ Optional.empty(),
+ flavors.getFlavorOrThrow("default"),
+ Optional.empty(),
+ NodeType.tenant);
nodeList.add(node);
}
return nodeList;
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 4ff0fee2eb7..3778b7aa625 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
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.Allocation;
import com.yahoo.vespa.hosted.provision.node.Generation;
@@ -41,6 +42,7 @@ public final class Node {
private final NodeType type;
private final Reports reports;
private final Optional<String> modelName;
+ private final Optional<TenantName> reservedTo;
/** Record of the last event of each type happening to this node */
private final History history;
@@ -50,52 +52,46 @@ public final class Node {
/** Creates a node in the initial state (reserved) */
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());
+ 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());
}
/** 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, NodeType type) {
+ public static Node create(String openStackId, IP.Config ipConfig, String hostname, Optional<String> parentHostname,
+ Optional<String> modelName, Flavor flavor, Optional<TenantName> reservedTo, NodeType type) {
return new Node(openStackId, ipConfig, hostname, parentHostname, flavor, Status.initial(), State.provisioned,
- Optional.empty(), History.empty(), type, new Reports(), modelName);
+ Optional.empty(), History.empty(), type, new Reports(), modelName, reservedTo);
}
/** 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) {
- Objects.requireNonNull(id, "A node must have an ID");
- requireNonEmptyString(hostname, "A node must have a hostname");
- Objects.requireNonNull(ipConfig, "A node must a have an IP config");
- requireNonEmptyString(parentHostname, "A parent host name must be a proper value");
- Objects.requireNonNull(flavor, "A node must have a flavor");
- Objects.requireNonNull(status, "A node must have a status");
- Objects.requireNonNull(state, "A null node state is not permitted");
- Objects.requireNonNull(allocation, "A null node allocation is not permitted");
- Objects.requireNonNull(history, "A null node history is not permitted");
- Objects.requireNonNull(type, "A null node type is not permitted");
- Objects.requireNonNull(reports, "A null reports is not permitted");
- Objects.requireNonNull(modelName, "A null modelName is not permitted");
+ Reports reports, Optional<String> modelName, Optional<TenantName> reservedTo) {
+ 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");
+ this.parentHostname = requireNonEmptyString(parentHostname, "A parent host name must be a proper value");
+ this.flavor = Objects.requireNonNull(flavor, "A node must have a flavor");
+ this.status = Objects.requireNonNull(status, "A node must have a status");
+ this.state = Objects.requireNonNull(state, "A null node state is not permitted");
+ this.allocation = Objects.requireNonNull(allocation, "A null node allocation is not permitted");
+ this.history = Objects.requireNonNull(history, "A null node history is not permitted");
+ this.type = Objects.requireNonNull(type, "A null node type is not permitted");
+ 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");
if (state == State.active)
requireNonEmpty(ipConfig.primary(), "An active node must have at least one valid IP address");
+
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");
}
- this.hostname = hostname;
- this.ipConfig = ipConfig;
- this.parentHostname = parentHostname;
- this.id = id;
- this.flavor = flavor;
- this.status = status;
- this.state = state;
- this.allocation = allocation;
- this.history = history;
- this.type = type;
- this.reports = reports;
- this.modelName = modelName;
+ if (type != NodeType.host && reservedTo.isPresent())
+ throw new IllegalArgumentException("Only hosts can be reserved to a tenant");
}
/** Returns the IP addresses of this node */
@@ -166,6 +162,12 @@ public final class Node {
public Optional<String> modelName() { return modelName; }
/**
+ * Returns the tenant this node is reserved to, if any. Only hosts can be reserved to a tenant.
+ * If this is set, resources on this host cannot be allocated to any other tenant
+ */
+ public Optional<TenantName> reservedTo() { return reservedTo; }
+
+ /**
* Returns a copy of this node with wantToRetire set to the given value and updated history.
* If given wantToRetire is equal to the current, the method is no-op.
*/
@@ -209,37 +211,42 @@ 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);
+ return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, reports, modelName, reservedTo);
}
/** 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);
+ return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, reports, modelName, reservedTo);
}
/** 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);
+ return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state,
+ allocation, history, type, reports, modelName, reservedTo);
}
/** 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);
+ return new Node(id, ipConfig, hostname, parentHostname, flavor, status.withReboot(generation), state,
+ allocation, history, type, reports, modelName, reservedTo);
}
/** 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);
+ return new Node(openStackId, ipConfig, hostname, parentHostname, flavor, status, state,
+ allocation, history, type, reports, modelName, reservedTo);
}
/** 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));
+ return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state,
+ allocation, history, type, reports, Optional.of(modelName), reservedTo);
}
/** 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());
+ return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state,
+ allocation, history, type, reports, Optional.empty(), reservedTo);
}
/** Returns a copy of this with a history record saying it was detected to be down at this instant */
@@ -265,26 +272,32 @@ 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);
+ Optional.of(allocation), history, type, reports, modelName, reservedTo);
}
/** 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);
+ Optional.empty(), history, type, reports, modelName, reservedTo);
}
/** 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);
+ allocation, history, type, reports, modelName, reservedTo);
}
/** 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);
+ allocation, history, type, reports, modelName, reservedTo);
+ }
+
+ /** 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());
}
/** Returns a copy of this node with the current reboot generation set to the given number at the given instant */
@@ -316,28 +329,32 @@ 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);
+ return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state,
+ allocation, history, type, reports, modelName, reservedTo);
}
public Node with(Reports reports) {
- return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, reports, modelName);
+ return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state,
+ allocation, history, type, reports, modelName, reservedTo);
}
- private static void requireNonEmptyString(Optional<String> value, String message) {
+ private static Optional<String> requireNonEmptyString(Optional<String> value, String message) {
Objects.requireNonNull(value, message);
value.ifPresent(v -> requireNonEmptyString(v, message));
+ return value;
}
- private static void requireNonEmptyString(String value, String message) {
+ private static String requireNonEmptyString(String value, String message) {
Objects.requireNonNull(value, message);
if (value.trim().isEmpty())
throw new IllegalArgumentException(message + ", but was '" + value + "'");
+ return value;
}
- private static void requireNonEmpty(Set<String> values, String message) {
- if (values == null || values.isEmpty()) {
+ private static Set<String> requireNonEmpty(Set<String> values, String message) {
+ if (values == null || values.isEmpty())
throw new IllegalArgumentException(message);
- }
+ return values;
}
/** Computes the allocation skew of a host node */
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 1c9e1445447..d5aaf5dbad7 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
@@ -10,6 +10,7 @@ import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeFlavors;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.config.provisioning.NodeRepositoryConfig;
import com.yahoo.transaction.Mutex;
@@ -309,16 +310,15 @@ public class NodeRepository extends AbstractComponent {
/** Creates a new node object, without adding it to the node repo. If no IP address is given, it will be resolved */
public Node createNode(String openStackId, String hostname, IP.Config ipConfig, Optional<String> parentHostname,
- Flavor flavor, NodeType type) {
+ 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, type);
+ return Node.create(openStackId, ipConfig, hostname, parentHostname, Optional.empty(), flavor, reservedTo, type);
}
- public Node createNode(String openStackId, String hostname, Optional<String> parentHostname, Flavor flavor,
- NodeType type) {
- return createNode(openStackId, hostname, IP.Config.EMPTY, parentHostname, flavor, type);
+ public Node createNode(String openStackId, String hostname, Optional<String> parentHostname, Flavor flavor, NodeType type) {
+ return createNode(openStackId, hostname, IP.Config.EMPTY, parentHostname, flavor, Optional.empty(), type);
}
/** Adds a list of newly created docker container nodes to the node repository as <i>reserved</i> nodes */
@@ -341,7 +341,7 @@ public class NodeRepository extends AbstractComponent {
/** Adds a list of (newly created) nodes to the node repository as <i>provisioned</i> nodes */
public List<Node> addNodes(List<Node> nodes) {
- try (Mutex lock = lockAllocation()) {
+ try (Mutex lock = lockUnallocated()) {
for (int i = 0; i < nodes.size(); i++) {
var node = nodes.get(i);
var message = "Cannot add " + node.hostname() + ": A node with this name already exists";
@@ -361,7 +361,7 @@ public class NodeRepository extends AbstractComponent {
/** Sets a list of nodes ready and returns the nodes in the ready state */
public List<Node> setReady(List<Node> nodes, Agent agent, String reason) {
- try (Mutex lock = lockAllocation()) {
+ try (Mutex lock = lockUnallocated()) {
List<Node> nodesWithResetFields = nodes.stream()
.map(node -> {
if (node.state() != Node.State.provisioned && node.state() != Node.State.dirty)
@@ -585,7 +585,7 @@ public class NodeRepository extends AbstractComponent {
}
public List<Node> removeRecursively(Node node, boolean force) {
- try (Mutex lock = lockAllocation()) {
+ try (Mutex lock = lockUnallocated()) {
List<Node> removed = new ArrayList<>();
if (node.type().isDockerHost()) {
@@ -713,7 +713,7 @@ public class NodeRepository extends AbstractComponent {
// perform operation while holding locks
List<Node> resultingNodes = new ArrayList<>();
- try (Mutex lock = lockAllocation()) {
+ try (Mutex lock = lockUnallocated()) {
for (Node node : unallocatedNodes)
resultingNodes.add(action.apply(node, lock));
}
@@ -738,12 +738,12 @@ public class NodeRepository extends AbstractComponent {
/** Create a lock with a timeout which provides exclusive rights to making changes to the given application */
public Mutex lock(ApplicationId application, Duration timeout) { return db.lock(application, timeout); }
- /** Create a lock which provides exclusive rights to allocating nodes */
- public Mutex lockAllocation() { return db.lockInactive(); }
+ /** Create a lock which provides exclusive rights to modifying unallocated nodes */
+ public Mutex lockUnallocated() { return db.lockInactive(); }
/** Acquires the appropriate lock for this node */
public Mutex lock(Node node) {
- return node.allocation().isPresent() ? lock(node.allocation().get().owner()) : lockAllocation();
+ return node.allocation().isPresent() ? lock(node.allocation().get().owner()) : lockUnallocated();
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
index 1c27f9d1713..bb6d53d3304 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
@@ -59,7 +59,7 @@ public class DynamicProvisioningMaintainer extends Maintainer {
protected void maintain() {
if (! dynamicProvisioningEnabled.value()) return;
- try (Mutex lock = nodeRepository().lockAllocation()) {
+ try (Mutex lock = nodeRepository().lockUnallocated()) {
NodeList nodes = nodeRepository().list();
updateProvisioningNodes(nodes, lock);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
index dca5c092ad2..424d6409670 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
@@ -97,7 +97,7 @@ public class NodeFailer extends Maintainer {
int throttledNodeFailures = 0;
// Ready nodes
- try (Mutex lock = nodeRepository().lockAllocation()) {
+ try (Mutex lock = nodeRepository().lockUnallocated()) {
updateNodeLivenessEventsForReadyNodes(lock);
for (Map.Entry<Node, String> entry : getReadyNodesByFailureReason().entrySet()) {
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 a28845109dc..ce0bcc2e337 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
@@ -206,7 +206,7 @@ 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.type(), node.reports(), node.modelName(), node.reservedTo());
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 2cbfbc349a6..728ec344c3f 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
@@ -19,6 +19,7 @@ import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
+import com.yahoo.slime.Type;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.node.Agent;
@@ -77,6 +78,7 @@ public class NodeSerializer {
private static final String firmwareCheckKey = "firmwareCheck";
private static final String reportsKey = "reports";
private static final String modelNameKey = "modelName";
+ private static final String reservedToKey = "reservedTo";
// Node resource fields
// ...for hosts and nodes allocated by legacy flavor specs
@@ -149,6 +151,7 @@ public class NodeSerializer {
node.status().firmwareVerifiedAt().ifPresent(instant -> object.setLong(firmwareCheckKey, instant.toEpochMilli()));
node.reports().toSlime(object, reportsKey);
node.modelName().ifPresent(modelName -> object.setString(modelNameKey, modelName));
+ node.reservedTo().ifPresent(tenant -> object.setString(reservedToKey, tenant.value()));
}
private void toSlime(Flavor flavor, Cursor object) {
@@ -222,7 +225,8 @@ public class NodeSerializer {
historyFromSlime(object.field(historyKey)),
nodeTypeFromString(object.field(nodeTypeKey).asString()),
Reports.fromSlime(object.field(reportsKey)),
- modelNameFromSlime(object));
+ modelNameFromSlime(object),
+ reservedToFromSlime(object.field(reservedToKey)));
}
private Status statusFromSlime(Inspector object) {
@@ -341,6 +345,13 @@ public class NodeSerializer {
return Optional.empty();
}
+ private Optional<TenantName> reservedToFromSlime(Inspector object) {
+ if (! object.valid()) return Optional.empty();
+ if (object.type() != Type.STRING)
+ throw new IllegalArgumentException("Expected 'reservedTo' to be a string but is " + object);
+ return Optional.of(TenantName.from(object.asString()));
+ }
+
// ----------------- Enum <-> string mappings ----------------------------------------
/** Returns the event type, or null if this event type should be ignored */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java
index a7519de3776..36034b62cfb 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java
@@ -6,7 +6,6 @@ import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.ParentHostUnavailableException;
-import com.yahoo.log.LogLevel;
import com.yahoo.transaction.Mutex;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.hosted.provision.Node;
@@ -90,6 +89,22 @@ class Activator {
nodeRepository.deactivate(activeToRemove, transaction);
nodeRepository.activate(updateFrom(hosts, continuedActive), transaction); // update active with any changes
nodeRepository.activate(updatePortsFrom(hosts, reservedToActivate), transaction);
+ unreserveParentsOf(reservedToActivate);
+ }
+
+ /** When a tenant node is activated on a host, we can open up that host for use by others */
+ private void unreserveParentsOf(List<Node> nodes) {
+ for (Node node : nodes) {
+ if ( node.parentHostname().isEmpty()) continue;
+ Optional<Node> parent = nodeRepository.getNode(node.parentHostname().get());
+ if (parent.isEmpty()) continue;
+ if (parent.get().reservedTo().isEmpty()) continue;
+ try (Mutex lock = nodeRepository.lock(parent.get())) {
+ Optional<Node> lockedParent = nodeRepository.getNode(parent.get().hostname());
+ if (lockedParent.isEmpty()) continue;
+ nodeRepository.write(lockedParent.get().withoutReservedTo(), lock);
+ }
+ }
}
/** Activate load balancers */
@@ -117,9 +132,9 @@ class Activator {
.filter(node -> node.state() != Node.State.active)
.map(Node::hostname)
.collect(Collectors.toSet());
- long numNonActive = nonActiveHosts.size();
- if (numNonActive > 0) {
- long numActive = parentHostnames.size() - numNonActive;
+
+ if (nonActiveHosts.size() > 0) {
+ long numActive = parentHostnames.size() - nonActiveHosts.size();
var messageBuilder = new StringBuilder()
.append(numActive).append("/").append(parentHostnames.size())
.append(" hosts for ")
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 9cc4b3f3bdb..6686160982a 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
@@ -73,7 +73,7 @@ public class GroupPreparer {
try (Mutex lock = nodeRepository.lock(application)) {
// Lock ready pool to ensure that the same nodes are not simultaneously allocated by others
- try (Mutex allocationLock = nodeRepository.lockAllocation()) {
+ try (Mutex allocationLock = nodeRepository.lockUnallocated()) {
// Create a prioritized set of nodes
LockedNodeList nodeList = nodeRepository.list(allocationLock);
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 ef6e4747f94..ebd6a01e61f 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
@@ -140,9 +140,9 @@ class NodeAllocation {
continue;
}
node.node = offered.allocate(application,
- ClusterMembership.from(cluster, highestIndex.add(1)),
- requestedNodes.resources().orElse(node.node.flavor().resources()),
- clock.instant());
+ ClusterMembership.from(cluster, highestIndex.add(1)),
+ requestedNodes.resources().orElse(node.node.flavor().resources()),
+ clock.instant());
accepted.add(acceptNode(node, false, false));
}
}
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 a17d92117fb..a5fd37640d8 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
@@ -40,7 +40,7 @@ public class NodePrioritizer {
private final LockedNodeList allNodes;
private final DockerHostCapacity capacity;
private final NodeSpec requestedNodes;
- private final ApplicationId appId;
+ private final ApplicationId application;
private final ClusterSpec clusterSpec;
private final NameResolver nameResolver;
private final boolean isDocker;
@@ -50,19 +50,19 @@ public class NodePrioritizer {
private final int currentClusterSize;
private final Set<Node> spareHosts;
- NodePrioritizer(LockedNodeList allNodes, ApplicationId appId, ClusterSpec clusterSpec, NodeSpec nodeSpec,
+ NodePrioritizer(LockedNodeList allNodes, ApplicationId application, ClusterSpec clusterSpec, NodeSpec nodeSpec,
int spares, int wantedGroups, NameResolver nameResolver, HostResourcesCalculator hostResourcesCalculator,
boolean inPlaceResizeEnabled) {
this.allNodes = allNodes;
this.capacity = new DockerHostCapacity(allNodes, hostResourcesCalculator);
this.requestedNodes = nodeSpec;
this.clusterSpec = clusterSpec;
- this.appId = appId;
+ this.application = application;
this.nameResolver = nameResolver;
this.spareHosts = findSpareHosts(allNodes, capacity, spares);
this.inPlaceResizeEnabled = inPlaceResizeEnabled;
- NodeList nodesInCluster = allNodes.owner(appId).type(clusterSpec.type()).cluster(clusterSpec.id());
+ NodeList nodesInCluster = allNodes.owner(application).type(clusterSpec.type()).cluster(clusterSpec.id());
NodeList nonRetiredNodesInCluster = nodesInCluster.not().retired();
long currentGroups = nonRetiredNodesInCluster.state(Node.State.active).stream()
.flatMap(node -> node.allocation()
@@ -127,13 +127,14 @@ public class NodePrioritizer {
if ( ! isDocker) return;
LockedNodeList candidates = allNodes
- .filter(node -> node.type() != NodeType.host || ALLOCATABLE_HOST_STATES.contains(node.state()));
+ .filter(node -> node.type() != NodeType.host || ALLOCATABLE_HOST_STATES.contains(node.state()))
+ .filter(node -> node.reservedTo().isEmpty() || node.reservedTo().get().equals(application.tenant()));
if (exclusively) {
Set<String> candidateHostnames = candidates.asList().stream()
.filter(node -> node.type() == NodeType.tenant)
.filter(node -> node.allocation()
- .map(a -> a.owner().tenant().equals(appId.tenant()))
+ .map(a -> a.owner().tenant().equals(this.application.tenant()))
.orElse(false))
.flatMap(node -> node.parentHostname().stream())
.collect(Collectors.toSet());
@@ -152,7 +153,7 @@ public class NodePrioritizer {
if (host.status().wantToRetire()) continue;
boolean hostHasCapacityForWantedFlavor = capacity.hasCapacity(host, wantedResources);
- boolean conflictingCluster = allNodes.childrenOf(host).owner(appId).asList().stream()
+ boolean conflictingCluster = allNodes.childrenOf(host).owner(application).asList().stream()
.anyMatch(child -> child.allocation().get().membership().cluster().id().equals(clusterSpec.id()));
if (!hostHasCapacityForWantedFlavor || conflictingCluster) continue;
@@ -188,7 +189,7 @@ public class NodePrioritizer {
.filter(node -> node.type() == requestedNodes.type())
.filter(node -> legalStates.contains(node.state()))
.filter(node -> node.allocation().isPresent())
- .filter(node -> node.allocation().get().owner().equals(appId))
+ .filter(node -> node.allocation().get().owner().equals(application))
.map(node -> toPrioritizable(node, false, false))
.forEach(prioritizableNode -> nodes.put(prioritizableNode.node, prioritizableNode));
}
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/PrioritizableNode.java
index c7732695069..6183bffe5ba 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/PrioritizableNode.java
@@ -87,6 +87,10 @@ class PrioritizableNode implements Comparable<PrioritizableNode> {
throw new IllegalStateException("Nodes " + this.node + " and " + other.node + " have different states");
if (this.parent.isPresent() && other.parent.isPresent()) {
+ // Prefer reserved hosts (that they are reserved to the right tenant is ensured elsewhere)
+ if ( this.parent.get().reservedTo().isPresent() && ! other.parent.get().reservedTo().isPresent()) return -1;
+ if ( ! this.parent.get().reservedTo().isPresent() && other.parent.get().reservedTo().isPresent()) return 1;
+
int diskCostDifference = NodeResources.DiskSpeed.compare(this.parent.get().flavor().resources().diskSpeed(),
other.parent.get().flavor().resources().diskSpeed());
if (diskCostDifference != 0)
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 451ab089a8d..b979ccda740 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
@@ -34,7 +34,7 @@ public class ProvisionedHost {
/** Generate {@link Node} instance representing the provisioned physical host */
public Node generateHost() {
- return Node.create(id, IP.Config.EMPTY, hostHostname, Optional.empty(), Optional.empty(), hostFlavor, NodeType.host);
+ return Node.create(id, IP.Config.EMPTY, hostHostname, Optional.empty(), Optional.empty(), hostFlavor, Optional.empty(), NodeType.host);
}
/** Generate {@link Node} instance representing the node running on this physical host */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
index 692402ea914..c64d1a9b5ff 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.HostFilter;
import com.yahoo.config.provision.NodeFlavors;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
@@ -19,6 +20,7 @@ import com.yahoo.restapi.ResourceResponse;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
+import com.yahoo.slime.Type;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.provision.NoSuchNodeException;
import com.yahoo.vespa.hosted.provision.Node;
@@ -225,14 +227,15 @@ public class NodesApiHandler extends LoggingRequestHandler {
Set<String> ipAddressPool = new HashSet<>();
inspector.field("additionalIpAddresses").traverse((ArrayTraverser) (i, item) -> ipAddressPool.add(item.asString()));
- return Node.create(
- inspector.field("openStackId").asString(),
- new IP.Config(ipAddresses, ipAddressPool),
- inspector.field("hostname").asString(),
- parentHostname,
- modelName,
- flavorFromSlime(inspector),
- nodeTypeFromSlime(inspector.field("type")));
+ return Node.create(inspector.field("openStackId").asString(),
+ new IP.Config(ipAddresses, ipAddressPool),
+ inspector.field("hostname").asString(),
+ parentHostname,
+ modelName,
+ flavorFromSlime(inspector),
+ reservedToFromSlime(inspector.field("reservedTo")),
+ nodeTypeFromSlime(inspector.field("type"))
+ );
}
private Flavor flavorFromSlime(Inspector inspector) {
@@ -277,6 +280,13 @@ public class NodesApiHandler extends LoggingRequestHandler {
return serializer.typeFrom(object.asString());
}
+ private Optional<TenantName> reservedToFromSlime(Inspector object) {
+ if (! object.valid()) return Optional.empty();
+ if ( object.type() != Type.STRING)
+ throw new IllegalArgumentException("Expected 'reservedTo' to be a string but is " + object);
+ return Optional.of(TenantName.from(object.asString()));
+ }
+
public static NodeFilter toNodeFilter(HttpRequest request) {
NodeFilter filter = NodeHostFilter.from(HostFilter.from(request.getProperty("hostname"),
request.getProperty("flavor"),
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
index 7ed05432e01..0410bbd8064 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
@@ -146,6 +146,7 @@ class NodesResponse extends HttpResponse {
}
object.setString("openStackId", node.id());
object.setString("flavor", node.flavor().name());
+ node.reservedTo().ifPresent(reservedTo -> object.setString("reservedTo", reservedTo.value()));
if (node.flavor().isConfigured())
object.setDouble("cpuCores", node.flavor().getMinCpuCores());
toSlime(node.flavor().resources(), object.setObject("resources"));
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index 0d331ac0f3f..1817470a63b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -70,34 +70,34 @@ public class MockNodeRepository extends NodeRepository {
// Regular nodes
nodes.add(createNode("node1", "host1.yahoo.com", ipConfig(1), Optional.empty(),
- new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), NodeType.tenant));
+ new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), Optional.empty(), NodeType.tenant));
nodes.add(createNode("node2", "host2.yahoo.com", ipConfig(2), Optional.empty(),
- new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), NodeType.tenant));
+ new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), Optional.empty(), NodeType.tenant));
nodes.add(createNode("node3", "host3.yahoo.com", ipConfig(3), Optional.empty(),
- new Flavor(new NodeResources(0.5, 48, 500, 1, fast, local)), NodeType.tenant));
+ new Flavor(new NodeResources(0.5, 48, 500, 1, fast, local)), Optional.empty(), NodeType.tenant));
Node node4 = createNode("node4", "host4.yahoo.com", ipConfig(4), Optional.of("dockerhost1.yahoo.com"),
- new Flavor(new NodeResources(1, 4, 100, 1, fast, local)), NodeType.tenant);
+ new Flavor(new NodeResources(1, 4, 100, 1, fast, local)), Optional.empty(), NodeType.tenant);
node4 = node4.with(node4.status()
.withVespaVersion(new Version("6.41.0"))
.withDockerImage(DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa:6.41.0")));
nodes.add(node4);
Node node5 = createNode("node5", "host5.yahoo.com", ipConfig(5), Optional.of("dockerhost2.yahoo.com"),
- new Flavor(new NodeResources(1, 8, 100, 1, slow, remote)), NodeType.tenant);
+ new Flavor(new NodeResources(1, 8, 100, 1, slow, remote)), Optional.empty(), NodeType.tenant);
nodes.add(node5.with(node5.status()
.withVespaVersion(new Version("1.2.3"))
.withDockerImage(DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa:1.2.3"))));
nodes.add(createNode("node6", "host6.yahoo.com", ipConfig(6), Optional.empty(),
- new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), NodeType.tenant));
+ new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), Optional.empty(), NodeType.tenant));
Node node7 = createNode("node7", "host7.yahoo.com", ipConfig(7), Optional.empty(),
- new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), NodeType.tenant);
+ new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), Optional.empty(), NodeType.tenant);
nodes.add(node7);
// 8, 9, 11 and 12 are added by web service calls
Node node10 = createNode("node10", "host10.yahoo.com", ipConfig(10), Optional.of("parent1.yahoo.com"),
- new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), NodeType.tenant);
+ new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), Optional.empty(), NodeType.tenant);
Status node10newStatus = node10.status();
node10newStatus = node10newStatus
.withVespaVersion(Version.fromString("5.104.142"))
@@ -106,26 +106,26 @@ public class MockNodeRepository extends NodeRepository {
nodes.add(node10);
Node node55 = createNode("node55", "host55.yahoo.com", ipConfig(55), Optional.empty(),
- new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), NodeType.tenant);
+ new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), Optional.empty(), NodeType.tenant);
nodes.add(node55.with(node55.status().withWantToRetire(true).withWantToDeprovision(true)));
/* Setup docker hosts (two of these will be reserved for spares */
nodes.add(createNode("dockerhost1", "dockerhost1.yahoo.com", ipConfig(100, 1, 3), Optional.empty(),
- flavors.getFlavorOrThrow("large"), NodeType.host));
+ flavors.getFlavorOrThrow("large"), Optional.empty(), NodeType.host));
nodes.add(createNode("dockerhost2", "dockerhost2.yahoo.com", ipConfig(101, 1, 3), Optional.empty(),
- flavors.getFlavorOrThrow("large"), NodeType.host));
+ flavors.getFlavorOrThrow("large"), Optional.empty(), NodeType.host));
nodes.add(createNode("dockerhost3", "dockerhost3.yahoo.com", ipConfig(102, 1, 3), Optional.empty(),
- flavors.getFlavorOrThrow("large"), NodeType.host));
+ flavors.getFlavorOrThrow("large"), Optional.empty(), NodeType.host));
nodes.add(createNode("dockerhost4", "dockerhost4.yahoo.com", ipConfig(103, 1, 3), Optional.empty(),
- flavors.getFlavorOrThrow("large"), NodeType.host));
+ flavors.getFlavorOrThrow("large"), Optional.empty(), NodeType.host));
nodes.add(createNode("dockerhost5", "dockerhost5.yahoo.com", ipConfig(104, 1, 3), Optional.empty(),
- flavors.getFlavorOrThrow("large"), NodeType.host));
+ flavors.getFlavorOrThrow("large"), Optional.empty(), NodeType.host));
// Config servers
nodes.add(createNode("cfg1", "cfg1.yahoo.com", ipConfig(201), Optional.empty(),
- flavors.getFlavorOrThrow("default"), NodeType.config));
+ flavors.getFlavorOrThrow("default"), Optional.empty(), NodeType.config));
nodes.add(createNode("cfg2", "cfg2.yahoo.com", ipConfig(202), Optional.empty(),
- flavors.getFlavorOrThrow("default"), NodeType.config));
+ flavors.getFlavorOrThrow("default"), Optional.empty(), NodeType.config));
// Ready all nodes, except 7 and 55
nodes = addNodes(nodes);
@@ -167,9 +167,9 @@ public class MockNodeRepository extends NodeRepository {
List<Node> largeNodes = new ArrayList<>();
largeNodes.add(createNode("node13", "host13.yahoo.com", ipConfig(13), Optional.empty(),
- new Flavor(new NodeResources(10, 48, 500, 1, fast, local)), NodeType.tenant));
+ new Flavor(new NodeResources(10, 48, 500, 1, fast, local)), Optional.empty(), NodeType.tenant));
largeNodes.add(createNode("node14", "host14.yahoo.com", ipConfig(14), Optional.empty(),
- new Flavor(new NodeResources(10, 48, 500, 1, fast, local)), NodeType.tenant));
+ new Flavor(new NodeResources(10, 48, 500, 1, fast, local)), Optional.empty(), NodeType.tenant));
addNodes(largeNodes);
setReady(largeNodes, Agent.system, getClass().getSimpleName());
ApplicationId app4 = ApplicationId.from(TenantName.from("tenant4"), ApplicationName.from("application4"), InstanceName.from("instance4"));
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java
index da174a8d38c..afac44856c9 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java
@@ -133,8 +133,8 @@ public class CapacityCheckerTester {
NodeResources nr = containingNodeResources(childResources,
excessCapacity);
Node node = nodeRepository.createNode(hostname, hostname,
- new IP.Config(Set.of("::"), availableIps), Optional.empty(),
- new Flavor(nr), NodeType.host);
+ new IP.Config(Set.of("::"), availableIps), Optional.empty(),
+ new Flavor(nr), Optional.empty(), NodeType.host);
hosts.add(node);
}
return hosts;
@@ -152,8 +152,8 @@ public class CapacityCheckerTester {
.mapToObj(n -> String.format("%04X::%04X", hostid, n))
.collect(Collectors.toSet());
Node node = nodeRepository.createNode(hostname, hostname,
- new IP.Config(Set.of("::"), availableIps), Optional.empty(),
- new Flavor(capacity), NodeType.host);
+ new IP.Config(Set.of("::"), availableIps), Optional.empty(),
+ new Flavor(capacity), Optional.empty(), NodeType.host);
hosts.add(node);
}
return hosts;
@@ -263,8 +263,8 @@ public class CapacityCheckerTester {
Flavor f = new Flavor(nr);
Node node = nodeRepository.createNode(nodeModel.id, nodeModel.hostname,
- new IP.Config(nodeModel.ipAddresses, nodeModel.additionalIpAddresses),
- nodeModel.parentHostname, f, nodeModel.type);
+ new IP.Config(nodeModel.ipAddresses, nodeModel.additionalIpAddresses),
+ nodeModel.parentHostname, f, Optional.empty(), nodeModel.type);
if (membership != null) {
return node.allocate(owner, membership, node.flavor().resources(), Instant.now());
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 71788fb1a30..c7a1486a1a4 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
@@ -204,7 +204,7 @@ 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());
+ state, allocation, History.empty(), nodeType, new Reports(), Optional.empty(), Optional.empty());
}
}
} \ No newline at end of file
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 539d8c7cff2..2a8783748ef 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
@@ -119,7 +119,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"), NodeType.host);
+ Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), Optional.empty(), NodeType.host);
nodeRepository.addNodes(List.of(dockerHost));
nodeRepository.dirtyRecursively("dockerHost", Agent.system, getClass().getSimpleName());
nodeRepository.setReady("dockerHost", Agent.system, getClass().getSimpleName());
@@ -127,12 +127,12 @@ public class MetricsReporterTest {
Node container1 = Node.createDockerNode(Set.of("::2"), "container1",
"dockerHost", new NodeResources(1, 3, 2, 1), NodeType.tenant);
container1 = container1.with(allocation(Optional.of("app1"), container1).get());
- nodeRepository.addDockerNodes(new LockedNodeList(List.of(container1), nodeRepository.lockAllocation()));
+ nodeRepository.addDockerNodes(new LockedNodeList(List.of(container1), nodeRepository.lockUnallocated()));
Node container2 = Node.createDockerNode(Set.of("::3"), "container2",
"dockerHost", new NodeResources(2, 4, 4, 1), NodeType.tenant);
container2 = container2.with(allocation(Optional.of("app2"), container2).get());
- nodeRepository.addDockerNodes(new LockedNodeList(List.of(container2), nodeRepository.lockAllocation()));
+ nodeRepository.addDockerNodes(new LockedNodeList(List.of(container2), nodeRepository.lockUnallocated()));
Orchestrator orchestrator = mock(Orchestrator.class);
ServiceMonitor serviceMonitor = mock(ServiceMonitor.class);
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 5e44b2e903e..fcf2ab5a52d 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"),
- NodeType.host);
+ Optional.empty(), NodeType.host);
}
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java
index 08e7772b5ba..8e048001b98 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java
@@ -229,7 +229,7 @@ public class SerializationTest {
@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"), 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);
Node deserializedNode = nodeSerializer.fromJson(State.provisioned, nodeSerializer.toJson(node));
assertEquals(parentHostname, deserializedNode.parentHostname().get());
@@ -441,7 +441,8 @@ public class SerializationTest {
Optional.empty(),
Optional.empty(),
nodeFlavors.getFlavorOrThrow("default"),
- NodeType.tenant);
+ Optional.empty(), NodeType.tenant
+ );
}
}
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 7e7338ab9ae..2c01cdde932 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
@@ -81,7 +81,7 @@ 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());
+ parent.isPresent() ? NodeType.tenant : NodeType.host, new Reports(), Optional.empty(), Optional.empty());
}
private Set<String> getAdditionalIP() {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java
index e2015cfd30a..7d9ac230771 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java
@@ -43,9 +43,9 @@ public class DockerHostCapacityTest {
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"), NodeType.host);
- host2 = Node.create("host2", new IP.Config(Set.of("::11"), generateIPs(12, 3)), "host2", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), NodeType.host);
- host3 = Node.create("host3", new IP.Config(Set.of("::21"), generateIPs(22, 1)), "host3", Optional.empty(), Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), 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);
+ 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);
// Add two containers to host1
var nodeA = Node.createDockerNode(Set.of("::2"), "nodeA", "host1", resources1, NodeType.tenant);
@@ -110,7 +110,7 @@ public class DockerHostCapacityTest {
// 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"), 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);
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/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
index b731ab309a6..1e59add8304 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
@@ -14,6 +14,7 @@ import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.OutOfCapacityException;
import com.yahoo.config.provision.ParentHostUnavailableException;
import com.yahoo.config.provision.RegionName;
+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;
@@ -107,6 +108,45 @@ public class DockerProvisioningTest {
assertEquals(nodeCount, activeNodes.size());
}
+ @Test
+ public void reservations_are_respected() {
+ NodeResources resources = new NodeResources(10, 10, 10, 10);
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
+ TenantName tenant1 = TenantName.from("tenant1");
+ TenantName tenant2 = TenantName.from("tenant2");
+ ApplicationId application1_1 = ApplicationId.from(tenant1, ApplicationName.from("application1"), InstanceName.defaultName());
+ ApplicationId application2_1 = ApplicationId.from(tenant2, ApplicationName.from("application1"), InstanceName.defaultName());
+ ApplicationId application2_2 = ApplicationId.from(tenant2, ApplicationName.from("application2"), InstanceName.defaultName());
+
+ tester.makeReadyNodes(10, resources, Optional.of(tenant1), NodeType.host, 1);
+ tester.makeReadyNodes(10, resources, Optional.empty(), NodeType.host, 1);
+ tester.deployZoneApp();
+
+ Version wantedVespaVersion = Version.fromString("6.39");
+ List<HostSpec> nodes = tester.prepare(application2_1,
+ ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContent"), wantedVespaVersion, false),
+ 6, 1, resources);
+ assertHostSpecParentReservation(nodes, Optional.empty(), tester); // We do not get nodes on hosts reserved to tenant1
+ tester.activate(application2_1, nodes);
+
+ try {
+ tester.prepare(application2_2,
+ ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContent"), wantedVespaVersion, false),
+ 5, 1, resources);
+ fail("Expected exception");
+ }
+ catch (OutOfCapacityException e) {
+ // Success: Not enough nonreserved hosts left
+ }
+
+ nodes = tester.prepare(application1_1,
+ ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContent"), wantedVespaVersion, false),
+ 10, 1, resources);
+ assertHostSpecParentReservation(nodes, Optional.of(tenant1), tester);
+ tester.activate(application1_1, nodes);
+ assertNodeParentReservation(tester.getNodes(application1_1).asList(), Optional.empty(), tester); // Reservation is cleared after activation
+ }
+
/** Exclusive app first, then non-exclusive: Should give the same result as below */
@Test
public void docker_application_deployment_with_exclusive_app_first() {
@@ -260,4 +300,16 @@ public class DockerProvisioningTest {
tester.activate(application, hosts);
}
+ private void assertNodeParentReservation(List<Node> nodes, Optional<TenantName> reservation, ProvisioningTester tester) {
+ for (Node node : nodes)
+ assertEquals(reservation, tester.nodeRepository().getNode(node.parentHostname().get()).get().reservedTo());
+ }
+
+ private void assertHostSpecParentReservation(List<HostSpec> hostSpecs, Optional<TenantName> reservation, ProvisioningTester tester) {
+ for (HostSpec hostSpec : hostSpecs) {
+ Node node = tester.nodeRepository().getNode(hostSpec.hostname()).get();
+ assertEquals(reservation, tester.nodeRepository().getNode(node.parentHostname().get()).get().reservedTo());
+ }
+ }
+
}
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 d1498507a7c..53fecbf6095 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
@@ -423,7 +423,9 @@ 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), NodeType.tenant);
+ 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
+ );
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
Node node1aAllocation = node1a.allocate(id, clusterMembership1, node1a.flavor().resources(), Instant.now());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java
index c0e06ef3dff..9145cb9d88f 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java
@@ -47,6 +47,7 @@ import static org.junit.Assert.fail;
* @author freva
*/
public class InPlaceResizeProvisionTest {
+
private static final NodeResources smallResources = new NodeResources(2, 4, 8, 1, NodeResources.DiskSpeed.any, NodeResources.StorageType.any);
private static final NodeResources mediumResources = new NodeResources(4, 8, 16, 1, NodeResources.DiskSpeed.any, NodeResources.StorageType.any);
private static final NodeResources largeResources = new NodeResources(8, 16, 32, 1, NodeResources.DiskSpeed.any, NodeResources.StorageType.any);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
index 6dfca4d2c04..a26e802dfe8 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
@@ -247,7 +247,7 @@ public class LoadBalancerProvisionerTest {
}
private void assignIps(List<Node> nodes) {
- try (var lock = tester.nodeRepository().lockAllocation()) {
+ try (var lock = tester.nodeRepository().lockUnallocated()) {
for (int i = 0; i < nodes.size(); i++) {
tester.nodeRepository().write(nodes.get(i).with(IP.Config.EMPTY.with(Set.of("127.0.0." + i))), lock);
}
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/PrioritizableNodeTest.java
index 718abf7d73d..1d9c135b4b5 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/PrioritizableNodeTest.java
@@ -124,18 +124,26 @@ public class PrioritizableNodeTest {
}
private static Node node(String hostname, Node.State state) {
- 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());
+ 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());
}
private static PrioritizableNode 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());
- 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());
- return new PrioritizableNode(node, totalHostResources.subtract(allocatedHostResources), Optional.of(parent), false, false, true, false);
+ 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());
+ 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);
}
+
}
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 8be0da7c83d..4e63d3cc79f 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
@@ -241,13 +241,16 @@ public class ProvisioningTester {
}
public List<Node> makeReadyNodes(int n, String flavor, NodeType type) {
- return makeReadyNodes(n, asFlavor(flavor, type), type, 0);
+ return makeReadyNodes(n, asFlavor(flavor, type), Optional.empty(), type, 0);
}
public List<Node> makeReadyNodes(int n, NodeResources resources, NodeType type) {
- return makeReadyNodes(n, new Flavor(resources), type, 0);
+ return makeReadyNodes(n, new Flavor(resources), Optional.empty(), type, 0);
}
public List<Node> makeReadyNodes(int n, NodeResources resources, NodeType type, int ipAddressPoolSize) {
- return makeReadyNodes(n, new Flavor(resources), type, ipAddressPoolSize);
+ return makeReadyNodes(n, resources, Optional.empty(), type, ipAddressPoolSize);
+ }
+ public List<Node> makeReadyNodes(int n, NodeResources resources, Optional<TenantName> reservedTo, NodeType type, int ipAddressPoolSize) {
+ return makeReadyNodes(n, new Flavor(resources), reservedTo, type, ipAddressPoolSize);
}
public List<Node> makeProvisionedNodes(int count, String flavor, NodeType type, int ipAddressPoolSize) {
@@ -255,9 +258,9 @@ public class ProvisioningTester {
}
public List<Node> makeProvisionedNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
- return makeProvisionedNodes(n, asFlavor(flavor, type), type, ipAddressPoolSize, dualStack);
+ return makeProvisionedNodes(n, asFlavor(flavor, type), Optional.empty(), type, ipAddressPoolSize, dualStack);
}
- public List<Node> makeProvisionedNodes(int n, Flavor flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
+ public List<Node> makeProvisionedNodes(int n, Flavor flavor, Optional<TenantName> reservedTo, NodeType type, int ipAddressPoolSize, boolean dualStack) {
List<Node> nodes = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
@@ -299,6 +302,7 @@ public class ProvisioningTester {
new IP.Config(hostIps, ipAddressPool),
Optional.empty(),
flavor,
+ reservedTo,
type));
}
nodes = nodeRepository.addNodes(nodes);
@@ -315,11 +319,12 @@ public class ProvisioningTester {
nameResolver.addRecord(hostname, ipv4);
Node node = nodeRepository.createNode(hostname,
- hostname,
- new IP.Config(Set.of(ipv4), Set.of()),
- Optional.empty(),
- nodeFlavors.getFlavorOrThrow(flavor),
- NodeType.config);
+ hostname,
+ new IP.Config(Set.of(ipv4), Set.of()),
+ Optional.empty(),
+ nodeFlavors.getFlavorOrThrow(flavor),
+ Optional.empty(),
+ NodeType.config);
nodes.add(node);
}
@@ -338,17 +343,20 @@ public class ProvisioningTester {
}
public List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize) {
- return makeReadyNodes(n, asFlavor(flavor, type), type, ipAddressPoolSize);
+ return makeReadyNodes(n, asFlavor(flavor, type), Optional.empty(), type, ipAddressPoolSize);
}
- public List<Node> makeReadyNodes(int n, Flavor flavor, NodeType type, int ipAddressPoolSize) {
- return makeReadyNodes(n, flavor, type, ipAddressPoolSize, false);
+ public List<Node> makeReadyNodes(int n, Flavor flavor, Optional<TenantName> reservedTo, NodeType type, int ipAddressPoolSize) {
+ return makeReadyNodes(n, flavor, reservedTo, type, ipAddressPoolSize, false);
}
public List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
return makeReadyNodes(n, asFlavor(flavor, type), type, ipAddressPoolSize, dualStack);
}
public List<Node> makeReadyNodes(int n, Flavor flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
- List<Node> nodes = makeProvisionedNodes(n, flavor, type, ipAddressPoolSize, dualStack);
+ return makeReadyNodes(n, flavor, Optional.empty(), type, ipAddressPoolSize, dualStack);
+ }
+ public List<Node> makeReadyNodes(int n, Flavor flavor, Optional<TenantName> reservedTo, NodeType type, int ipAddressPoolSize, boolean dualStack) {
+ List<Node> nodes = makeProvisionedNodes(n, flavor, reservedTo, type, ipAddressPoolSize, dualStack);
nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
return nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java
index 18f0d989900..1a1cbdea6b8 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java
@@ -6,6 +6,7 @@ import com.yahoo.application.container.JDisc;
import com.yahoo.application.container.handler.Request;
import com.yahoo.application.container.handler.Response;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.io.IOUtils;
import com.yahoo.text.Utf8;
import com.yahoo.vespa.hosted.provision.NodeRepository;
@@ -23,6 +24,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -106,7 +108,7 @@ public class RestApiTest {
assertResponse(new Request("http://localhost:8080/nodes/v2/node",
("[" + asNodeJson("host8.yahoo.com", "default", "127.0.8.1") + "," + // test with only 1 ip address
asNodeJson("host9.yahoo.com", "large-variant", "127.0.9.1", "::9:1") + "," +
- asHostJson("parent2.yahoo.com", "large-variant", "127.0.127.1", "::127:1") + "," +
+ asHostJson("parent2.yahoo.com", "large-variant", Optional.of(TenantName.from("myTenant")), "127.0.127.1", "::127:1") + "," +
asDockerNodeJson("host11.yahoo.com", "parent.host.yahoo.com", "::11") + "]").
getBytes(StandardCharsets.UTF_8),
Request.Method.POST),
@@ -292,7 +294,7 @@ public class RestApiTest {
// Attempt to POST host node with already assigned IP
assertResponse(new Request("http://localhost:8080/nodes/v2/node",
- "[" + asHostJson("host200.yahoo.com", "default", "127.0.2.1") + "]",
+ "[" + asHostJson("host200.yahoo.com", "default", Optional.empty(), "127.0.2.1") + "]",
Request.Method.POST), 400,
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Cannot assign [127.0.2.1] to host200.yahoo.com: [127.0.2.1] already assigned to host2.yahoo.com\"}");
@@ -304,7 +306,7 @@ public class RestApiTest {
// Node types running a single container can share their IP address with child node
assertResponse(new Request("http://localhost:8080/nodes/v2/node",
- "[" + asNodeJson("cfghost42.yahoo.com", NodeType.confighost, "default", "127.0.42.1") + "]",
+ "[" + asNodeJson("cfghost42.yahoo.com", NodeType.confighost, "default", Optional.empty(), "127.0.42.1") + "]",
Request.Method.POST), 200,
"{\"message\":\"Added 1 nodes to the provisioned state\"}");
assertResponse(new Request("http://localhost:8080/nodes/v2/node",
@@ -320,7 +322,7 @@ public class RestApiTest {
// ... nor with child node on different host
assertResponse(new Request("http://localhost:8080/nodes/v2/node",
- "[" + asNodeJson("cfghost43.yahoo.com", NodeType.confighost, "default", "127.0.43.1") + "]",
+ "[" + asNodeJson("cfghost43.yahoo.com", NodeType.confighost, "default", Optional.empty(), "127.0.43.1") + "]",
Request.Method.POST), 200,
"{\"message\":\"Added 1 nodes to the provisioned state\"}");
assertResponse(new Request("http://localhost:8080/nodes/v2/node/cfg42.yahoo.com",
@@ -921,14 +923,15 @@ public class RestApiTest {
"\"flavor\":\"" + flavor + "\"}";
}
- private static String asHostJson(String hostname, String flavor, String... ipAddress) {
- return asNodeJson(hostname, NodeType.host, flavor, ipAddress);
+ private static String asHostJson(String hostname, String flavor, Optional<TenantName> reservedTo, String... ipAddress) {
+ return asNodeJson(hostname, NodeType.host, flavor, reservedTo, ipAddress);
}
- private static String asNodeJson(String hostname, NodeType nodeType, String flavor, String... ipAddress) {
+ private static String asNodeJson(String hostname, NodeType nodeType, String flavor, Optional<TenantName> reservedTo, String... ipAddress) {
return "{\"hostname\":\"" + hostname + "\", \"openStackId\":\"" + hostname + "\"," +
createIpAddresses(ipAddress) +
"\"flavor\":\"" + flavor + "\"" +
+ (reservedTo.isPresent() ? ", \"reservedTo\":\"" + reservedTo.get().value() + "\"" : "") +
", \"type\":\"" + nodeType + "\"}";
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json
index 8be034cb036..ecc497172c7 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent2.json
@@ -6,6 +6,7 @@
"hostname": "parent2.yahoo.com",
"openStackId": "parent2.yahoo.com",
"flavor": "large-variant",
+ "reservedTo": "myTenant",
"cpuCores": 64.0,
"resources": {"vcpu":64.0,"memoryGb":128.0,"diskGb":2000.0,"bandwidthGbps":15.0,"diskSpeed":"fast","storageType":"remote"},
"environment": "BARE_METAL",