summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java86
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java16
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java1
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java11
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java16
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json1
12 files changed, 97 insertions, 48 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 32f6ff32319..44e9e0a0d42 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
@@ -56,6 +56,7 @@ public final class Node implements Nodelike {
private final Optional<String> modelName;
private final Optional<TenantName> reservedTo;
private final Optional<ApplicationId> exclusiveToApplicationId;
+ private final Optional<ApplicationId> provisionedForApplicationId;
private final Optional<Duration> hostTTL;
private final Optional<Instant> hostEmptyAt;
private final Optional<ClusterSpec.Type> exclusiveToClusterType;
@@ -93,9 +94,9 @@ public final class Node implements Nodelike {
public Node(String id, Optional<String> extraId, 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,
- Optional<ApplicationId> exclusiveToApplicationId, Optional<Duration> hostTTL, Optional<Instant> hostEmptyAt,
- Optional<ClusterSpec.Type> exclusiveToClusterType, Optional<String> switchHostname,
- List<TrustStoreItem> trustStoreItems, CloudAccount cloudAccount,
+ Optional<ApplicationId> exclusiveToApplicationId, Optional<ApplicationId> provisionedForApplicationId,
+ Optional<Duration> hostTTL, Optional<Instant> hostEmptyAt, Optional<ClusterSpec.Type> exclusiveToClusterType,
+ Optional<String> switchHostname, List<TrustStoreItem> trustStoreItems, CloudAccount cloudAccount,
Optional<WireguardKeyWithTimestamp> wireguardPubKey) {
this.id = Objects.requireNonNull(id, "A node must have an ID");
this.extraId = Objects.requireNonNull(extraId, "Extra ID cannot be null");
@@ -112,6 +113,7 @@ public final class Node implements Nodelike {
this.modelName = Objects.requireNonNull(modelName, "A null modelName is not permitted");
this.reservedTo = Objects.requireNonNull(reservedTo, "reservedTo cannot be null");
this.exclusiveToApplicationId = Objects.requireNonNull(exclusiveToApplicationId, "exclusiveToApplicationId cannot be null");
+ this.provisionedForApplicationId = Objects.requireNonNull(exclusiveToApplicationId, "provisionedForApplicationId cannot be null");
this.hostTTL = Objects.requireNonNull(hostTTL, "hostTTL cannot be null");
this.hostEmptyAt = Objects.requireNonNull(hostEmptyAt, "hostEmptyAt cannot be null");
this.exclusiveToClusterType = Objects.requireNonNull(exclusiveToClusterType, "exclusiveToClusterType cannot be null");
@@ -141,6 +143,9 @@ public final class Node implements Nodelike {
if (type != NodeType.host && exclusiveToApplicationId.isPresent())
throw new IllegalArgumentException("Only tenant hosts can be exclusive to an application");
+ if (provisionedForApplicationId.isPresent() && ! exclusiveToApplicationId.equals(provisionedForApplicationId))
+ throw new IllegalArgumentException("exclusiveToApplicationId must be the same as provisionedForApplicationId when this is set");
+
if (type != NodeType.host && hostTTL.isPresent())
throw new IllegalArgumentException("Only tenant hosts can have a TTL");
@@ -221,12 +226,21 @@ public final class Node implements Nodelike {
/**
* Returns the application this host is exclusive to, if any. Only tenant hosts can be exclusive to an application.
- * If this is set, resources on this host cannot be allocated to any other application. This is set during
- * provisioning and applies for the entire lifetime of the host
+ * If this is set, resources on this host cannot be allocated to any other application. Additionally, the host will
+ * not be reused once its allocated containers are deleted, i.e., this property can only be set <em>once</em> per host.
*/
public Optional<ApplicationId> exclusiveToApplicationId() { return exclusiveToApplicationId; }
/**
+ * Returns the application this host was provisioned specifically for, if any. Only tenant hosts can be exclusive
+ * to an application. This property, when set, also implies {@link #exclusiveToApplicationId()}.
+ * This is set during provisioning and applies for the entire lifetime of the host. Provisioning a host specifically
+ * for an application allows access to application-specific resources, through integration with cloud providers'
+ * provisioning-with-secrets mechanisms.
+ */
+ public Optional<ApplicationId> provisionedForApplicationId() { return provisionedForApplicationId; }
+
+ /**
* Returns the additional time to live of tenant host, in a dynamically provisioned zone, after all its child
* nodes are removed, before being deprovisioned, if any.
* This is set during provisioning and applies for the entire lifetime of the host.
@@ -359,14 +373,14 @@ public final class Node implements Nodelike {
/** Returns a node with the status assigned to the given value */
public Node with(Status status) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type,
- reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a node with the type assigned to the given value */
public Node with(NodeType type) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type,
- reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
@@ -375,35 +389,35 @@ public final class Node implements Nodelike {
if (flavor.equals(this.flavor)) return this;
History updateHistory = history.with(new History.Event(History.Event.Type.resized, agent, instant));
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, updateHistory, type,
- reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with the reboot generation set to generation */
public Node withReboot(Generation generation) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status.withReboot(generation), state, allocation,
- history, type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ history, type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with given id set */
public Node withId(String id) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation,
- history, type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ history, type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with model name set to given value */
public Node withModelName(String modelName) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, Optional.of(modelName), reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, Optional.of(modelName), reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this with model name cleared */
public Node withoutModelName() {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, Optional.empty(), reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, Optional.empty(), reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
@@ -445,21 +459,21 @@ public final class Node implements Nodelike {
*/
public Node with(Allocation allocation) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, Optional.of(allocation), history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this node with IP config set to the given value. */
public Node with(IP.Config ipConfig) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this node with the parent hostname assigned to the given value. */
public Node withParentHostname(String parentHostname) {
return new Node(id, extraId, ipConfig, hostname, Optional.of(parentHostname), flavor, status, state, allocation,
- history, type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ history, type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
@@ -467,50 +481,56 @@ public final class Node implements Nodelike {
if (type != NodeType.host)
throw new IllegalArgumentException("Only host nodes can be reserved, " + hostname + " has type " + type);
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, Optional.of(tenant), exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, Optional.of(tenant), exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
/** Returns a copy of this node which is not reserved to a tenant */
public Node withoutReservedTo() {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, Optional.empty(), exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, Optional.empty(), exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withExclusiveToApplicationId(ApplicationId exclusiveTo) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, Optional.ofNullable(exclusiveTo), hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, Optional.ofNullable(exclusiveTo), provisionedForApplicationId.filter(__ -> exclusiveTo != null), hostTTL, hostEmptyAt,
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ }
+
+ public Node withProvisionedForApplicationId(ApplicationId provisionedFor) {
+ return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
+ type, reports, modelName, reservedTo, Optional.ofNullable(provisionedFor), Optional.ofNullable(provisionedFor), hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withExtraId(Optional<String> extraId) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withHostTTL(Duration hostTTL) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, Optional.ofNullable(hostTTL), hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, Optional.ofNullable(hostTTL), hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withHostEmptyAt(Instant hostEmptyAt) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, Optional.ofNullable(hostEmptyAt),
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, Optional.ofNullable(hostEmptyAt),
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withExclusiveToClusterType(ClusterSpec.Type exclusiveTo) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
Optional.ofNullable(exclusiveTo), switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node withWireguardPubkey(WireguardKeyWithTimestamp wireguardPubkey) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount,
Optional.ofNullable(wireguardPubkey));
}
@@ -518,7 +538,7 @@ public final class Node implements Nodelike {
/** Returns a copy of this node with switch hostname set to given value */
public Node withSwitchHostname(String switchHostname) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, Optional.ofNullable(switchHostname), trustStoreItems, cloudAccount,
wireguardPubKey);
}
@@ -572,19 +592,19 @@ public final class Node implements Nodelike {
/** Returns a copy of this node with the given history. */
public Node with(History history) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node with(Reports reports) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
public Node with(List<TrustStoreItem> trustStoreItems) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
- type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, provisionedForApplicationId, hostTTL, hostEmptyAt,
exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
}
@@ -722,6 +742,7 @@ public final class Node implements Nodelike {
private String modelName;
private TenantName reservedTo;
private ApplicationId exclusiveToApplicationId;
+ private ApplicationId provisionedForApplicationId;
private Duration hostTTL;
private Instant hostEmptyAt;
private ClusterSpec.Type exclusiveToClusterType;
@@ -763,6 +784,11 @@ public final class Node implements Nodelike {
return this;
}
+ public Builder provisionedForApplicationId(ApplicationId provisionedFor) {
+ this.provisionedForApplicationId = provisionedFor;
+ return exclusiveToApplicationId(provisionedFor);
+ }
+
public Builder hostTTL(Duration hostTTL) {
this.hostTTL = hostTTL;
return this;
@@ -833,9 +859,9 @@ public final class Node implements Nodelike {
flavor, Optional.ofNullable(status).orElseGet(Status::initial), state, Optional.ofNullable(allocation),
Optional.ofNullable(history).orElseGet(History::empty), type, Optional.ofNullable(reports).orElseGet(Reports::new),
Optional.ofNullable(modelName), Optional.ofNullable(reservedTo), Optional.ofNullable(exclusiveToApplicationId),
- Optional.ofNullable(hostTTL), Optional.ofNullable(hostEmptyAt), Optional.ofNullable(exclusiveToClusterType),
- Optional.ofNullable(switchHostname), Optional.ofNullable(trustStoreItems).orElseGet(List::of), cloudAccount,
- Optional.ofNullable(wireguardPubKey));
+ Optional.ofNullable(provisionedForApplicationId), Optional.ofNullable(hostTTL), Optional.ofNullable(hostEmptyAt),
+ Optional.ofNullable(exclusiveToClusterType), Optional.ofNullable(switchHostname),
+ Optional.ofNullable(trustStoreItems).orElseGet(List::of), cloudAccount, Optional.ofNullable(wireguardPubKey));
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
index 976f3543298..4b81e580b64 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
@@ -221,7 +221,7 @@ public class CuratorDb {
toState.isAllocated() ? node.allocation() : Optional.empty(),
node.history().recordStateTransition(node.state(), toState, agent, clock.instant()),
node.type(), node.reports(), node.modelName(), node.reservedTo(),
- node.exclusiveToApplicationId(), node.hostTTL(), node.hostEmptyAt(),
+ node.exclusiveToApplicationId(), node.provisionedForApplicationId(), node.hostTTL(), node.hostEmptyAt(),
node.exclusiveToClusterType(), node.switchHostname(), node.trustedCertificates(),
node.cloudAccount(), node.wireguardPubKey());
curatorTransaction.add(createOrSet(nodePath(newNode), nodeSerializer.toJson(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 afdedabcf71..40d8394142b 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
@@ -50,7 +50,7 @@ import java.util.Optional;
*/
public class NodeSerializer {
- // WARNING: Since there are multiple servers in a ZooKeeper cluster and they upgrade one by one
+ // WARNING: Since there are multiple servers in a ZooKeeper cluster, and they upgrade one by one
// (and rewrite all nodes on startup), changes to the serialized format must be made
// such that what is serialized on version N+1 can be read by version N:
// - ADDING FIELDS: Always ok
@@ -92,6 +92,7 @@ public class NodeSerializer {
private static final String modelNameKey = "modelName";
private static final String reservedToKey = "reservedTo";
private static final String exclusiveToApplicationIdKey = "exclusiveTo";
+ private static final String provisionedForApplicationIdKey = "provisionedFor";
private static final String hostTTLKey = "hostTTL";
private static final String hostEmptyAtKey = "hostEmptyAt";
private static final String exclusiveToClusterTypeKey = "exclusiveToClusterType";
@@ -182,6 +183,7 @@ public class NodeSerializer {
node.modelName().ifPresent(modelName -> object.setString(modelNameKey, modelName));
node.reservedTo().ifPresent(tenant -> object.setString(reservedToKey, tenant.value()));
node.exclusiveToApplicationId().ifPresent(applicationId -> object.setString(exclusiveToApplicationIdKey, applicationId.serializedForm()));
+ node.provisionedForApplicationId().ifPresent(applicationId -> object.setString(provisionedForApplicationIdKey, applicationId.serializedForm()));
node.hostTTL().ifPresent(hostTTL -> object.setLong(hostTTLKey, hostTTL.toMillis()));
node.hostEmptyAt().ifPresent(emptyAt -> object.setLong(hostEmptyAtKey, emptyAt.toEpochMilli()));
node.exclusiveToClusterType().ifPresent(clusterType -> object.setString(exclusiveToClusterTypeKey, clusterType.name()));
@@ -281,6 +283,7 @@ public class NodeSerializer {
SlimeUtils.optionalString(object.field(modelNameKey)),
SlimeUtils.optionalString(object.field(reservedToKey)).map(TenantName::from),
SlimeUtils.optionalString(object.field(exclusiveToApplicationIdKey)).map(ApplicationId::fromSerializedForm),
+ SlimeUtils.optionalString(object.field(exclusiveToApplicationIdKey)).map(ApplicationId::fromSerializedForm), // TODO: change to provisionedForApplicationIdKey
SlimeUtils.optionalDuration(object.field(hostTTLKey)),
SlimeUtils.optionalInstant(object.field(hostEmptyAtKey)),
SlimeUtils.optionalString(object.field(exclusiveToClusterTypeKey)).map(ClusterSpec.Type::from),
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 fbfa9649e59..7da80440667 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
@@ -30,7 +30,7 @@ public class ProvisionedHost {
private final String hostHostname;
private final Flavor hostFlavor;
private final NodeType hostType;
- private final Optional<ApplicationId> exclusiveToApplicationId;
+ private final Optional<ApplicationId> provisionedForApplicationId;
private final Optional<ClusterSpec.Type> exclusiveToClusterType;
private final List<HostName> nodeHostnames;
private final NodeResources nodeResources;
@@ -38,7 +38,7 @@ public class ProvisionedHost {
private final CloudAccount cloudAccount;
public ProvisionedHost(String id, String hostHostname, Flavor hostFlavor, NodeType hostType,
- Optional<ApplicationId> exclusiveToApplicationId, Optional<ClusterSpec.Type> exclusiveToClusterType,
+ Optional<ApplicationId> provisionedForApplicationId, Optional<ClusterSpec.Type> exclusiveToClusterType,
List<HostName> nodeHostnames, NodeResources nodeResources,
Version osVersion, CloudAccount cloudAccount) {
if (!hostType.isHost()) throw new IllegalArgumentException(hostType + " is not a host");
@@ -46,7 +46,7 @@ public class ProvisionedHost {
this.hostHostname = Objects.requireNonNull(hostHostname, "Host hostname must be set");
this.hostFlavor = Objects.requireNonNull(hostFlavor, "Host flavor must be set");
this.hostType = Objects.requireNonNull(hostType, "Host type must be set");
- this.exclusiveToApplicationId = Objects.requireNonNull(exclusiveToApplicationId, "exclusiveToApplicationId must be set");
+ this.provisionedForApplicationId = Objects.requireNonNull(provisionedForApplicationId, "provisionedForApplicationId must be set");
this.exclusiveToClusterType = Objects.requireNonNull(exclusiveToClusterType, "exclusiveToClusterType must be set");
this.nodeHostnames = validateNodeAddresses(nodeHostnames);
this.nodeResources = Objects.requireNonNull(nodeResources, "Node resources must be set");
@@ -67,7 +67,7 @@ public class ProvisionedHost {
Node.Builder builder = Node.create(id, IP.Config.of(List.of(), List.of(), nodeHostnames), hostHostname, hostFlavor, hostType)
.status(Status.initial().withOsVersion(OsVersion.EMPTY.withCurrent(Optional.of(osVersion))))
.cloudAccount(cloudAccount);
- exclusiveToApplicationId.ifPresent(builder::exclusiveToApplicationId);
+ provisionedForApplicationId.ifPresent(builder::provisionedForApplicationId);
exclusiveToClusterType.ifPresent(builder::exclusiveToClusterType);
if ( ! hostTTL.isZero()) builder.hostTTL(hostTTL);
return builder.build();
@@ -84,7 +84,7 @@ public class ProvisionedHost {
public String hostHostname() { return hostHostname; }
public Flavor hostFlavor() { return hostFlavor; }
public NodeType hostType() { return hostType; }
- public Optional<ApplicationId> exclusiveToApplicationId() { return exclusiveToApplicationId; }
+ public Optional<ApplicationId> provisionedForApplicationId() { return provisionedForApplicationId; }
public Optional<ClusterSpec.Type> exclusiveToClusterType() { return exclusiveToClusterType; }
public List<HostName> nodeHostnames() { return nodeHostnames; }
public NodeResources nodeResources() { return nodeResources; }
@@ -102,7 +102,7 @@ public class ProvisionedHost {
hostHostname.equals(that.hostHostname) &&
hostFlavor.equals(that.hostFlavor) &&
hostType == that.hostType &&
- exclusiveToApplicationId.equals(that.exclusiveToApplicationId) &&
+ provisionedForApplicationId.equals(that.provisionedForApplicationId) &&
exclusiveToClusterType.equals(that.exclusiveToClusterType) &&
nodeHostnames.equals(that.nodeHostnames) &&
nodeResources.equals(that.nodeResources) &&
@@ -112,7 +112,7 @@ public class ProvisionedHost {
@Override
public int hashCode() {
- return Objects.hash(id, hostHostname, hostFlavor, hostType, exclusiveToApplicationId, exclusiveToClusterType, nodeHostnames, nodeResources, osVersion, cloudAccount);
+ return Objects.hash(id, hostHostname, hostFlavor, hostType, provisionedForApplicationId, exclusiveToClusterType, nodeHostnames, nodeResources, osVersion, cloudAccount);
}
@Override
@@ -122,7 +122,7 @@ public class ProvisionedHost {
", hostHostname='" + hostHostname + '\'' +
", hostFlavor=" + hostFlavor +
", hostType=" + hostType +
- ", exclusiveToApplicationId=" + exclusiveToApplicationId +
+ ", provisionedForApplicationId=" + provisionedForApplicationId +
", exclusiveToClusterType=" + exclusiveToClusterType +
", nodeAddresses=" + nodeHostnames +
", nodeResources=" + nodeResources +
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 9825e98acdb..19b9fc26fd3 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
@@ -263,6 +263,8 @@ public class NodePatcher {
case "exclusiveTo":
case "exclusiveToApplicationId":
return node.withExclusiveToApplicationId(SlimeUtils.optionalString(value).map(ApplicationId::fromSerializedForm).orElse(null));
+ case "provisionedFor":
+ return node.withProvisionedForApplicationId(SlimeUtils.optionalString(value).map(ApplicationId::fromSerializedForm).orElse(null));
case "hostTTL":
return node.withHostTTL(SlimeUtils.optionalDuration(value).orElse(null));
case "hostEmptyAt":
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 d7cb7b4a33a..73e48d6df55 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
@@ -134,6 +134,7 @@ class NodesResponse extends SlimeJsonResponse {
object.setString("flavor", node.flavor().name());
node.reservedTo().ifPresent(reservedTo -> object.setString("reservedTo", reservedTo.value()));
node.exclusiveToApplicationId().ifPresent(applicationId -> object.setString("exclusiveTo", applicationId.serializedForm()));
+ node.provisionedForApplicationId().ifPresent(applicationId -> object.setString("provisionedFor", applicationId.serializedForm()));
node.hostTTL().ifPresent(ttl -> object.setLong("hostTTL", ttl.toMillis()));
node.hostEmptyAt().ifPresent(emptyAt -> object.setLong("hostEmptyAt", emptyAt.toEpochMilli()));
node.exclusiveToClusterType().ifPresent(clusterType -> object.setString("exclusiveToClusterType", clusterType.name()));
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 060bac4b732..1ed138625ae 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
@@ -299,6 +299,7 @@ public class NodesV2ApiHandler extends ThreadedHttpRequestHandler {
optionalString(inspector.field("parentHostname")).ifPresent(builder::parentHostname);
optionalString(inspector.field("modelName")).ifPresent(builder::modelName);
optionalString(inspector.field("reservedTo")).map(TenantName::from).ifPresent(builder::reservedTo);
+ optionalString(inspector.field("provisionedFor")).map(ApplicationId::fromSerializedForm).ifPresent(builder::provisionedForApplicationId);
optionalString(inspector.field("exclusiveTo")).map(ApplicationId::fromSerializedForm).ifPresent(builder::exclusiveToApplicationId);
optionalString(inspector.field("switchHostname")).ifPresent(builder::switchHostname);
return builder.build();
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 146d2cad425..cc414cc50c2 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
@@ -365,7 +365,7 @@ public class MetricsReporterTest {
Capacity capacity = Capacity.from(new ClusterResources(4, 1, resources));
tester.deploy(app, spec, capacity);
- // Host are now in use
+ // Hosts are now in use
metricsReporter.maintain();
assertEquals(0, metric.values.get("nodes.emptyExclusive").intValue());
}
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 c0958029bf5..4aab8b683b0 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
@@ -480,14 +480,19 @@ public class NodeSerializerTest {
assertFalse(node.hostTTL().isPresent());
assertFalse(node.exclusiveToClusterType().isPresent());
- ApplicationId exclusiveToApp = ApplicationId.from("tenant1", "app1", "instance1");
+ ApplicationId provisionedForApp = ApplicationId.from("tenant1", "app1", "instance1");
+ node = nodeSerializer.fromJson(nodeSerializer.toJson(builder.exclusiveToApplicationId(provisionedForApp).build()));
+ assertEquals(Optional.of(provisionedForApp), node.exclusiveToApplicationId());
+ // assertEquals(Optional.empty(), node.provisionedForApplicationId()); TODO: enable once serialisation phase 1 is done
+
ClusterSpec.Type exclusiveToCluster = ClusterSpec.Type.admin;
- node = builder.exclusiveToApplicationId(exclusiveToApp)
+ node = builder.provisionedForApplicationId(provisionedForApp)
.hostTTL(Duration.ofDays(1))
.hostEmptyAt(clock.instant().minus(Duration.ofDays(1)).truncatedTo(MILLIS))
.exclusiveToClusterType(exclusiveToCluster).build();
node = nodeSerializer.fromJson(nodeSerializer.toJson(node));
- assertEquals(exclusiveToApp, node.exclusiveToApplicationId().get());
+ assertEquals(provisionedForApp, node.exclusiveToApplicationId().get());
+ assertEquals(provisionedForApp, node.provisionedForApplicationId().get());
assertEquals(Duration.ofDays(1), node.hostTTL().get());
assertEquals(clock.instant().minus(Duration.ofDays(1)).truncatedTo(MILLIS), node.hostEmptyAt().get());
assertEquals(exclusiveToCluster, node.exclusiveToClusterType().get());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java
index 973014566a0..5927cb43c3a 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTest.java
@@ -238,7 +238,7 @@ public class DynamicProvisioningTest {
tester.patchNodes(initialNodes.asList(), node -> node.removable(true));
NodeList exclusiveViolators = nodes.owner(application1).not().retired().first(2);
List<Node> parents = exclusiveViolators.mapToList(node -> nodes.parentOf(node).get());
- tester.patchNode(parents.get(0), node -> node.withExclusiveToApplicationId(ApplicationId.defaultId()));
+ tester.patchNode(parents.get(0), node -> node.withProvisionedForApplicationId(ApplicationId.defaultId()));
tester.patchNode(parents.get(1), node -> node.withExclusiveToClusterType(ClusterSpec.Type.container));
prepareAndActivate(application1, clusterSpec("mycluster"), 4, 1, smallerExclusiveResources, tester);
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 cdbbb3b8126..72c1e2e4ec3 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
@@ -1020,10 +1020,14 @@ public class NodesV2ApiTest {
String url = "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com";
tester.assertPartialResponse(new Request(url), "exclusiveTo", false); // Initially there is no exclusiveTo
- assertResponse(new Request(url, Utf8.toBytes("{\"exclusiveToApplicationId\": \"t1:a1:i1\"}"), Request.Method.PATCH),
+ assertResponse(new Request(url, Utf8.toBytes("{\"exclusiveTo\": \"t1:a1:i1\"}"), Request.Method.PATCH),
"{\"message\":\"Updated dockerhost1.yahoo.com\"}");
tester.assertPartialResponse(new Request(url), "\"exclusiveTo\":\"t1:a1:i1\",", true);
+ assertResponse(new Request(url, Utf8.toBytes("{\"provisionedFor\": \"t1:a1:i1\"}"), Request.Method.PATCH),
+ "{\"message\":\"Updated dockerhost1.yahoo.com\"}");
+ tester.assertPartialResponse(new Request(url), "\"provisionedFor\":\"t1:a1:i1\",", true);
+
assertResponse(new Request(url, Utf8.toBytes("{\"hostTTL\": 86400000}"), Request.Method.PATCH),
"{\"message\":\"Updated dockerhost1.yahoo.com\"}");
tester.assertPartialResponse(new Request(url), "\"hostTTL\":86400000", true);
@@ -1033,11 +1037,17 @@ public class NodesV2ApiTest {
tester.assertPartialResponse(new Request(url), "\"hostEmptyAt\":789", true);
assertResponse(new Request(url, Utf8.toBytes("{\"exclusiveToClusterType\": \"admin\"}"), Request.Method.PATCH),
- "{\"message\":\"Updated dockerhost1.yahoo.com\"}");
+ "{\"message\":\"Updated dockerhost1.yahoo.com\"}");
tester.assertPartialResponse(new Request(url), "\"exclusiveToClusterType\":\"admin\",", true);
+ tester.assertResponse(new Request(url, Utf8.toBytes("{\"exclusiveTo\": \"t1:a1:i2\"}"), Request.Method.PATCH),
+ 400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not set field 'exclusiveTo': exclusiveToApplicationId must be the same as provisionedForApplicationId when this is set\"}");
+
+ assertResponse(new Request(url, Utf8.toBytes("{\"provisionedFor\": null}"), Request.Method.PATCH),
+ "{\"message\":\"Updated dockerhost1.yahoo.com\"}");
assertResponse(new Request(url, Utf8.toBytes("{\"exclusiveTo\": null, \"hostTTL\":null, \"hostEmptyAt\":null, \"exclusiveToClusterType\": null}"), Request.Method.PATCH),
- "{\"message\":\"Updated dockerhost1.yahoo.com\"}");
+ "{\"message\":\"Updated dockerhost1.yahoo.com\"}");
+
tester.assertPartialResponse(new Request(url), "\"exclusiveTo", false);
tester.assertPartialResponse(new Request(url), "\"hostTTL\"", false);
tester.assertPartialResponse(new Request(url), "\"hostEmptyAt\"", false);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json
index 9979f5fc5c7..4bdd0d41999 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json
@@ -7,6 +7,7 @@
"flavor": "large-variant",
"reservedTo": "myTenant",
"exclusiveTo": "tenant1:app1:instance1",
+ "provisionedFor": "tenant1:app1:instance1",
"cpuCores": 64.0,
"resources": {
"vcpu": 64.0,