diff options
Diffstat (limited to 'node-repository/src')
25 files changed, 355 insertions, 199 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 24159b88a9b..d5e891a33c7 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 @@ -10,7 +10,7 @@ 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.config.provision.WireguardKey; +import com.yahoo.config.provision.WireguardKeyWithTimestamp; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.provision.lb.LoadBalancers; import com.yahoo.vespa.hosted.provision.node.Agent; @@ -64,8 +64,7 @@ public final class Node implements Nodelike { private final CloudAccount cloudAccount; /** Only set for configservers and exclave nodes */ - private final Optional<WireguardKey> wireguardPubKey; - private final Optional<Instant> wireguardKeyTimestamp; + private final Optional<WireguardKeyWithTimestamp> wireguardPubKey; /** Record of the last event of each type happening to this node */ private final History history; @@ -96,8 +95,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, - Optional<Instant> wireguardKeyTimestamp) { + 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"); this.hostname = requireNonEmptyString(hostname, "A node must have a hostname"); @@ -120,7 +119,6 @@ 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"); @@ -264,15 +262,10 @@ public final class Node implements Nodelike { } /** Returns the wireguard public key of this node. Only relevant for enclave nodes. */ - public Optional<WireguardKey> wireguardPubKey() { + public Optional<WireguardKeyWithTimestamp> wireguardPubKey() { 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. */ @@ -367,16 +360,14 @@ 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, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey); } /** Returns a node with the flavor assigned to the given value */ @@ -385,40 +376,35 @@ 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, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey); } /** Returns a copy of this with a history record saying it was detected to be down at this instant */ @@ -460,24 +446,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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey); } public Node withReservedTo(TenantName tenant) { @@ -485,73 +468,59 @@ 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, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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), - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - Optional.ofNullable(exclusiveTo), switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + Optional.ofNullable(exclusiveTo), switchHostname, trustStoreItems, cloudAccount, wireguardPubKey); } - public Node withWireguardPubkey(WireguardKey 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, - 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)); + exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, + Optional.ofNullable(wireguardPubkey)); } /** 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, - wireguardKeyTimestamp); + exclusiveToClusterType, Optional.ofNullable(switchHostname), trustStoreItems, cloudAccount, + wireguardPubKey); } /** Returns a copy of this node with switch hostname unset */ @@ -604,22 +573,19 @@ 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, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + 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, - exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey, - wireguardKeyTimestamp); + exclusiveToClusterType, switchHostname, trustStoreItems, cloudAccount, wireguardPubKey); } private static Optional<String> requireNonEmptyString(Optional<String> value, String message) { @@ -767,8 +733,7 @@ public final class Node implements Nodelike { private History history; private List<TrustStoreItem> trustStoreItems; private CloudAccount cloudAccount = CloudAccount.empty; - private WireguardKey wireguardPubKey; - private Instant wireguardKeyTimestamp; + private WireguardKeyWithTimestamp wireguardPubKey; private Builder(String id, String hostname, Flavor flavor, State state, NodeType type) { this.id = id; @@ -858,16 +823,11 @@ public final class Node implements Nodelike { return this; } - public Builder wireguardPubKey(WireguardKey wireguardPubKey) { + public Builder wireguardKey(WireguardKeyWithTimestamp wireguardPubKey) { this.wireguardPubKey = wireguardPubKey; 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), @@ -875,7 +835,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(wireguardKeyTimestamp)); + Optional.ofNullable(wireguardPubKey)); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java index 8069c9c089b..286ec2451f8 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java @@ -160,7 +160,7 @@ public class AllocatableResources { for (Node node : nodes) { sum = sum.add(nodeRepository.resourcesCalculator().realResourcesOf(node, nodeRepository).justNumbers()); } - return nodes.get(0).allocation().get().requestedResources().justNonNumbers() + return nodes.get(0).allocation().get().requestedResources() .withVcpu(sum.vcpu() / nodes.size()) .withMemoryGb(sum.memoryGb() / nodes.size()) .withDiskGb(sum.diskGb() / nodes.size()) diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java index e228d31384c..f42d1ce9bd3 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerInstance.java @@ -21,7 +21,8 @@ import java.util.Set; public class LoadBalancerInstance { private final Optional<DomainName> hostname; - private final Optional<String> ipAddress; + private final Optional<String> ip4Address; + private final Optional<String> ip6Address; private final Optional<DnsZone> dnsZone; private final Set<Integer> ports; private final Set<String> networks; @@ -30,11 +31,12 @@ public class LoadBalancerInstance { private final List<PrivateServiceId> serviceIds; private final CloudAccount cloudAccount; - public LoadBalancerInstance(Optional<DomainName> hostname, Optional<String> ipAddress, + public LoadBalancerInstance(Optional<DomainName> hostname, Optional<String> ip4Address, Optional<String> ip6Address, Optional<DnsZone> dnsZone, Set<Integer> ports, Set<String> networks, Set<Real> reals, ZoneEndpoint settings, List<PrivateServiceId> serviceIds, CloudAccount cloudAccount) { this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null"); - this.ipAddress = Objects.requireNonNull(ipAddress, "ip must be non-null"); + this.ip4Address = Objects.requireNonNull(ip4Address, "ip4Address must be non-null"); + this.ip6Address = Objects.requireNonNull(ip6Address, "ip6Address must be non-null"); this.dnsZone = Objects.requireNonNull(dnsZone, "dnsZone must be non-null"); this.ports = ImmutableSortedSet.copyOf(requirePorts(ports)); this.networks = ImmutableSortedSet.copyOf(Objects.requireNonNull(networks, "networks must be non-null")); @@ -43,9 +45,9 @@ public class LoadBalancerInstance { this.serviceIds = List.copyOf(Objects.requireNonNull(serviceIds, "private service id must be non-null")); this.cloudAccount = Objects.requireNonNull(cloudAccount, "cloudAccount must be non-null"); - if (hostname.isEmpty() == ipAddress.isEmpty()) { - throw new IllegalArgumentException("Exactly 1 of hostname=%s and ipAddress=%s must be set".formatted( - hostname.map(DomainName::value).orElse("<empty>"), ipAddress.orElse("<empty>"))); + if (hostname.isEmpty() == ip4Address.isEmpty()) { + throw new IllegalArgumentException("Exactly 1 of hostname=%s and ip4Address=%s must be set".formatted( + hostname.map(DomainName::value).orElse("<empty>"), ip4Address.orElse("<empty>"))); } } @@ -54,9 +56,14 @@ public class LoadBalancerInstance { return hostname; } - /** IP address of this (public) load balancer */ - public Optional<String> ipAddress() { - return ipAddress; + /** IPv4 address of this (public) load balancer */ + public Optional<String> ip4Address() { + return ip4Address; + } + + /** IPv6 address of this (public) load balancer */ + public Optional<String> ip6Address() { + return ip6Address; } /** ID of the DNS zone associated with this */ @@ -114,7 +121,7 @@ public class LoadBalancerInstance { public LoadBalancerInstance with(Set<Real> reals, ZoneEndpoint settings, Optional<PrivateServiceId> serviceId) { List<PrivateServiceId> ids = new ArrayList<>(serviceIds); serviceId.filter(id -> ! ids.contains(id)).ifPresent(ids::add); - return new LoadBalancerInstance(hostname, ipAddress, dnsZone, ports, networks, + return new LoadBalancerInstance(hostname, ip4Address, ip6Address, dnsZone, ports, networks, reals, settings, ids, cloudAccount); } @@ -123,7 +130,7 @@ public class LoadBalancerInstance { public LoadBalancerInstance withServiceIds(List<PrivateServiceId> serviceIds) { List<PrivateServiceId> ids = new ArrayList<>(serviceIds); for (PrivateServiceId id : this.serviceIds) if ( ! ids.contains(id)) ids.add(id); - return new LoadBalancerInstance(hostname, ipAddress, dnsZone, ports, networks, reals, settings, ids, cloudAccount); + return new LoadBalancerInstance(hostname, ip4Address, ip6Address, dnsZone, ports, networks, reals, settings, ids, cloudAccount); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java index a79766a577d..c79ccc2aece 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java @@ -57,6 +57,7 @@ public class LoadBalancerServiceMock implements LoadBalancerService { var instance = new LoadBalancerInstance( Optional.of(DomainName.of("lb-" + spec.application().toShortString() + "-" + spec.cluster().value())), Optional.empty(), + Optional.empty(), Optional.of(new DnsZone("zone-id-1")), Collections.singleton(4443), ImmutableSet.of("10.2.3.0/24", "10.4.5.0/24"), diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java index e49d1b302cf..073662b39fe 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java @@ -45,6 +45,7 @@ public class SharedLoadBalancerService implements LoadBalancerService { return new LoadBalancerInstance(Optional.of(DomainName.of(vipHostname)), Optional.empty(), Optional.empty(), + Optional.empty(), Set.of(4443), Set.of(), spec.reals(), 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 3c3868bfeb8..e4e08e5a15c 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 @@ -47,6 +47,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import static com.yahoo.stream.CustomCollectors.toLinkedMap; import static java.util.stream.Collectors.collectingAndThen; @@ -222,7 +223,7 @@ public class CuratorDb { 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.wireguardKeyTimestamp()); + node.cloudAccount(), node.wireguardPubKey()); curatorTransaction.add(createOrSet(nodePath(newNode), nodeSerializer.toJson(newNode))); writtenNodes.add(newNode); } @@ -456,7 +457,12 @@ public class CuratorDb { transaction.onCommitted(() -> { for (var lb : loadBalancers) { if (lb.state() == fromState) continue; - Optional<String> target = lb.instance().flatMap(instance -> instance.hostname().map(DomainName::value).or(instance::ipAddress)); + Optional<String> target = lb.instance() + .flatMap(instance -> instance.hostname() + .map(DomainName::value) + .or(() -> Optional.of(Stream.concat(instance.ip4Address().stream(), + instance.ip6Address().stream()) + .collect(Collectors.joining(","))))); if (fromState == null) { log.log(Level.INFO, () -> "Creating " + lb.id() + target.map(t -> " (" + t + ")").orElse("") + " in " + lb.state()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java index b85d96c6b54..d329676f842 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java @@ -45,6 +45,7 @@ public class LoadBalancerSerializer { private static final String idField = "id"; private static final String hostnameField = "hostname"; private static final String lbIpAddressField = "ipAddress"; + private static final String lbIp6AddressField = "ip6Address"; private static final String stateField = "state"; private static final String changedAtField = "changedAt"; private static final String dnsZoneField = "dnsZone"; @@ -69,7 +70,8 @@ public class LoadBalancerSerializer { root.setString(idField, loadBalancer.id().serializedForm()); loadBalancer.instance().flatMap(LoadBalancerInstance::hostname).ifPresent(hostname -> root.setString(hostnameField, hostname.value())); - loadBalancer.instance().flatMap(LoadBalancerInstance::ipAddress).ifPresent(ip -> root.setString(lbIpAddressField, ip)); + loadBalancer.instance().flatMap(LoadBalancerInstance::ip4Address).ifPresent(ip -> root.setString(lbIpAddressField, ip)); + loadBalancer.instance().flatMap(LoadBalancerInstance::ip6Address).ifPresent(ip -> root.setString(lbIp6AddressField, ip)); root.setString(stateField, asString(loadBalancer.state())); root.setLong(changedAtField, loadBalancer.changedAt().toEpochMilli()); loadBalancer.instance().flatMap(LoadBalancerInstance::dnsZone).ifPresent(dnsZone -> root.setString(dnsZoneField, dnsZone.id())); @@ -123,7 +125,8 @@ public class LoadBalancerSerializer { object.field(networksField).traverse((ArrayTraverser) (i, network) -> networks.add(network.asString())); Optional<DomainName> hostname = optionalString(object.field(hostnameField), Function.identity()).filter(s -> !s.isEmpty()).map(DomainName::of); - Optional<String> ipAddress = optionalString(object.field(lbIpAddressField), Function.identity()).filter(s -> !s.isEmpty()); + Optional<String> ip4Address = optionalString(object.field(lbIpAddressField), Function.identity()).filter(s -> !s.isEmpty()); + Optional<String> ip6Address = optionalString(object.field(lbIp6AddressField), Function.identity()).filter(s -> !s.isEmpty()); Optional<DnsZone> dnsZone = optionalString(object.field(dnsZoneField), DnsZone::new); ZoneEndpoint settings = zoneEndpoint(object.field(settingsField)); Optional<PrivateServiceId> serviceId = optionalString(object.field(serviceIdField), PrivateServiceId::of); @@ -131,9 +134,9 @@ public class LoadBalancerSerializer { object.field(serviceIdsField).traverse((ArrayTraverser) (__, serviceIdObject) -> serviceIds.add(PrivateServiceId.of(serviceIdObject.asString()))); if (serviceIds.isEmpty()) serviceId.ifPresent(serviceIds::add); // TODO: remove after winter vacation '23 CloudAccount cloudAccount = optionalString(object.field(cloudAccountField), CloudAccount::from).orElse(CloudAccount.empty); - Optional<LoadBalancerInstance> instance = hostname.isEmpty() && ipAddress.isEmpty() + Optional<LoadBalancerInstance> instance = hostname.isEmpty() && ip4Address.isEmpty() && ip6Address.isEmpty() ? Optional.empty() - : Optional.of(new LoadBalancerInstance(hostname, ipAddress, dnsZone, ports, networks, reals, settings, serviceIds, cloudAccount)); + : Optional.of(new LoadBalancerInstance(hostname, ip4Address, ip6Address, dnsZone, ports, networks, reals, settings, serviceIds, cloudAccount)); return new LoadBalancer(LoadBalancerId.fromSerializedForm(object.field(idField).asString()), instance, 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 870e678a250..73531d650d5 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 @@ -16,6 +16,7 @@ import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.WireguardKey; +import com.yahoo.config.provision.WireguardKeyWithTimestamp; import com.yahoo.config.provision.host.FlavorOverrides; import com.yahoo.config.provision.serialization.NetworkPortsSerializer; import com.yahoo.slime.ArrayTraverser; @@ -188,8 +189,10 @@ public class NodeSerializer { if (!node.cloudAccount().isUnspecified()) { object.setString(cloudAccountKey, node.cloudAccount().value()); } - node.wireguardPubKey().ifPresent(pubKey -> object.setString(wireguardPubKeyKey, pubKey.value())); - node.wireguardKeyTimestamp().ifPresent(timestamp -> object.setLong(wireguardKeyTimestampKey, timestamp.toEpochMilli())); + node.wireguardPubKey().ifPresent(pubKey -> { + object.setString(wireguardPubKeyKey, pubKey.key().value()); + object.setLong(wireguardKeyTimestampKey, pubKey.timestamp().toEpochMilli()); + }); } private void toSlime(Flavor flavor, Cursor object) { @@ -284,8 +287,7 @@ 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.optionalInstant(object.field(wireguardKeyTimestampKey))); + wireguardKeyWithTimestampFromSlime(object.field(wireguardPubKeyKey), object.field(wireguardKeyTimestampKey))); } private Status statusFromSlime(Inspector object) { @@ -397,6 +399,13 @@ public class NodeSerializer { .toList(); } + private Optional<WireguardKeyWithTimestamp> wireguardKeyWithTimestampFromSlime(Inspector keyObject, Inspector timestampObject) { + if ( ! keyObject.valid()) return Optional.empty(); + return SlimeUtils.optionalString(keyObject).map( + key -> new WireguardKeyWithTimestamp(WireguardKey.from(key), + SlimeUtils.optionalInstant(timestampObject).orElse(null))); + } + // ----------------- 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/restapi/LoadBalancersResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java index 09f947503f6..20aa7d8181e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java @@ -57,7 +57,8 @@ public class LoadBalancersResponse extends SlimeJsonResponse { lbObject.setString("instance", lb.id().application().instance().value()); lbObject.setString("cluster", lb.id().cluster().value()); lb.instance().flatMap(LoadBalancerInstance::hostname).ifPresent(hostname -> lbObject.setString("hostname", hostname.value())); - lb.instance().flatMap(LoadBalancerInstance::ipAddress).ifPresent(ipAddress -> lbObject.setString("ipAddress", ipAddress)); + lb.instance().flatMap(LoadBalancerInstance::ip4Address).ifPresent(ip -> lbObject.setString("ipAddress", ip)); + lb.instance().flatMap(LoadBalancerInstance::ip6Address).ifPresent(ip -> lbObject.setString("ip6Address", ip)); lb.instance().flatMap(LoadBalancerInstance::dnsZone).ifPresent(dnsZone -> lbObject.setString("dnsZone", dnsZone.id())); Cursor networkArray = lbObject.setArray("networks"); 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 9f1ab3dc3d5..cad034e01aa 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 @@ -11,6 +11,7 @@ import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.WireguardKey; +import com.yahoo.config.provision.WireguardKeyWithTimestamp; import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; import com.yahoo.slime.ObjectTraverser; @@ -108,7 +109,8 @@ public class NodePatcher { "reports", "trustStore", "vespaVersion", - "wireguardPubkey")); + "wireguardPubkey", // TODO wg: remove when all nodes use new key+timestamp format + "wireguard")); if (!disallowedFields.isEmpty()) { throw new IllegalArgumentException("Patching fields not supported: " + disallowedFields); } @@ -271,9 +273,13 @@ public class NodePatcher { return value.type() == Type.NIX ? node.withoutSwitchHostname() : node.withSwitchHostname(value.asString()); case "trustStore": return nodeWithTrustStore(node, value); - case "wireguardPubkey": - return node.withWireguardPubkey(SlimeUtils.optionalString(value).map(WireguardKey::new).orElse(null)) - .withWireguardKeyTimestamp(clock.instant()); + case "wireguard": + // This is where we set the key timestamp. + var key = SlimeUtils.optionalString(value.field("key")).map(WireguardKey::new).orElse(null); + return node.withWireguardPubkey(new WireguardKeyWithTimestamp(key, clock.instant())); + case "wireguardPubkey": // TODO wg: remove when all nodes use new key+timestamp format + var oldKey = SlimeUtils.optionalString(value).map(WireguardKey::new).orElse(null); + return node.withWireguardPubkey(new WireguardKeyWithTimestamp(oldKey, clock.instant())); 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 a8f526544d7..05bb0a27d69 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 @@ -8,6 +8,7 @@ import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeResources; +import com.yahoo.config.provision.WireguardKeyWithTimestamp; import com.yahoo.config.provision.serialization.NetworkPortsSerializer; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.restapi.SlimeJsonResponse; @@ -192,8 +193,13 @@ class NodesResponse extends SlimeJsonResponse { if (!node.cloudAccount().isUnspecified()) { object.setString("cloudAccount", node.cloudAccount().value()); } - node.wireguardPubKey().ifPresent(key -> object.setString("wireguardPubkey", key.value())); - node.wireguardKeyTimestamp().ifPresent(timestamp -> object.setLong("wireguardKeyTimestamp", timestamp.toEpochMilli())); + node.wireguardPubKey().ifPresent(key -> toSlime(key, object.setObject("wireguard"))); + + // TODO wg: remove when all nodes have upgraded to new key+timestamp format + node.wireguardPubKey().ifPresent(key -> { + object.setString("wireguardPubkey", key.key().value()); + object.setLong("wireguardKeyTimestamp", key.timestamp().toEpochMilli()); + }); } private Version resolveVersionFlag(StringFlag flag, Node node, Allocation allocation) { @@ -237,6 +243,11 @@ class NodesResponse extends SlimeJsonResponse { } } + static void toSlime(WireguardKeyWithTimestamp keyWithTimestamp, Cursor object) { + object.setString("key", keyWithTimestamp.key().value()); + object.setLong("timestamp", keyWithTimestamp.timestamp().toEpochMilli()); + } + private Optional<DockerImage> currentContainerImage(Node node) { if (node.status().containerImage().isPresent()) { return node.status().containerImage(); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java index 16e85dfa48a..e29c4f1b87a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/WireguardResponse.java @@ -1,7 +1,7 @@ package com.yahoo.vespa.hosted.provision.restapi; import com.yahoo.config.provision.NodeType; -import com.yahoo.config.provision.WireguardKey; +import com.yahoo.config.provision.WireguardKeyWithTimestamp; import com.yahoo.restapi.SlimeJsonResponse; import com.yahoo.slime.Cursor; import com.yahoo.vespa.hosted.provision.Node; @@ -10,9 +10,9 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.IP; import java.net.InetAddress; -import java.time.Instant; import java.util.List; -import java.util.Optional; + +import static com.yahoo.vespa.hosted.provision.restapi.NodesResponse.toSlime; /** * A response containing the wireguard peer config for each configserver that has a public key. @@ -36,17 +36,20 @@ public class WireguardResponse extends SlimeJsonResponse { .toList(); if (ipAddresses.isEmpty()) continue; - addConfigserver(cfgArray.addObject(), cfg.hostname(), cfg.wireguardPubKey().get(), - cfg.wireguardKeyTimestamp(), ipAddresses); + addConfigserver(cfgArray.addObject(), cfg.hostname(), cfg.wireguardPubKey().get(), ipAddresses); } } - private void addConfigserver(Cursor cfgEntry, String hostname, WireguardKey key, Optional<Instant> keyTimestamp, + private void addConfigserver(Cursor cfgEntry, String hostname, WireguardKeyWithTimestamp keyWithTimestamp, List<String> ipAddresses) { cfgEntry.setString("hostname", hostname); - cfgEntry.setString("wireguardPubkey", key.value()); - cfgEntry.setLong("wireguardKeyTimestamp", keyTimestamp.orElse(Instant.EPOCH).toEpochMilli()); + + // TODO wg: remove when all nodes are using new key+timestamp format + cfgEntry.setString("wireguardPubkey", keyWithTimestamp.key().value()); + cfgEntry.setLong("wireguardKeyTimestamp", keyWithTimestamp.timestamp().toEpochMilli()); + NodesResponse.ipAddressesToSlime(ipAddresses, cfgEntry.setArray("ipAddresses")); + toSlime(keyWithTimestamp, cfgEntry.setObject("wireguard")); } private static boolean isPublicIp(String ipAddress) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java index 5cdb08d6fc6..a72c2fb0b9c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java @@ -79,7 +79,7 @@ public class MockHostProvisioner implements HostProvisioner { if (hostFlavor == null) hostFlavor = flavors.stream() .filter(f -> request.sharing() == HostSharing.exclusive ? compatible(f, request.resources()) - : f.resources().satisfies(request.resources())) + : satisfies(f, request.resources())) .filter(f -> realHostResourcesWithinLimits.test(f.resources())) .findFirst() .orElseThrow(() -> new NodeAllocationException("No host flavor matches " + request.resources(), true)); @@ -223,6 +223,10 @@ public class MockHostProvisioner implements HostProvisioner { return flavor.resources().compatibleWith(resourcesToVerify); } + public boolean satisfies(Flavor flavor, NodeResources resources) { + return flavor.resources().satisfies(resources); + } + private List<HostName> createHostnames(NodeType hostType, Flavor flavor, int hostIndex) { long numAddresses = Math.max(2, Math.round(flavor.resources().bandwidthGbps())); return IntStream.range(1, (int) numAddresses) 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 72225763381..2fb549acc11 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 @@ -21,6 +21,7 @@ import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.WireguardKey; +import com.yahoo.config.provision.WireguardKeyWithTimestamp; import com.yahoo.config.provision.Zone; import com.yahoo.config.provision.ZoneEndpoint; import com.yahoo.config.provision.ZoneEndpoint.AccessType; @@ -161,8 +162,8 @@ public class MockNodeRepository extends NodeRepository { // Emulate host in tenant account 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)) + .wireguardKey(new WireguardKeyWithTimestamp(WireguardKey.from("000011112222333344445555666677778888999900c="), + 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,8 +177,8 @@ 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=")) - .wireguardKeyTimestamp(Instant.ofEpochMilli(456L)) + .wireguardKey(new WireguardKeyWithTimestamp(WireguardKey.from("lololololololololololololololololololololoo="), + Instant.ofEpochMilli(456L))) .build()); nodes.add(Node.create("cfg2", ipConfig(202), "cfg2.yahoo.com", flavors.getFlavorOrThrow("default"), NodeType.config) .cloudAccount(defaultCloudAccount) diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java index f64e50310bb..3f66b2c94d8 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/RealDataScenarioTest.java @@ -8,6 +8,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationTransaction; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.Cloud; +import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.CloudName; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; @@ -25,6 +26,7 @@ import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.hosted.provision.maintenance.SwitchRebalancer; import com.yahoo.vespa.hosted.provision.node.Agent; +import com.yahoo.vespa.hosted.provision.persistence.ApplicationSerializer; import com.yahoo.vespa.hosted.provision.persistence.DnsNameResolver; import com.yahoo.vespa.hosted.provision.persistence.NameResolver; import com.yahoo.vespa.hosted.provision.persistence.NodeSerializer; @@ -45,9 +47,10 @@ import java.nio.file.Path; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; +import java.util.Map; +import java.util.function.BiConsumer; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.stream.IntStream; @@ -75,8 +78,8 @@ public class RealDataScenarioTest { @Ignore @Test - public void test() { - ProvisioningTester tester = tester(SystemName.Public, CloudName.AWS, Environment.prod, parseFlavors(Path.of("/tmp/node-flavors.xml"))); + public void test() throws Exception { + ProvisioningTester tester = tester(SystemName.Public, CloudName.AWS, Environment.prod, CloudAccount.empty, parseFlavors(Path.of("/tmp/node-flavors.xml"))); initFromZk(tester.nodeRepository(), Path.of("/tmp/snapshot")); ApplicationId app = ApplicationId.from("tenant", "app", "default"); @@ -132,41 +135,34 @@ public class RealDataScenarioTest { } } - private static void initFromZk(NodeRepository nodeRepository, Path pathToZkSnapshot) { + private static void initFromZk(NodeRepository nodeRepository, Path pathToZkSnapshot) throws Exception { NodeSerializer nodeSerializer = new NodeSerializer(nodeRepository.flavors()); - AtomicBoolean nodeNext = new AtomicBoolean(false); - Pattern zkNodePathPattern = Pattern.compile(".?/provision/v1/nodes/[a-z0-9.-]+\\.(com|cloud).?"); - Consumer<String> consumer = input -> { - if (nodeNext.get()) { - String json = input.substring(input.indexOf("{\""), input.lastIndexOf('}') + 1); - Node node = nodeSerializer.fromJson(json.getBytes(UTF_8)); - nodeRepository.database().addNodesInState(new LockedNodeList(List.of(node), () -> { }), node.state(), Agent.system); - nodeNext.set(false); - } else { - if (!zkNodePathPattern.matcher(input).matches()) return; - if (nodeNext.getAndSet(true)) - throw new IllegalStateException("Expected to find node JSON, but found another node path: " + input); + Map<Pattern, BiConsumer<byte[], NestedTransaction>> jsonConsumerByPathPattern = Map.of( + Pattern.compile(".?/provision/v1/nodes/[a-z0-9.-]+\\.(com|cloud).?"), (json, transaction) -> { + Node node = nodeSerializer.fromJson(json); + nodeRepository.database().addNodesInState(new LockedNodeList(List.of(node), () -> { }), node.state(), Agent.system, transaction); + }, + Pattern.compile(".?/provision/v1/applications/[a-z0-9:-]+.?"), (json, transaction) -> + nodeRepository.database().writeApplication(ApplicationSerializer.fromJson(json), transaction)); + + try (StringsIterator iterator = new StringsIterator(pathToZkSnapshot); NestedTransaction transaction = new NestedTransaction()) { + while (iterator.hasNext()) { + String s1 = iterator.next(); + if (!iterator.hasNext()) break; + for (var entry : jsonConsumerByPathPattern.entrySet()) { + if (!entry.getKey().matcher(s1).matches()) continue; + String s2 = iterator.next(); + byte[] json = s2.substring(s2.indexOf("{\""), s2.lastIndexOf('}') + 1).getBytes(UTF_8); + entry.getValue().accept(json, transaction); + break; + } } - }; - - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(Files.newInputStream(pathToZkSnapshot), UTF_8))) { - StringBuilder sb = new StringBuilder(1000); - for (int r; (r = reader.read()) != -1; ) { - if (r < 0x20 || r >= 0x7F) { - if (sb.length() > 0) { - consumer.accept(sb.toString()); - sb.setLength(0); - } - } else sb.append((char) r); - } - } catch (IOException e) { - throw new UncheckedIOException(e); + transaction.commit(); } } - private static ProvisioningTester tester(SystemName systemName, CloudName cloudName, Environment environment, List<Flavor> flavors) { - Cloud cloud = Cloud.builder().name(cloudName).dynamicProvisioning(cloudName != CloudName.YAHOO).build(); + private static ProvisioningTester tester(SystemName systemName, CloudName cloudName, Environment environment, CloudAccount cloudAccount, List<Flavor> flavors) { + Cloud cloud = Cloud.builder().name(cloudName).dynamicProvisioning(cloudName != CloudName.YAHOO).account(cloudAccount).build(); NameResolver nameResolver = cloudName == CloudName.YAHOO ? new DnsNameResolver() : new MockNameResolver().mockAnyLookup(); ProvisioningTester.Builder builder = new ProvisioningTester.Builder() .zone(new Zone(cloud, systemName, environment, RegionName.defaultName())) @@ -179,4 +175,42 @@ public class RealDataScenarioTest { return builder.build(); } + /** Extracts sequences longer than 5 printable characters from a binary file, similar to `strings` command */ + private static class StringsIterator implements Iterator<String>, AutoCloseable { + private final BufferedReader reader; + private final StringBuilder sb = new StringBuilder(1000); + private StringsIterator(Path path) throws IOException { + this.reader = new BufferedReader(new InputStreamReader(Files.newInputStream(path), UTF_8)); + } + + @Override + public boolean hasNext() { + if (!sb.isEmpty()) return true; + try { + for (int r; (r = reader.read()) != -1; ) { + if (r < 0x20 || r >= 0x7F) { + if (sb.isEmpty()) continue; // Still haven't encountered any real data + if (sb.length() > 5) break; // We (probably) found some real data + sb.setLength(0); // Probably some random binary data that happened to be a printable character, reset + } else sb.append((char) r); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return !sb.isEmpty(); + } + + @Override + public String next() { + String next = sb.toString(); + sb.setLength(0); + return next; + } + + @Override + public void close() throws Exception { + reader.close(); + } + } + } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java index 52d4c85bcaf..aa8ff1245d7 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java @@ -32,6 +32,25 @@ import static org.junit.Assert.assertTrue; public class AutoscalingTest { @Test + public void test_autoscaling_with_gpu() { + var resources = new NodeResources(8, 32, 225, 0.1, fast, StorageType.local, NodeResources.Architecture.x86_64, new NodeResources.GpuResources(1, 16)); + var min = new ClusterResources( 8, 1, resources); + var now = new ClusterResources(12, 1, resources); + var max = new ClusterResources(12, 1, resources); + var fixture = DynamicProvisioningTester.fixture() + .awsProdSetup(true) + .clusterType(ClusterSpec.Type.container) + .initialResources(Optional.of(now)) + .capacity(Capacity.from(min, max)) + .build(); + fixture.tester.clock().advance(Duration.ofDays(2)); + fixture.loader().applyLoad(new Load(0.8f, 0.17, 0.12), 1, true, true, 100); + var result = fixture.autoscale(); + assertTrue(result.resources().isEmpty()); + assertEquals(Autoscaling.Status.insufficient, result.status()); + } + + @Test public void test_autoscaling_nodes_only() { var resources = new NodeResources(16, 32, 200, 0.1); var min = new ClusterResources( 8, 1, resources); @@ -112,7 +131,7 @@ public class AutoscalingTest { fixture.loader().applyLoad(new Load(0.1, 0.1, 0.1), 3); fixture.loader().applyLoad(new Load(1.0, 1.0, 1.0), 1); fixture.tester().assertResources("Scaling up since resource usage is too high", - 8, 1, 5.3, 17.5, 75.4, + 8, 1, 5.3, 17.0, 75.1, fixture.autoscale()); } @@ -173,7 +192,7 @@ public class AutoscalingTest { fixture.setScalingDuration(Duration.ofHours(12)); // Fixture sets last completion to be 1 day into the past fixture.loader().applyLoad(new Load(1.0, 0.1, 1.0), 10); fixture.tester().assertResources("Scaling up (only) since resource usage is too high", - 5, 1, 11.7, 15.4, 132.0, + 5, 1, 11.7, 14.9, 131.5, fixture.autoscale()); } @@ -185,7 +204,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(Duration.ofDays(2)); fixture.loader().applyLoad(new Load(1.0, 0.1, 1.0), 10); fixture.tester().assertResources("Scaling cpu and disk up and memory down", - 5, 1, 11.7, 4.0, 132.0, + 5, 1, 11.7, 4.0, 131.5, fixture.autoscale()); } @@ -208,7 +227,7 @@ public class AutoscalingTest { fixture.loader().applyCpuLoad(0.70, 1); fixture.loader().applyCpuLoad(0.01, 100); fixture.tester().assertResources("Scaling up since peak resource usage is too high", - 5, 1, 7.1, 12.3, 50.7, + 5, 1, 7.1, 11.9, 50.5, fixture.autoscale()); } @@ -355,7 +374,7 @@ public class AutoscalingTest { fixture.tester().clock().advance(Duration.ofDays(2)); fixture.loader().applyLoad(new Load(0.05f, 0.05f, 0.05f), 120); fixture.tester().assertResources("Scaling down to limit since resource usage is low", - 4, 1, 1.8, 7.4, 23.5, + 4, 1, 1.8, 7.4, 23.4, fixture.autoscale()); } @@ -459,7 +478,7 @@ public class AutoscalingTest { fixture.tester().clock().advance(Duration.ofDays(2)); fixture.loader().applyCpuLoad(1.0, 120); fixture.tester().assertResources("Suggesting above capacity limit", - 5, 1, 10.2, 12.3, 50.7, + 5, 1, 10.2, 11.9, 50.5, fixture.tester().suggest(fixture.applicationId, fixture.clusterSpec.id(), min, min)); } @@ -663,7 +682,7 @@ public class AutoscalingTest { fixture.tester().clock().advance(Duration.ofHours(12 * 3 + 1)); fixture.loader().applyCpuLoad(0.02, 5); fixture.tester().assertResources("Scaling down since enough time has passed", - 5, 1, 1.0, 12.3, 50.7, + 5, 1, 1.0, 11.9, 50.5, fixture.autoscale()); } @@ -707,7 +726,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.25, 200); fixture.tester().assertResources("Scale up since we assume we need 2x cpu for growth when no scaling time data", - 5, 1, 2.6, 12.3, 50.7, + 5, 1, 2.6, 11.9, 50.5, fixture.autoscale()); fixture.setScalingDuration(Duration.ofHours(8)); @@ -716,7 +735,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.20, 200); fixture.tester().assertResources("Scale down since observed growth is slower than scaling time", - 5, 1, 1.6, 12.3, 50.7, + 5, 1, 1.6, 11.9, 50.5, fixture.autoscale()); fixture.setScalingDuration(Duration.ofHours(8)); @@ -727,7 +746,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.25, 200); fixture.tester().assertResources("Scale up since observed growth is faster than scaling time", - 5, 1, 2.4, 12.3, 50.7, + 5, 1, 2.4, 11.9, 50.5, fixture.autoscale()); } @@ -744,7 +763,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.7, 200); fixture.tester().assertResources("Scale up slightly since observed growth is faster than scaling time, but we are not confident", - 5, 1, 2.2, 12.3, 50.7, + 5, 1, 2.2, 11.9, 50.5, fixture.autoscale()); } @@ -763,7 +782,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); fixture.tester.assertResources("Query and write load is equal -> scale up somewhat", - 5, 1, 2.9, 12.3, 50.7, + 5, 1, 2.9, 11.9, 50.5, fixture.autoscale()); fixture.tester().clock().advance(Duration.ofDays(2)); @@ -772,7 +791,7 @@ public class AutoscalingTest { fixture.loader().addCpuMeasurements(0.4, 200); // TODO: Ackhually, we scale up less here - why? fixture.tester().assertResources("Query load is 4x write load -> scale up more", - 5, 1, 2.2, 12.3, 50.7, + 5, 1, 2.2, 11.9, 50.5, fixture.autoscale()); fixture.tester().clock().advance(Duration.ofDays(2)); @@ -780,7 +799,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); fixture.tester().assertResources("Write load is 10x query load -> scale down", - 5, 1, 1.3, 12.3, 50.7, + 5, 1, 1.3, 11.9, 50.5, fixture.autoscale()); fixture.tester().clock().advance(Duration.ofDays(2)); @@ -788,7 +807,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); fixture.tester().assertResources("Query only -> larger", - 5, 1, 3.5, 12.3, 50.7, + 5, 1, 3.5, 11.9, 50.5, fixture.autoscale()); fixture.tester().clock().advance(Duration.ofDays(2)); @@ -796,7 +815,7 @@ public class AutoscalingTest { fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); fixture.tester().assertResources("Write only -> smallest possible", - 5, 1, 1.0, 12.3, 50.7, + 5, 1, 1.0, 11.9, 50.5, fixture.autoscale()); } @@ -825,7 +844,7 @@ public class AutoscalingTest { fixture.tester().clock().advance(Duration.ofDays(2)); fixture.loader().applyLoad(new Load(1.0, 1.0, 1.0), 200); fixture.tester().assertResources("Scale only to a single node and group since this is dev", - 1, 1, 0.1, 23.6, 105.6, + 1, 1, 0.1, 22.9, 105.2, fixture.autoscale()); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java index be7bc3c44a8..f2b80adc513 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingUsingBcpGroupInfoTest.java @@ -32,7 +32,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(100, 1.1, 0.3)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 3.4, 7.4, 29.0, + 8, 1, 3.4, 7.2, 28.8, fixture.autoscale()); // Higher query rate @@ -40,7 +40,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(200, 1.1, 0.3)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 6.8, 7.4, 29.0, + 8, 1, 6.8, 7.2, 28.8, fixture.autoscale()); // Higher headroom @@ -48,7 +48,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(100, 1.3, 0.3)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 4.0, 7.4, 29.0, + 8, 1, 4.0, 7.2, 28.8, fixture.autoscale()); // Higher per query cost @@ -56,7 +56,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(100, 1.1, 0.45)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 5.1, 7.4, 29.0, + 8, 1, 5.1, 7.2, 28.8, fixture.autoscale()); // Bcp elsewhere is 0 - use local only @@ -64,7 +64,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(0, 1.1, 0.45)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling using local info", - 8, 1, 1, 7.4, 29.0, + 8, 1, 1, 7.2, 28.8, fixture.autoscale()); } @@ -85,7 +85,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(100, 1.1, 0.3)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 3, 3, 11.7, 43.2, 190.0, + 3, 3, 11.7, 41.8, 189.3, fixture.autoscale()); // Higher query rate @@ -93,7 +93,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(200, 1.1, 0.3)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 3, 3, 23.1, 43.2, 190.0, + 3, 3, 23.1, 41.8, 189.3, fixture.autoscale()); // Higher headroom @@ -101,7 +101,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(100, 1.3, 0.3)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 3, 3, 13.8, 43.2, 190.0, + 3, 3, 13.8, 41.8, 189.3, fixture.autoscale()); // Higher per query cost @@ -109,7 +109,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(100, 1.1, 0.45)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 3, 3, 17.4, 43.2, 190.0, + 3, 3, 17.4, 41.8, 189.3, fixture.autoscale()); } @@ -186,7 +186,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.store(new BcpGroupInfo(200, 1.3, 0.45)); fixture.loader().addCpuMeasurements(0.7f, 10); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 11.9, 7.4, 29.0, + 8, 1, 11.9, 7.2, 28.8, fixture.autoscale()); // Some local traffic @@ -196,7 +196,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.tester().clock().advance(duration1.negated()); fixture.loader().addQueryRateMeasurements(10, __ -> 10.0); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 6.8, 7.4, 29.0, + 8, 1, 6.8, 7.2, 28.8, fixture.autoscale()); // Enough local traffic to get half the votes @@ -206,7 +206,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.tester().clock().advance(duration2.negated()); fixture.loader().addQueryRateMeasurements(10, __ -> 50.0); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 3.0, 7.4, 29.0, + 8, 1, 3.0, 7.2, 28.8, fixture.autoscale()); // Mostly local @@ -216,7 +216,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.tester().clock().advance(duration3.negated()); fixture.loader().addQueryRateMeasurements(10, __ -> 90.0); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 2.2, 7.4, 29.0, + 8, 1, 2.2, 7.2, 28.8, fixture.autoscale()); // Local only @@ -226,7 +226,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.tester().clock().advance(duration4.negated()); fixture.loader().addQueryRateMeasurements(10, __ -> 100.0); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 2.1, 7.4, 29.0, + 8, 1, 2.1, 7.2, 28.8, fixture.autoscale()); // No group info, should be the same as the above @@ -236,7 +236,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.tester().clock().advance(duration5.negated()); fixture.loader().addQueryRateMeasurements(10, __ -> 100.0); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 2.1, 7.4, 29.0, + 8, 1, 2.1, 7.2, 28.8, fixture.autoscale()); // 40 query rate, no group info (for reference to the below) @@ -246,7 +246,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.tester().clock().advance(duration6.negated()); fixture.loader().addQueryRateMeasurements(10, __ -> 40.0); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 1.5, 7.4, 29.0, + 8, 1, 1.5, 7.2, 28.8, fixture.autoscale()); // Local query rate is too low but global is even lower so disregard it, giving the same as above @@ -256,7 +256,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.tester().clock().advance(duration7.negated()); fixture.loader().addQueryRateMeasurements(10, __ -> 40.0); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 1.5, 7.4, 29.0, + 8, 1, 1.5, 7.2, 28.8, fixture.autoscale()); // Local query rate is too low to be fully confident, and so is global but as it is slightly larger, incorporate it slightly @@ -266,7 +266,7 @@ public class AutoscalingUsingBcpGroupInfoTest { fixture.tester().clock().advance(duration8.negated()); fixture.loader().addQueryRateMeasurements(10, __ -> 40.0); fixture.tester().assertResources("Scaling up cpu using bcp group cpu info", - 8, 1, 1.8, 7.4, 29.0, + 8, 1, 1.8, 7.2, 28.8, fixture.autoscale()); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java index df7c468f035..6225982af42 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsNodeTypes.java @@ -11,11 +11,12 @@ import java.util.Optional; import static com.yahoo.config.provision.NodeResources.Architecture.arm64; import static com.yahoo.config.provision.NodeResources.Architecture.x86_64; import static com.yahoo.config.provision.NodeResources.DiskSpeed.fast; +import static com.yahoo.config.provision.NodeResources.GpuResources; import static com.yahoo.config.provision.NodeResources.StorageType.local; import static com.yahoo.config.provision.NodeResources.StorageType.remote; /** - * Returns the information about all AWS node types supported on Vespa Cloud as of 2022-10-31. + * Returns the information about all AWS node types supported on Vespa Cloud as of 2023-09-28. * * @author bratseth */ @@ -45,13 +46,34 @@ public class AwsNodeTypes { new VespaFlavor("m5d_16xlarge", 64.0, 64.0, 256.0, 246.0, 2400.0, 10.0, fast, local, x86_64), new VespaFlavor("m5_24xlarge", 96.0, 96.0, 384.0, 370.0, 16384.0, 10.0, fast, remote, x86_64), new VespaFlavor("m5d_24xlarge", 96.0, 96.0, 384.0, 370.0, 3600.0, 10.0, fast, local, x86_64), + new VespaFlavor("m6i_large", 2.0, 2.0, 8.0, 7.3, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("m6id_large", 2.0, 2.0, 8.0, 7.3, 118.0, 10.0, fast, local, x86_64), + new VespaFlavor("m6i_xlarge", 4.0, 4.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("m6id_xlarge", 4.0, 4.0, 16.0, 15.1, 237.0, 10.0, fast, local, x86_64), + new VespaFlavor("m6i_2xlarge", 8.0, 8.0, 32.0, 30.5, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("m6id_2xlarge", 8.0, 8.0, 32.0, 30.5, 474.0, 10.0, fast, local, x86_64), + new VespaFlavor("m6i_4xlarge", 16.0, 16.0, 64.0, 61.3, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("m6id_4xlarge", 16.0, 16.0, 64.0, 61.3, 950.0, 10.0, fast, local, x86_64), + new VespaFlavor("m6i_8xlarge", 32.0, 32.0, 128.0, 123.0, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("m6id_8xlarge", 32.0, 32.0, 128.0, 123.0, 1900.0, 10.0, fast, local, x86_64), + new VespaFlavor("m6i_12xlarge", 48.0, 48.0, 192.0, 185.0, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("m6id_12xlarge", 48.0, 48.0, 192.0, 185.0, 2850.0, 10.0, fast, local, x86_64), + new VespaFlavor("m6i_16xlarge", 64.0, 64.0, 256.0, 246.0, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("m6id_16xlarge", 64.0, 64.0, 256.0, 246.0, 3800.0, 10.0, fast, local, x86_64), new VespaFlavor("m6g_large", 2.0, 2.0, 8.0, 7.3, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("m6gd_large", 2.0, 2.0, 8.0, 7.3, 118.0, 10.0, fast, local, arm64), new VespaFlavor("m6g_xlarge", 4.0, 4.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("m6gd_xlarge", 4.0, 4.0, 16.0, 15.1, 237.0, 10.0, fast, local, arm64), new VespaFlavor("m6g_2xlarge", 8.0, 8.0, 32.0, 30.5, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("m6gd_2xlarge", 8.0, 8.0, 32.0, 30.5, 474.0, 10.0, fast, local, arm64), new VespaFlavor("m6g_4xlarge", 16.0, 16.0, 64.0, 61.3, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("m6gd_4xlarge", 16.0, 16.0, 64.0, 61.3, 950.0, 10.0, fast, local, arm64), new VespaFlavor("m6g_8xlarge", 32.0, 32.0, 128.0, 123.0, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("m6gd_8xlarge", 32.0, 32.0, 128.0, 123.0, 1900.0, 10.0, fast, local, arm64), new VespaFlavor("m6g_12xlarge", 48.0, 48.0, 192.0, 185.0, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("m6gd_12xlarge", 48.0, 48.0, 192.0, 185.0, 2850.0, 10.0, fast, local, arm64), new VespaFlavor("m6g_16xlarge", 64.0, 64.0, 256.0, 246.0, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("m6gd_16xlarge", 64.0, 64.0, 256.0, 246.0, 3800.0, 10.0, fast, local, arm64), new VespaFlavor("c5_large", 2.0, 2.0, 4.0, 3.5, 16384.0, 10.0, fast, remote, x86_64), new VespaFlavor("c5d_large", 2.0, 2.0, 4.0, 3.5, 50.0, 10.0, fast, local, x86_64), new VespaFlavor("c5_xlarge", 4.0, 4.0, 8.0, 7.3, 16384.0, 10.0, fast, remote, x86_64), @@ -68,10 +90,44 @@ public class AwsNodeTypes { new VespaFlavor("c5d_18xlarge", 72.0, 72.0, 144.0, 137.0, 1800.0, 10.0, fast, local, x86_64), new VespaFlavor("c5_24xlarge", 96.0, 96.0, 192.0, 185.0, 16384.0, 10.0, fast, remote, x86_64), new VespaFlavor("c5d_24xlarge", 96.0, 96.0, 192.0, 185.0, 3600.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6i_large", 2.0, 2.0, 4.0, 3.5, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("c6id_large", 2.0, 2.0, 4.0, 3.5, 118.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6i_xlarge", 4.0, 4.0, 8.0, 7.4, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("c6id_xlarge", 4.0, 4.0, 8.0, 7.4, 237.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6i_2xlarge", 8.0, 8.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("c6id_2xlarge", 8.0, 8.0, 16.0, 15.1, 474.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6i_4xlarge", 16.0, 16.0, 32.0, 30.6, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("c6id_4xlarge", 16.0, 16.0, 32.0, 30.6, 950.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6i_8xlarge", 32.0, 32.0, 64.0, 61.7, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("c6id_8xlarge", 32.0, 32.0, 64.0, 61.7, 1900.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6i_12xlarge", 48.0, 48.0, 96.0, 92.6, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("c6id_12xlarge", 48.0, 48.0, 96.0, 92.6, 2850.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6i_16xlarge", 64.0, 64.0, 128.0, 123.0, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("c6id_16xlarge", 64.0, 64.0, 128.0, 123.0, 3800.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6i_24xlarge", 96.0, 96.0, 192.0, 185.6, 16384.0, 10.0, fast, remote, x86_64), + new VespaFlavor("c6id_24xlarge", 96.0, 96.0, 192.0, 185.6, 5700.0, 10.0, fast, local, x86_64), new VespaFlavor("c5ad_8xlarge", 32.0, 32.0, 64.0, 62.0, 1200.0, 10.0, fast, local, x86_64), + new VespaFlavor("c6g_large", 2.0, 2.0, 4.0, 3.35, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c6gd_large", 2.0, 2.0, 4.0, 3.35, 118.0, 10.0, fast, local, arm64), + new VespaFlavor("c6g_xlarge", 4.0, 4.0, 8.0, 7.3, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c6gd_xlarge", 4.0, 4.0, 8.0, 7.3, 237.0, 10.0, fast, local, arm64), + new VespaFlavor("c6g_2xlarge", 8.0, 8.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c6gd_2xlarge", 8.0, 8.0, 16.0, 15.1, 475.0, 10.0, fast, local, arm64), + new VespaFlavor("c6g_4xlarge", 16.0, 16.0, 32.0, 30.8, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c6gd_4xlarge", 16.0, 16.0, 32.0, 30.8, 950.0, 10.0, fast, local, arm64), + new VespaFlavor("c6g_8xlarge", 32.0, 32.0, 64.0, 62.3, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c6gd_8xlarge", 32.0, 32.0, 64.0, 62.3, 1900.0, 10.0, fast, local, arm64), + new VespaFlavor("c6g_12xlarge", 48.0, 48.0, 96.0, 93.0, 16384.0, 10.0, fast, remote, arm64), new VespaFlavor("c6gd_12xlarge", 48.0, 48.0, 96.0, 93.0, 2850.0, 10.0, fast, local, arm64), + new VespaFlavor("c6g_16xlarge", 64.0, 64.0, 128.0, 125.0, 16384.0, 10.0, fast, remote, arm64), new VespaFlavor("c6gd_16xlarge", 64.0, 64.0, 128.0, 125.0, 3800.0, 10.0, fast, local, arm64), - new VespaFlavor("c6id_16xlarge", 64.0, 64.0, 128.0, 123.0, 3800.0, 10.0, fast, local, x86_64), + new VespaFlavor("m7g_8xlarge", 32.0, 32.0, 128.0, 123.0, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c7g_large", 2.0, 2.0, 4.0, 3.35, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c7g_xlarge", 4.0, 4.0, 8.0, 7.2, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c7g_2xlarge", 8.0, 8.0, 16.0, 15.1, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c7g_4xlarge", 16.0, 16.0, 32.0, 30.8, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c7g_8xlarge", 32.0, 32.0, 64.0, 62.3, 16384.0, 10.0, fast, remote, arm64), + new VespaFlavor("c7g_12xlarge", 48.0, 48.0, 96.0, 92.6, 16384.0, 10.0, fast, remote, arm64), new VespaFlavor("c7g_16xlarge", 64.0, 64.0, 128.0, 125.0, 16384.0, 10.0, fast, remote, arm64), new VespaFlavor("r5_large", 2.0, 2.0, 16.0, 15.2, 16384.0, 10.0, fast, remote, x86_64), new VespaFlavor("r5d_large", 2.0, 2.0, 16.0, 15.2, 75.0, 10.0, fast, local, x86_64), @@ -89,14 +145,7 @@ public class AwsNodeTypes { new VespaFlavor("r5d_16xlarge", 64.0, 64.0, 512.0, 498.0, 2400.0, 10.0, fast, local, x86_64), new VespaFlavor("r5_24xlarge", 96.0, 96.0, 768.0, 748.0, 16384.0, 10.0, fast, remote, x86_64), new VespaFlavor("r5d_24xlarge", 96.0, 96.0, 768.0, 748.0, 3600.0, 10.0, fast, local, x86_64), - new VespaFlavor("x1_16xlarge", 64.0, 64.0, 976.0, 946.0, 1920.0, 10.0, fast, local, x86_64), - new VespaFlavor("x1_32xlarge", 128.0, 128.0, 1952.0, 1893.0, 3840.0, 10.0, fast, local, x86_64), - new VespaFlavor("x1e_xlarge", 4.0, 4.0, 122.0, 118.0, 120.0, 10.0, fast, local, x86_64), - new VespaFlavor("x1e_2xlarge", 8.0, 8.0, 244.0, 237.0, 240.0, 10.0, fast, local, x86_64), - new VespaFlavor("x1e_4xlarge", 16.0, 16.0, 488.0, 474.0, 480.0, 10.0, fast, local, x86_64), - new VespaFlavor("x1e_8xlarge", 32.0, 32.0, 976.0, 946.0, 960.0, 10.0, fast, local, x86_64), - new VespaFlavor("x1e_16xlarge", 64.0, 64.0, 1952.0, 1893.0, 1920.0, 10.0, fast, local, x86_64), - new VespaFlavor("x1e_32xlarge", 128.0, 128.0, 3904.0, 3786.0, 3840.0, 10.0, fast, local, x86_64), + new VespaFlavor("r6id_xlarge", 4.0, 4.0, 32.0, 30.8, 237.0, 10.0, fast, local, x86_64), new VespaFlavor("z1d_6xlarge", 24.0, 24.0, 192.0, 185.0, 900.0, 10.0, fast, local, x86_64), new VespaFlavor("i4i_large", 2.0, 2.0, 16.0, 15.1, 468.0, 10.0, fast, local, x86_64), new VespaFlavor("i4i_xlarge", 4.0, 4.0, 32.0, 30.5, 937.0, 10.0, fast, local, x86_64), @@ -104,7 +153,9 @@ public class AwsNodeTypes { new VespaFlavor("i4i_4xlarge", 16.0, 16.0, 128.0, 123.0, 3750.0, 10.0, fast, local, x86_64), new VespaFlavor("i4i_8xlarge", 32.0, 32.0, 256.0, 246.0, 7500.0, 10.0, fast, local, x86_64), new VespaFlavor("i4i_16xlarge", 64.0, 64.0, 512.0, 498.0, 15000.0, 10.0, fast, local, x86_64), - new VespaFlavor("i4i_32xlarge", 128.0, 128.0, 1024.0, 996.0, 30000.0, 10.0, fast, local, x86_64)); + new VespaFlavor("i4i_32xlarge", 128.0, 128.0, 1024.0, 996.0, 30000.0, 10.0, fast, local, x86_64), + new VespaFlavor("g4dn_xlarge", 4.0, 4.0, 16.0, 15.1, 125.0, 10.0, fast, local, x86_64, new GpuResources(1, 16.0)), + new VespaFlavor("g4dn_2xlarge", 8.0, 8.0, 32.0, 30.5, 225.0, 10.0, fast, local, x86_64, new GpuResources(1, 16.0))); public static List<VespaFlavor> asVespaFlavors() { return sorted(hostFlavors); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java index c42b61988e9..2e0a92a38af 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/VespaFlavor.java @@ -23,9 +23,23 @@ public class VespaFlavor { NodeResources.DiskSpeed diskSpeed, NodeResources.StorageType storageType, NodeResources.Architecture architecture) { + this(name, advertisedVcpu, realVcpu, advertisedMemoryGb, realMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture, NodeResources.GpuResources.zero()); + } + + public VespaFlavor(String name, + double advertisedVcpu, + double realVcpu, + double advertisedMemoryGb, + double realMemoryGb, + double diskGb, + double bandwidthGbps, + NodeResources.DiskSpeed diskSpeed, + NodeResources.StorageType storageType, + NodeResources.Architecture architecture, + NodeResources.GpuResources gpuResources) { this.name = name; - this.realResources = new NodeResources(realVcpu, realMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture); - this.advertisedResources = new NodeResources(advertisedVcpu, advertisedMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture); + this.realResources = new NodeResources(realVcpu, realMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture, gpuResources); + this.advertisedResources = new NodeResources(advertisedVcpu, advertisedMemoryGb, diskGb, bandwidthGbps, diskSpeed, storageType, architecture, gpuResources); } public String name() { return name; } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java index 6dc681ae5c8..b5257e23d9e 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java @@ -40,6 +40,7 @@ public class LoadBalancerSerializerTest { Optional.of(new LoadBalancerInstance( Optional.of(DomainName.of("lb-host")), Optional.empty(), + Optional.empty(), Optional.of(new DnsZone("zone-id-1")), Set.of(4080, 4443), Set.of("10.2.3.4/24"), @@ -73,6 +74,7 @@ public class LoadBalancerSerializerTest { Optional.of(new LoadBalancerInstance( Optional.empty(), Optional.of("1.2.3.4"), + Optional.of("fd00::1"), Optional.of(new DnsZone("zone-id-1")), Set.of(4443), Set.of("10.2.3.4/24", "12.3.2.1/30"), @@ -86,6 +88,8 @@ public class LoadBalancerSerializerTest { var serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer)); assertEquals(loadBalancer.id(), serialized.id()); assertEquals(loadBalancer.instance().get().hostname(), serialized.instance().get().hostname()); + assertEquals(loadBalancer.instance().get().ip4Address(), serialized.instance().get().ip4Address()); + assertEquals(loadBalancer.instance().get().ip6Address(), serialized.instance().get().ip6Address()); assertEquals(loadBalancer.instance().get().dnsZone(), serialized.instance().get().dnsZone()); assertEquals(loadBalancer.instance().get().ports(), serialized.instance().get().ports()); assertEquals(loadBalancer.instance().get().networks(), serialized.instance().get().networks()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java index ebac6071a14..1ef89f2d53d 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicProvisioningTester.java @@ -289,6 +289,11 @@ public class DynamicProvisioningTester { return flavorResources.compatibleWith(resources); } + @Override + public boolean satisfies(Flavor flavor, NodeResources resources) { + return hostResourcesCalculator.advertisedResourcesOf(flavor).satisfies(resources); + } + } } 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 928e91861a2..54a0e7e9757 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,6 +119,10 @@ ], "additionalIpAddresses": [], "cloudAccount": "aws:111222333444", - "wireguardPubkey":"lololololololololololololololololololololoo=", + "wireguard": { + "key": "lololololololololololololololololololololoo=", + "timestamp": 456 + }, + "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 72b5483d849..d3f1a8082ae 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,6 +117,10 @@ "ipAddresses": ["127.0.101.1", "::101:1"], "additionalIpAddresses": ["::101:2", "::101:3", "::101:4"], "cloudAccount": "aws:777888999000", + "wireguard": { + "key": "000011112222333344445555666677778888999900c=", + "timestamp": 123 + }, "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 d0d6df71fc1..404cf9a9a80 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,6 +118,10 @@ "ipAddresses": ["127.0.4.1", "::4:1"], "additionalIpAddresses": [], "cloudAccount": "aws:111222333444", + "wireguard": { + "key": "lololololololololololololololololololololoo=", + "timestamp": 123 + }, "wireguardPubkey": "lololololololololololololololololololololoo=", "wireguardKeyTimestamp": 123 } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json index 7bee06adc87..8e9af7f680f 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/wireguard.json @@ -4,7 +4,11 @@ "hostname": "cfg1.yahoo.com", "wireguardPubkey": "lololololololololololololololololololololoo=", "wireguardKeyTimestamp":456, - "ipAddresses": ["::201:1"] + "ipAddresses": ["::201:1"], + "wireguard": { + "key": "lololololololololololololololololololololoo=", + "timestamp": 456 + } } ] } |