aboutsummaryrefslogtreecommitdiffstats
path: root/node-repository
diff options
context:
space:
mode:
authorgjoranv <gjoranv@gmail.com>2023-09-05 13:17:10 +0200
committergjoranv <gjoranv@gmail.com>2023-09-11 18:25:56 +0200
commit1f55e759b1830bc8f2386d7bc5db71e524327620 (patch)
tree26bb44c172fc3812dc52b10d74c7086d418425b0 /node-repository
parent0a383addced96943dd7e94ed50ec7006b3e282e1 (diff)
Add wireguard key timestamp to node repo.
Diffstat (limited to 'node-repository')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java91
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java7
-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/restapi/NodePatcher.java5
-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/testutils/MockNodeRepository.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json3
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json3
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json3
10 files changed, 91 insertions, 36 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 864566f119e..8db8e7c2cf8 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
@@ -65,6 +65,7 @@ public final class Node implements Nodelike {
/** Only set for configservers and exclave nodes */
private final Optional<WireguardKey> wireguardPubKey;
+ private final Optional<Instant> wireguardKeyTimestamp;
/** Record of the last event of each type happening to this node */
private final History history;
@@ -95,7 +96,8 @@ public final class Node implements Nodelike {
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<WireguardKey> wireguardPubKey) {
+ List<TrustStoreItem> trustStoreItems, CloudAccount cloudAccount, Optional<WireguardKey> wireguardPubKey,
+ Optional<Instant> wireguardKeyTimestamp) {
this.id = Objects.requireNonNull(id, "A node must have an ID");
this.extraId = Objects.requireNonNull(extraId, "Extra ID cannot be null");
this.hostname = requireNonEmptyString(hostname, "A node must have a hostname");
@@ -118,6 +120,7 @@ public final class Node implements Nodelike {
this.trustStoreItems = Objects.requireNonNull(trustStoreItems).stream().distinct().toList();
this.cloudAccount = Objects.requireNonNull(cloudAccount);
this.wireguardPubKey = Objects.requireNonNull(wireguardPubKey);
+ this.wireguardKeyTimestamp = Objects.requireNonNull(wireguardKeyTimestamp);
if (state == State.active)
requireNonEmpty(ipConfig.primary(), "Active node " + hostname + " must have at least one valid IP address");
@@ -265,6 +268,11 @@ public final class Node implements Nodelike {
return wireguardPubKey;
}
+ /** Returns the timestamp of the wireguard key of this node. Only relevant for enclave nodes. */
+ public Optional<Instant> wireguardKeyTimestamp() {
+ return wireguardKeyTimestamp;
+ }
+
/**
* Returns a copy of this where wantToFail is set to true and history is updated to reflect this.
*/
@@ -359,14 +367,16 @@ public final class Node implements Nodelike {
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** Returns a node with the flavor assigned to the given value */
@@ -375,35 +385,40 @@ public final class Node implements Nodelike {
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** Returns a copy of this with a history record saying it was detected to be down at this instant */
@@ -445,21 +460,24 @@ 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
public Node withReservedTo(TenantName tenant) {
@@ -467,57 +485,73 @@ public final class Node implements Nodelike {
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** 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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
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),
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
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,
- Optional.ofNullable(exclusiveTo), switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ Optional.ofNullable(exclusiveTo), switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
public Node withWireguardPubkey(WireguardKey wireguardPubkey) {
return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, Optional.ofNullable(wireguardPubkey));
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, Optional.ofNullable(wireguardPubkey),
+ wireguardKeyTimestamp);
+ }
+
+ public Node withWireguardKeyTimestamp(Instant wireguardKeyTimestamp) {
+ return new Node(id, extraId, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history,
+ type, reports, modelName, reservedTo, exclusiveToApplicationId, hostTTL, hostEmptyAt,
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ Optional.ofNullable(wireguardKeyTimestamp));
}
/** 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,
- exclusiveToClusterType, Optional.ofNullable(switchHostname), trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, Optional.ofNullable(switchHostname), trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
/** Returns a copy of this node with switch hostname unset */
@@ -570,19 +604,22 @@ public final class Node implements Nodelike {
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
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,
- exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey);
+ exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey,
+ wireguardKeyTimestamp);
}
private static Optional<String> requireNonEmptyString(Optional<String> value, String message) {
@@ -731,6 +768,7 @@ public final class Node implements Nodelike {
private List<TrustStoreItem> trustStoreItems;
private CloudAccount cloudAccount = CloudAccount.empty;
private WireguardKey wireguardPubKey;
+ private Instant wireguardKeyTimestamp;
private Builder(String id, String hostname, Flavor flavor, State state, NodeType type) {
this.id = id;
@@ -825,6 +863,11 @@ public final class Node implements Nodelike {
return this;
}
+ public Builder wireguardKeyTimestamp(Instant wireguardKeyTimestamp) {
+ this.wireguardKeyTimestamp = wireguardKeyTimestamp;
+ return this;
+ }
+
public Node build() {
return new Node(id, Optional.empty(), Optional.ofNullable(ipConfig).orElse(IP.Config.EMPTY), hostname, Optional.ofNullable(parentHostname),
flavor, Optional.ofNullable(status).orElseGet(Status::initial), state, Optional.ofNullable(allocation),
@@ -832,7 +875,7 @@ public final class Node implements Nodelike {
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(wireguardPubKey), Optional.ofNullable(wireguardKeyTimestamp));
}
}
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 43a135a7e04..3c3868bfeb8 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
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.provision.persistence;
import ai.vespa.http.DomainName;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
-import com.google.common.util.concurrent.UncheckedExecutionException;
import com.yahoo.collections.Pair;
import com.yahoo.component.Version;
import com.yahoo.concurrent.UncheckedTimeoutException;
@@ -42,7 +41,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
-import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Level;
@@ -222,8 +220,9 @@ 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.exclusiveToClusterType(),
- node.switchHostname(), node.trustedCertificates(), node.cloudAccount(), node.wireguardPubKey());
+ node.exclusiveToApplicationId(), node.hostTTL(), node.hostEmptyAt(),
+ node.exclusiveToClusterType(), node.switchHostname(), node.trustedCertificates(),
+ node.cloudAccount(), node.wireguardPubKey(), node.wireguardKeyTimestamp());
curatorTransaction.add(createOrSet(nodePath(newNode), nodeSerializer.toJson(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 df39a0230b6..358c94895d1 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
@@ -100,6 +100,7 @@ public class NodeSerializer {
private static final String trustedCertificatesKey = "trustedCertificates";
private static final String cloudAccountKey = "cloudAccount";
private static final String wireguardPubKeyKey = "wireguardPubkey";
+ private static final String wireguardKeyTimestampKey = "wireguardKeyTimestamp";
// Node resource fields
private static final String flavorKey = "flavor";
@@ -190,6 +191,7 @@ public class NodeSerializer {
object.setString(cloudAccountKey, node.cloudAccount().value());
}
node.wireguardPubKey().ifPresent(pubKey -> object.setString(wireguardPubKeyKey, pubKey.value()));
+ node.wireguardKeyTimestamp().ifPresent(timestamp -> object.setLong(wireguardKeyTimestampKey, timestamp.toEpochMilli()));
}
private void toSlime(Flavor flavor, Cursor object) {
@@ -284,7 +286,8 @@ public class NodeSerializer {
SlimeUtils.optionalString(object.field(switchHostnameKey)),
trustedCertificatesFromSlime(object),
SlimeUtils.optionalString(object.field(cloudAccountKey)).map(CloudAccount::from).orElse(CloudAccount.empty),
- SlimeUtils.optionalString(object.field(wireguardPubKeyKey)).map(WireguardKey::from));
+ SlimeUtils.optionalString(object.field(wireguardPubKeyKey)).map(WireguardKey::from),
+ SlimeUtils.optionalInstant(object.field(wireguardKeyTimestampKey)));
}
private Status statusFromSlime(Inspector object) {
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 d88cade0022..45d6f13513a 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
@@ -109,7 +109,8 @@ public class NodePatcher {
"reports",
"trustStore",
"vespaVersion",
- "wireguardPubkey"));
+ "wireguardPubkey",
+ "wireguardKeyTimestamp"));
if (!disallowedFields.isEmpty()) {
throw new IllegalArgumentException("Patching fields not supported: " + disallowedFields);
}
@@ -274,6 +275,8 @@ public class NodePatcher {
return nodeWithTrustStore(node, value);
case "wireguardPubkey":
return node.withWireguardPubkey(SlimeUtils.optionalString(value).map(WireguardKey::new).orElse(null));
+ case "wireguardKeyTimestamp":
+ return node.withWireguardKeyTimestamp(SlimeUtils.optionalInstant(value).orElse(null));
default :
throw new IllegalArgumentException("Could not apply field '" + name + "' on a node: No such modifiable field");
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
index 5221fa8875b..e3960cc5db5 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
@@ -193,6 +193,7 @@ class NodesResponse extends SlimeJsonResponse {
object.setString("cloudAccount", node.cloudAccount().value());
}
node.wireguardPubKey().ifPresent(key -> object.setString("wireguardPubkey", key.value()));
+ node.wireguardKeyTimestamp().ifPresent(timestamp -> object.setLong("wireguardKeyTimestamp", timestamp.toEpochMilli()));
}
private Version resolveVersionFlag(StringFlag flag, Node node, Allocation allocation) {
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 90cf37aa876..086639d7276 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
@@ -163,6 +163,7 @@ public class MockNodeRepository extends NodeRepository {
nodes.add(Node.create("dockerhost2", ipConfig(101, 1, 3), "dockerhost2.yahoo.com",
flavors.getFlavorOrThrow("large"), NodeType.host)
.wireguardPubKey(WireguardKey.from("000011112222333344445555666677778888999900c="))
+ .wireguardKeyTimestamp(Instant.ofEpochMilli(123L))
.cloudAccount(tenantAccount).build());
nodes.add(Node.create("dockerhost3", ipConfig(102, 1, 3), "dockerhost3.yahoo.com",
flavors.getFlavorOrThrow("large"), NodeType.host).cloudAccount(defaultCloudAccount).build());
@@ -176,7 +177,9 @@ public class MockNodeRepository extends NodeRepository {
// Config servers
nodes.add(Node.create("cfg1", ipConfig(201), "cfg1.yahoo.com", flavors.getFlavorOrThrow("default"), NodeType.config)
.cloudAccount(defaultCloudAccount)
- .wireguardPubKey(WireguardKey.from("lololololololololololololololololololololoo=")).build());
+ .wireguardPubKey(WireguardKey.from("lololololololololololololololololololololoo="))
+ .wireguardKeyTimestamp(Instant.ofEpochMilli(456L))
+ .build());
nodes.add(Node.create("cfg2", ipConfig(202), "cfg2.yahoo.com", flavors.getFlavorOrThrow("default"), NodeType.config)
.cloudAccount(defaultCloudAccount)
.build());
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 7769523f3d6..5f0a70ab420 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
@@ -399,13 +399,13 @@ public class NodesV2ApiTest {
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not set field 'wireguardPubkey': Wireguard key must match '^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$', but got: 'not a wg key'\"}");
assertResponse(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com",
- Utf8.toBytes("{\"wireguardPubkey\": \"lololololololololololololololololololololoo=\"}"), Request.Method.PATCH),
+ Utf8.toBytes("{\"wireguardPubkey\": \"lololololololololololololololololololololoo=\",\"wireguardKeyTimestamp\" : 789}"), Request.Method.PATCH),
"{\"message\":\"Updated host4.yahoo.com\"}");
assertFile(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com"), "node4-wg.json");
}
- @Test
+ @Test
public void post_controller_node() throws Exception {
String data = "[{\"hostname\":\"controller1.yahoo.com\", \"id\":\"fake-controller1.yahoo.com\"," +
createIpAddresses("127.0.0.1") +
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json
index fc72d9e08cc..928e91861a2 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/cfg1.json
@@ -119,5 +119,6 @@
],
"additionalIpAddresses": [],
"cloudAccount": "aws:111222333444",
- "wireguardPubkey":"lololololololololololololololololololololoo="
+ "wireguardPubkey":"lololololololololololololololololololololoo=",
+ "wireguardKeyTimestamp": 456
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json
index c1c1ba0cae8..72b5483d849 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json
@@ -117,5 +117,6 @@
"ipAddresses": ["127.0.101.1", "::101:1"],
"additionalIpAddresses": ["::101:2", "::101:3", "::101:4"],
"cloudAccount": "aws:777888999000",
- "wireguardPubkey": "000011112222333344445555666677778888999900c="
+ "wireguardPubkey": "000011112222333344445555666677778888999900c=",
+ "wireguardKeyTimestamp": 123
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json
index a385a319bfe..7911c0eff3d 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node4-wg.json
@@ -118,5 +118,6 @@
"ipAddresses": ["127.0.4.1", "::4:1"],
"additionalIpAddresses": [],
"cloudAccount": "aws:111222333444",
- "wireguardPubkey": "lololololololololololololololololololololoo="
+ "wireguardPubkey": "lololololololololololololololololololololoo=",
+ "wireguardKeyTimestamp": 789
}