diff options
author | Valerij Fredriksen <freva@users.noreply.github.com> | 2023-02-24 16:13:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-24 16:13:15 +0100 |
commit | 91d90da73c6847a8d321da8cfd5bb7af3fca945d (patch) | |
tree | e3b5b03c75b68b67779ce7a4258205862cee2a3b /node-repository/src/main | |
parent | 1b4b5ce9ce9aaf6b9716c229360d0253948b0d46 (diff) |
Revert "Lock node when updating IP config"
Diffstat (limited to 'node-repository/src/main')
16 files changed, 291 insertions, 214 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 31283998702..79382a8bf5b 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 @@ -116,11 +116,11 @@ public final class Node implements Nodelike { if (state == State.ready && type.isHost()) { requireNonEmpty(ipConfig.primary(), "A " + type + " must have at least one primary IP address in state " + state); - requireNonEmpty(ipConfig.pool().asSet(), "A " + type + " must have a non-empty IP address pool in state " + state); + requireNonEmpty(ipConfig.pool().ipSet(), "A " + type + " must have a non-empty IP address pool in state " + state); } if (parentHostname.isPresent()) { - if (!ipConfig.pool().asSet().isEmpty()) throw new IllegalArgumentException("A child node cannot have an IP address pool"); + if (!ipConfig.pool().ipSet().isEmpty()) throw new IllegalArgumentException("A child node cannot have an IP address pool"); if (modelName.isPresent()) throw new IllegalArgumentException("A child node cannot have model name set"); if (switchHostname.isPresent()) throw new IllegalArgumentException("A child node cannot have switch hostname set"); if (status.wantToRebuild()) throw new IllegalArgumentException("A child node cannot be rebuilt"); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java index 1cf7bcfa4f2..800cf2150e9 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java @@ -9,6 +9,7 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.node.ClusterId; +import com.yahoo.vespa.hosted.provision.node.IP; import java.util.ArrayList; import java.util.Comparator; @@ -329,22 +330,22 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { /** * Returns the number of unused IP addresses in the pool, assuming any and all unaccounted for hostnames - * in the pool are resolved to exactly 1 IP address (or 2 if dual-stack). + * in the pool are resolved to exactly 1 IP address (or 2 with {@link IP.IpAddresses.Protocol#dualStack}). */ public int eventuallyUnusedIpAddressCount(Node host) { // The count in this method relies on the size of the IP address pool if that's non-empty, // otherwise fall back to the address/hostname pool. - if (host.ipConfig().pool().asSet().isEmpty()) { + if (host.ipConfig().pool().ipSet().isEmpty()) { Set<String> allHostnames = cache().keySet(); - return (int) host.ipConfig().pool().hostnames().stream() - .filter(hostname -> !allHostnames.contains(hostname.value())) + return (int) host.ipConfig().pool().getAddressList().stream() + .filter(address -> !allHostnames.contains(address.hostname())) .count(); } Set<String> allIps = ipCache.updateAndGet(old -> old != null ? old : stream().flatMap(node -> node.ipConfig().primary().stream()) .collect(Collectors.toUnmodifiableSet()) ); - return (int) host.ipConfig().pool().asSet().stream() + return (int) host.ipConfig().pool().ipSet().stream() .filter(address -> !allIps.contains(address)) .count(); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java index c8b736cb25b..603056856e2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java @@ -127,12 +127,12 @@ public class CapacityChecker { for (var host : hosts) { NodeResources hostResources = host.flavor().resources(); int occupiedIps = 0; - Set<String> ipPool = host.ipConfig().pool().asSet(); + Set<String> ipPool = host.ipConfig().pool().ipSet(); for (var child : nodeChildren.get(host)) { hostResources = hostResources.subtract(child.resources().justNumbers()); occupiedIps += child.ipConfig().primary().stream().filter(ipPool::contains).count(); } - availableResources.put(host, new AllocationResources(hostResources, host.ipConfig().pool().asSet().size() - occupiedIps)); + availableResources.put(host, new AllocationResources(hostResources, host.ipConfig().pool().ipSet().size() - occupiedIps)); } return availableResources; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java index 86c5a926900..c606ede05d1 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java @@ -9,14 +9,17 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.provisioning.FatalProvisioningException; -import com.yahoo.vespa.hosted.provision.provisioning.HostIpConfig; import com.yahoo.vespa.hosted.provision.provisioning.HostProvisioner; import com.yahoo.yolean.Exceptions; import javax.naming.NamingException; import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * Resumes provisioning (requests additional IP addresses, updates DNS when IPs are ready) of hosts in state provisioned @@ -38,13 +41,23 @@ public class HostResumeProvisioner extends NodeRepositoryMaintainer { @Override protected double maintain() { NodeList allNodes = nodeRepository().nodes().list(); + Map<String, Set<Node>> nodesByProvisionedParentHostname = + allNodes.nodeType(NodeType.tenant, NodeType.config, NodeType.controller) + .asList() + .stream() + .filter(node -> node.parentHostname().isPresent()) + .collect(Collectors.groupingBy(node -> node.parentHostname().get(), Collectors.toSet())); + NodeList hosts = allNodes.state(Node.State.provisioned).nodeType(NodeType.host, NodeType.confighost, NodeType.controllerhost); int failures = 0; for (Node host : hosts) { - NodeList children = allNodes.childrenOf(host); - try { - HostIpConfig hostIpConfig = hostProvisioner.provision(host, children.asSet()); - setIpConfig(host, children, hostIpConfig); + Set<Node> children = nodesByProvisionedParentHostname.getOrDefault(host.hostname(), Set.of()); + // This doesn't actually require unallocated lock, but that is much easier than simultaneously holding + // the application locks of the host and all it's children. + try (var lock = nodeRepository().nodes().lockUnallocated()) { + List<Node> updatedNodes = hostProvisioner.provision(host, children); + verifyDns(updatedNodes); + nodeRepository().nodes().write(updatedNodes, lock); } catch (IllegalArgumentException | IllegalStateException e) { log.log(Level.INFO, "Could not provision " + host.hostname() + " with " + children.size() + " children, will retry in " + interval() + ": " + Exceptions.toMessageString(e)); @@ -66,21 +79,13 @@ public class HostResumeProvisioner extends NodeRepositoryMaintainer { return asSuccessFactor(hosts.size(), failures); } - private void setIpConfig(Node host, NodeList children, HostIpConfig hostIpConfig) { - if (hostIpConfig.isEmpty()) return; - NodeList nodes = NodeList.of(host).and(children); + /** Verify DNS configuration of given nodes */ + private void verifyDns(List<Node> nodes) { for (var node : nodes) { - verifyDns(node, hostIpConfig.require(node.hostname())); - } - nodeRepository().nodes().setIpConfig(hostIpConfig); - } - - /** Verify DNS configuration of given node */ - private void verifyDns(Node node, IP.Config ipConfig) { - boolean enclave = node.cloudAccount().isEnclave(nodeRepository().zone()); - for (var ipAddress : ipConfig.primary()) { - IP.verifyDns(node.hostname(), ipAddress, nodeRepository().nameResolver(), !enclave); + boolean enclave = node.cloudAccount().isEnclave(nodeRepository().zone()); + for (var ipAddress : node.ipConfig().primary()) { + IP.verifyDns(node.hostname(), ipAddress, nodeRepository().nameResolver(), !enclave); + } } } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Address.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Address.java new file mode 100644 index 00000000000..d7ef2228960 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Address.java @@ -0,0 +1,17 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.node; + +import java.util.Objects; + +/** + * Address info about a container that might run on a host. + * + * @author hakon + */ +public record Address(String hostname) { + public Address { + Objects.requireNonNull(hostname, "hostname cannot be null"); + if (hostname.isEmpty()) + throw new IllegalArgumentException("hostname cannot be empty"); + } +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java index 708d84ba655..b2becc7ecfd 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java @@ -1,9 +1,9 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.node; +import com.google.common.collect.ImmutableSet; import com.google.common.net.InetAddresses; import com.google.common.primitives.UnsignedBytes; -import com.yahoo.config.provision.HostName; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; @@ -31,7 +31,7 @@ import static com.yahoo.config.provision.NodeType.proxyhost; * * @author mpolden */ -public record IP() { +public class IP { /** Comparator for sorting IP addresses by their natural order */ public static final Comparator<InetAddress> NATURAL_ORDER = (ip1, ip2) -> { @@ -59,26 +59,31 @@ public record IP() { }; /** IP configuration of a node */ - public record Config(Set<String> primary, Pool pool) { + public static class Config { public static final Config EMPTY = Config.ofEmptyPool(Set.of()); + private final Set<String> primary; + private final Pool pool; + public static Config ofEmptyPool(Set<String> primary) { - return new Config(primary, Pool.EMPTY); + return new Config(primary, Set.of(), List.of()); } - public static Config of(Set<String> primary, Set<String> ipPool, List<HostName> hostnames) { - return new Config(primary, new Pool(IpAddresses.of(ipPool), hostnames)); + public static Config of(Set<String> primary, Set<String> ipPool, List<Address> addressPool) { + return new Config(primary, ipPool, addressPool); } - public static Config of(Set<String> primary, Set<String> pool) { - return of(primary, pool, List.of()); + /** LEGACY TEST CONSTRUCTOR - use of() variants and/or the with- methods. */ + public Config(Set<String> primary, Set<String> pool) { + this(primary, pool, List.of()); } /** DO NOT USE: Public for NodeSerializer. */ - public Config(Set<String> primary, Pool pool) { - this.primary = Collections.unmodifiableSet(new LinkedHashSet<>(Objects.requireNonNull(primary, "primary must be non-null"))); - this.pool = Objects.requireNonNull(pool, "pool must be non-null"); + public Config(Set<String> primary, Set<String> pool, List<Address> addresses) { + this.primary = ImmutableSet.copyOf(Objects.requireNonNull(primary, "primary must be non-null")); + this.pool = Pool.of(Objects.requireNonNull(pool, "pool must be non-null"), + Objects.requireNonNull(addresses, "addresses must be non-null")); } /** The primary addresses of this. These addresses are used when communicating with the node itself */ @@ -93,12 +98,31 @@ public record IP() { /** Returns a copy of this with pool set to given value */ public Config withPool(Pool pool) { - return new Config(primary, pool); + return new Config(primary, pool.ipSet(), pool.getAddressList()); } /** Returns a copy of this with pool set to given value */ public Config withPrimary(Set<String> primary) { - return new Config(primary, pool); + return new Config(primary, pool.ipSet(), pool.getAddressList()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Config config = (Config) o; + return primary.equals(config.primary) && + pool.equals(config.pool); + } + + @Override + public int hashCode() { + return Objects.hash(primary, pool); + } + + @Override + public String toString() { + return String.format("ip config primary=%s pool=%s", primary, pool.ipSet()); } /** @@ -116,8 +140,8 @@ public record IP() { var addresses = new HashSet<>(node.ipConfig().primary()); var otherAddresses = new HashSet<>(other.ipConfig().primary()); if (node.type().isHost()) { // Addresses of a host can never overlap with any other nodes - addresses.addAll(node.ipConfig().pool().asSet()); - otherAddresses.addAll(other.ipConfig().pool().asSet()); + addresses.addAll(node.ipConfig().pool().ipSet()); + otherAddresses.addAll(other.ipConfig().pool().ipSet()); } otherAddresses.retainAll(addresses); if (!otherAddresses.isEmpty()) @@ -134,12 +158,12 @@ public record IP() { if (node.parentHostname().isPresent() == existingNode.parentHostname().isPresent()) return false; // Not a parent-child node if (node.parentHostname().isEmpty()) return canAssignIpOf(node, existingNode); if (!node.parentHostname().get().equals(existingNode.hostname())) return false; // Wrong host - return switch (node.type()) { - case proxy -> existingNode.type() == proxyhost; - case config -> existingNode.type() == confighost; - case controller -> existingNode.type() == controllerhost; - default -> false; - }; + switch (node.type()) { + case proxy: return existingNode.type() == proxyhost; + case config: return existingNode.type() == confighost; + case controller: return existingNode.type() == controllerhost; + } + return false; } public static Node verify(Node node, LockedNodeList allNodes) { @@ -149,13 +173,25 @@ public record IP() { } /** A list of IP addresses and their protocol */ - record IpAddresses(Set<String> addresses, Protocol protocol) { + public static class IpAddresses { - public IpAddresses(Set<String> addresses, Protocol protocol) { - this.addresses = Collections.unmodifiableSet(new LinkedHashSet<>(Objects.requireNonNull(addresses, "addresses must be non-null"))); + private final Set<String> ipAddresses; + private final Protocol protocol; + + private IpAddresses(Set<String> ipAddresses, Protocol protocol) { + this.ipAddresses = ImmutableSet.copyOf(Objects.requireNonNull(ipAddresses, "addresses must be non-null")); this.protocol = Objects.requireNonNull(protocol, "type must be non-null"); } + public Set<String> asSet() { + return ipAddresses; + } + + /** The protocol of addresses in this */ + public Protocol protocol() { + return protocol; + } + /** Create addresses of the given set */ private static IpAddresses of(Set<String> addresses) { long ipv6AddrCount = addresses.stream().filter(IP::isV6).count(); @@ -180,7 +216,6 @@ public record IP() { } public enum Protocol { - dualStack("dual-stack"), ipv4("IPv4-only"), ipv6("IPv6-only"); @@ -189,8 +224,21 @@ public record IP() { Protocol(String description) { this.description = description; } + public String getDescription() { return description; } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IpAddresses that = (IpAddresses) o; + return ipAddresses.equals(that.ipAddresses) && protocol == that.protocol; + } + + @Override + public int hashCode() { + return Objects.hash(ipAddresses, protocol); + } } /** @@ -198,23 +246,25 @@ public record IP() { * * Addresses in this are available for use by Linux containers. */ - public record Pool(IpAddresses ipAddresses, List<HostName> hostnames) { + public static class Pool { - public static final Pool EMPTY = new Pool(IpAddresses.of(Set.of()), List.of()); + private final IpAddresses ipAddresses; + private final List<Address> addresses; + + /** Creates an empty pool. */ + public static Pool of() { + return of(Set.of(), List.of()); + } /** Create a new pool containing given ipAddresses */ - public static Pool of(Set<String> ipAddresses, List<HostName> hostnames) { + public static Pool of(Set<String> ipAddresses, List<Address> addresses) { IpAddresses ips = IpAddresses.of(ipAddresses); - return new Pool(ips, hostnames); + return new Pool(ips, addresses); } - public Pool(IpAddresses ipAddresses, List<HostName> hostnames) { + private Pool(IpAddresses ipAddresses, List<Address> addresses) { this.ipAddresses = Objects.requireNonNull(ipAddresses, "ipAddresses must be non-null"); - this.hostnames = List.copyOf(Objects.requireNonNull(hostnames, "hostnames must be non-null")); - } - - public Set<String> asSet() { - return ipAddresses.addresses; + this.addresses = Objects.requireNonNull(addresses, "addresses must be non-null"); } /** @@ -224,15 +274,16 @@ public record IP() { * @return an allocation from the pool, if any can be made */ public Optional<Allocation> findAllocation(LockedNodeList nodes, NameResolver resolver, boolean hasPtr) { - if (ipAddresses.addresses.isEmpty()) { + if (ipAddresses.asSet().isEmpty()) { // IP addresses have not yet been resolved and should be done later. - return findUnusedHostnames(nodes).map(Allocation::ofHostname) - .findFirst(); + return findUnusedAddressStream(nodes) + .map(Allocation::ofAddress) + .findFirst(); } if (!hasPtr) { // Without PTR records (reverse IP mapping): Ensure only forward resolving from hostnames. - return findUnusedHostnames(nodes).findFirst().map(address -> Allocation.fromHostname(address, resolver, ipAddresses.protocol)); + return findUnusedAddressStream(nodes).findFirst().map(address -> Allocation.fromAddress(address, resolver, ipAddresses.protocol)); } if (ipAddresses.protocol == IpAddresses.Protocol.ipv4) { @@ -262,34 +313,64 @@ public record IP() { * @param nodes a list of all nodes in the repository */ public Set<String> findUnusedIpAddresses(NodeList nodes) { - Set<String> unusedAddresses = new LinkedHashSet<>(asSet()); - nodes.matching(node -> node.ipConfig().primary().stream().anyMatch(ip -> asSet().contains(ip))) + var unusedAddresses = new LinkedHashSet<>(ipSet()); + nodes.matching(node -> node.ipConfig().primary().stream().anyMatch(ip -> ipSet().contains(ip))) .forEach(node -> unusedAddresses.removeAll(node.ipConfig().primary())); return Collections.unmodifiableSet(unusedAddresses); } - private Stream<HostName> findUnusedHostnames(NodeList nodes) { - Set<String> usedHostnames = nodes.stream().map(Node::hostname).collect(Collectors.toSet()); - return hostnames.stream().filter(hostname -> !usedHostnames.contains(hostname.value())); + private Stream<Address> findUnusedAddressStream(NodeList nodes) { + Set<String> hostnames = nodes.stream().map(Node::hostname).collect(Collectors.toSet()); + return addresses.stream().filter(address -> !hostnames.contains(address.hostname())); + } + + public IpAddresses.Protocol getProtocol() { + return ipAddresses.protocol; + } + + /** Returns the IP addresses in this pool as a set */ + public Set<String> ipSet() { + return ipAddresses.asSet(); + } + + public List<Address> getAddressList() { + return addresses; } public Pool withIpAddresses(Set<String> ipAddresses) { - return Pool.of(ipAddresses, hostnames); + return Pool.of(ipAddresses, addresses); } - public Pool withHostnames(List<HostName> hostnames) { - return Pool.of(ipAddresses.addresses, hostnames); + public Pool withAddresses(List<Address> addresses) { + return Pool.of(ipAddresses.ipAddresses, addresses); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Pool pool = (Pool) o; + return ipAddresses.equals(pool.ipAddresses) && addresses.equals(pool.addresses); + } + + @Override + public int hashCode() { + return Objects.hash(ipAddresses, addresses); } } /** An address allocation from a pool */ - public record Allocation(String hostname, Optional<String> ipv4Address, Optional<String> ipv6Address) { + public static class Allocation { + + private final String hostname; + private final Optional<String> ipv4Address; + private final Optional<String> ipv6Address; - public Allocation { - Objects.requireNonNull(hostname, "hostname must be non-null"); - Objects.requireNonNull(ipv4Address, "ipv4Address must be non-null"); - Objects.requireNonNull(ipv6Address, "ipv6Address must be non-null"); + private Allocation(String hostname, Optional<String> ipv4Address, Optional<String> ipv6Address) { + this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null"); + this.ipv4Address = Objects.requireNonNull(ipv4Address, "ipv4Address must be non-null"); + this.ipv6Address = Objects.requireNonNull(ipv6Address, "ipv6Address must be non-null"); } /** @@ -346,22 +427,22 @@ public record IP() { return new Allocation(hostname4, Optional.of(addresses.get(0)), Optional.empty()); } - private static Allocation fromHostname(HostName hostname, NameResolver resolver, IpAddresses.Protocol protocol) { + private static Allocation fromAddress(Address address, NameResolver resolver, IpAddresses.Protocol protocol) { // Resolve both A and AAAA to verify they match the protocol and to avoid surprises later on. - Optional<String> ipv4Address = resolveOptional(hostname.value(), resolver, RecordType.A); + Optional<String> ipv4Address = resolveOptional(address.hostname(), resolver, RecordType.A); if (protocol != IpAddresses.Protocol.ipv6 && ipv4Address.isEmpty()) - throw new IllegalArgumentException(protocol.description + " hostname " + hostname.value() + " did not resolve to an IPv4 address"); + throw new IllegalArgumentException(protocol.description + " hostname " + address.hostname() + " did not resolve to an IPv4 address"); if (protocol == IpAddresses.Protocol.ipv6 && ipv4Address.isPresent()) - throw new IllegalArgumentException(protocol.description + " hostname " + hostname.value() + " has an IPv4 address: " + ipv4Address.get()); + throw new IllegalArgumentException(protocol.description + " hostname " + address.hostname() + " has an IPv4 address: " + ipv4Address.get()); - Optional<String> ipv6Address = resolveOptional(hostname.value(), resolver, RecordType.AAAA); + Optional<String> ipv6Address = resolveOptional(address.hostname(), resolver, RecordType.AAAA); if (protocol != IpAddresses.Protocol.ipv4 && ipv6Address.isEmpty()) - throw new IllegalArgumentException(protocol.description + " hostname " + hostname.value() + " did not resolve to an IPv6 address"); + throw new IllegalArgumentException(protocol.description + " hostname " + address.hostname() + " did not resolve to an IPv6 address"); if (protocol == IpAddresses.Protocol.ipv4 && ipv6Address.isPresent()) - throw new IllegalArgumentException(protocol.description + " hostname " + hostname.value() + " has an IPv6 address: " + ipv6Address.get()); + throw new IllegalArgumentException(protocol.description + " hostname " + address.hostname() + " has an IPv6 address: " + ipv6Address.get()); - return new Allocation(hostname.value(), ipv4Address, ipv6Address); + return new Allocation(address.hostname(), ipv4Address, ipv6Address); } private static Optional<String> resolveOptional(String hostname, NameResolver resolver, RecordType recordType) { @@ -373,14 +454,37 @@ public record IP() { }; } - private static Allocation ofHostname(HostName hostName) { - return new Allocation(hostName.value(), Optional.empty(), Optional.empty()); + private static Allocation ofAddress(Address address) { + return new Allocation(address.hostname(), Optional.empty(), Optional.empty()); + } + + /** Hostname pointing to the IP addresses in this */ + public String hostname() { + return hostname; + } + + /** IPv4 address of this allocation */ + public Optional<String> ipv4Address() { + return ipv4Address; + } + + /** IPv6 address of this allocation */ + public Optional<String> ipv6Address() { + return ipv6Address; } /** All IP addresses in this */ public Set<String> addresses() { - return Stream.concat(ipv4Address.stream(), ipv6Address.stream()) - .collect(Collectors.toUnmodifiableSet()); + ImmutableSet.Builder<String> builder = ImmutableSet.builder(); + ipv4Address.ifPresent(builder::add); + ipv6Address.ifPresent(builder::add); + return builder.build(); + } + + @Override + public String toString() { + return String.format("Address allocation [hostname=%s, IPv4=%s, IPv6=%s]", + hostname, ipv4Address.orElse("<none>"), ipv6Address.orElse("<none>")); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java index 5690a685345..cf6473c6b1b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java @@ -21,7 +21,6 @@ import com.yahoo.vespa.hosted.provision.applications.Applications; import com.yahoo.vespa.hosted.provision.maintenance.NodeFailer; import com.yahoo.vespa.hosted.provision.node.filter.NodeFilter; import com.yahoo.vespa.hosted.provision.persistence.CuratorDb; -import com.yahoo.vespa.hosted.provision.provisioning.HostIpConfig; import com.yahoo.vespa.orchestrator.HostNameNotFoundException; import com.yahoo.vespa.orchestrator.Orchestrator; @@ -355,15 +354,6 @@ public class Nodes { } } - /** Update IP config for nodes in given config */ - public void setIpConfig(HostIpConfig hostIpConfig) { - Predicate<Node> nodeInConfig = (node) -> hostIpConfig.contains(node.hostname()); - performOn(nodeInConfig, (node, lock) -> { - IP.Config ipConfig = hostIpConfig.require(node.hostname()); - return write(node.with(ipConfig), lock); - }); - } - /** * Parks this node and returns it in its new state. * 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 23ea14da4cc..39cccafb8ef 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 @@ -14,7 +14,6 @@ import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.Flavor; -import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.NodeResources; @@ -29,6 +28,7 @@ import com.yahoo.slime.Inspector; import com.yahoo.slime.Slime; import com.yahoo.slime.SlimeUtils; import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.node.Address; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.Generation; @@ -167,8 +167,8 @@ public class NodeSerializer { object.setString(hostnameKey, node.hostname()); object.setString(stateKey, toString(node.state())); toSlime(node.ipConfig().primary(), object.setArray(ipAddressesKey)); - toSlime(node.ipConfig().pool().asSet(), object.setArray(ipAddressPoolKey)); - toSlime(node.ipConfig().pool().hostnames(), object); + toSlime(node.ipConfig().pool().ipSet(), object.setArray(ipAddressPoolKey)); + toSlime(node.ipConfig().pool().getAddressList(), object); object.setString(idKey, node.id()); node.parentHostname().ifPresent(hostname -> object.setString(parentHostnameKey, hostname)); toSlime(node.flavor(), object); @@ -247,11 +247,11 @@ public class NodeSerializer { ipAddresses.stream().map(IP::parse).sorted(IP.NATURAL_ORDER).map(IP::asString).forEach(array::addString); } - private void toSlime(List<HostName> hostnames, Cursor object) { - if (hostnames.isEmpty()) return; - Cursor containersArray = object.setArray(containersKey); - hostnames.forEach(hostname -> { - containersArray.addObject().setString(containerHostnameKey, hostname.value()); + private void toSlime(List<Address> addresses, Cursor object) { + if (addresses.isEmpty()) return; + Cursor addressCursor = object.setArray(containersKey); + addresses.forEach(address -> { + addressCursor.addObject().setString(containerHostnameKey, address.hostname()); }); } @@ -277,9 +277,9 @@ public class NodeSerializer { private Node nodeFromSlime(Inspector object) { Flavor flavor = flavorFromSlime(object); return new Node(object.field(idKey).asString(), - IP.Config.of(ipAddressesFromSlime(object, ipAddressesKey), - ipAddressesFromSlime(object, ipAddressPoolKey), - hostnamesFromSlime(object)), + new IP.Config(ipAddressesFromSlime(object, ipAddressesKey), + ipAddressesFromSlime(object, ipAddressPoolKey), + addressesFromSlime(object)), object.field(hostnameKey).asString(), SlimeUtils.optionalString(object.field(parentHostnameKey)), flavor, @@ -394,9 +394,9 @@ public class NodeSerializer { return ipAddresses.build(); } - private List<HostName> hostnamesFromSlime(Inspector object) { + private List<Address> addressesFromSlime(Inspector object) { return SlimeUtils.entriesStream(object.field(containersKey)) - .map(elem -> HostName.of(elem.field(containerHostnameKey).asString())) + .map(elem -> new Address(elem.field(containerHostnameKey).asString())) .toList(); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostIpConfig.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostIpConfig.java deleted file mode 100644 index 891251fc892..00000000000 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostIpConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.provision.provisioning; - -import com.yahoo.vespa.hosted.provision.node.IP; - -import java.util.Map; -import java.util.Objects; - -/** - * IP config of a host and its children. - * - * @author mpolden - */ -public record HostIpConfig(Map<String, IP.Config> ipConfigByHostname) { - - public static final HostIpConfig EMPTY = new HostIpConfig(Map.of()); - - public HostIpConfig(Map<String, IP.Config> ipConfigByHostname) { - this.ipConfigByHostname = Map.copyOf(Objects.requireNonNull(ipConfigByHostname)); - } - - public Map<String, IP.Config> asMap() { - return ipConfigByHostname; - } - - public boolean contains(String hostname) { - return ipConfigByHostname.containsKey(hostname); - } - - public IP.Config require(String hostname) { - IP.Config ipConfig = this.ipConfigByHostname.get(hostname); - if (ipConfig == null) throw new IllegalArgumentException("No IP config exists for node '" + hostname + "'"); - return ipConfig; - } - - public boolean isEmpty() { - return this.equals(EMPTY); - } - -} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostProvisioner.java index 3144f42a92c..38fa1abf8e2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostProvisioner.java @@ -10,6 +10,7 @@ import com.yahoo.config.provision.NodeAllocationException; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; import java.util.List; import java.util.Optional; @@ -71,11 +72,12 @@ public interface HostProvisioner { * * @param host the host to provision * @param children list of all the nodes that run on the given host - * @return IP config for the provisioned host and its children + * @return a subset of {@code host} and {@code children} where the values have been modified and should + * be written back to node-repository. * @throws FatalProvisioningException if the provisioning has irrecoverably failed and the input nodes * should be deleted from node-repo. */ - HostIpConfig provision(Node host, Set<Node> children) throws FatalProvisioningException; + List<Node> provision(Node host, Set<Node> children) throws FatalProvisioningException; /** * Deprovisions a given host and resources associated with it and its children (such as DNS entries). diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java index b194730727f..fa07782057b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java @@ -181,11 +181,11 @@ public abstract class NodeCandidate implements Nodelike, Comparable<NodeCandidat if ( ! lessThanHalfTheHost(this) && lessThanHalfTheHost(other)) return 1; } - // Prefer host with the least skew + // Prefer host with least skew int hostPriority = hostPriority(other); if (hostPriority != 0) return hostPriority; - // Prefer node with the cheapest flavor + // Prefer node with cheapest flavor if (this.flavor().cost() < other.flavor().cost()) return -1; if (other.flavor().cost() < this.flavor().cost()) return 1; @@ -199,7 +199,7 @@ public abstract class NodeCandidate implements Nodelike, Comparable<NodeCandidat return Integer.compare(this.allocation().get().membership().index(), other.allocation().get().membership().index()); - // Prefer host with the latest OS version + // Prefer host with latest OS version Version thisHostOsVersion = this.parent.flatMap(host -> host.status().osVersion().current()).orElse(Version.emptyVersion); Version otherHostOsVersion = other.parent.flatMap(host -> host.status().osVersion().current()).orElse(Version.emptyVersion); if (thisHostOsVersion.isAfter(otherHostOsVersion)) return -1; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java index d083d81c196..15a6b6ba523 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java @@ -6,10 +6,10 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; -import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.node.Address; import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.node.OsVersion; import com.yahoo.vespa.hosted.provision.node.Status; @@ -32,38 +32,38 @@ public class ProvisionedHost { private final NodeType hostType; private final Optional<ApplicationId> exclusiveToApplicationId; private final Optional<ClusterSpec.Type> exclusiveToClusterType; - private final List<HostName> nodeHostnames; + private final List<Address> nodeAddresses; private final NodeResources nodeResources; private final Version osVersion; private final CloudAccount cloudAccount; public ProvisionedHost(String id, String hostHostname, Flavor hostFlavor, NodeType hostType, Optional<ApplicationId> exclusiveToApplicationId, Optional<ClusterSpec.Type> exclusiveToClusterType, - List<HostName> nodeHostnames, NodeResources nodeResources, Version osVersion, CloudAccount cloudAccount) { + List<Address> nodeAddresses, NodeResources nodeResources, Version osVersion, CloudAccount cloudAccount) { this.id = Objects.requireNonNull(id, "Host id must be set"); this.hostHostname = Objects.requireNonNull(hostHostname, "Host hostname must be set"); this.hostFlavor = Objects.requireNonNull(hostFlavor, "Host flavor must be set"); this.hostType = Objects.requireNonNull(hostType, "Host type must be set"); this.exclusiveToApplicationId = Objects.requireNonNull(exclusiveToApplicationId, "exclusiveToApplicationId must be set"); this.exclusiveToClusterType = Objects.requireNonNull(exclusiveToClusterType, "exclusiveToClusterType must be set"); - this.nodeHostnames = validateNodeAddresses(nodeHostnames); + this.nodeAddresses = validateNodeAddresses(nodeAddresses); this.nodeResources = Objects.requireNonNull(nodeResources, "Node resources must be set"); this.osVersion = Objects.requireNonNull(osVersion, "OS version must be set"); this.cloudAccount = Objects.requireNonNull(cloudAccount, "Cloud account must be set"); if (!hostType.isHost()) throw new IllegalArgumentException(hostType + " is not a host"); } - private static List<HostName> validateNodeAddresses(List<HostName> nodeHostnames) { - Objects.requireNonNull(nodeHostnames, "Node hostnames must be set"); - if (nodeHostnames.isEmpty()) { - throw new IllegalArgumentException("There must be at least one node hostname"); + private static List<Address> validateNodeAddresses(List<Address> nodeAddresses) { + Objects.requireNonNull(nodeAddresses, "Node addresses must be set"); + if (nodeAddresses.isEmpty()) { + throw new IllegalArgumentException("There must be at least one node address"); } - return nodeHostnames; + return nodeAddresses; } /** Generate {@link Node} instance representing the provisioned physical host */ public Node generateHost() { - Node.Builder builder = Node.create(id, IP.Config.of(Set.of(), Set.of(), nodeHostnames), hostHostname, hostFlavor, + Node.Builder builder = Node.create(id, IP.Config.of(Set.of(), Set.of(), nodeAddresses), hostHostname, hostFlavor, hostType) .status(Status.initial().withOsVersion(OsVersion.EMPTY.withCurrent(Optional.of(osVersion)))) .cloudAccount(cloudAccount); @@ -85,12 +85,12 @@ public class ProvisionedHost { public NodeType hostType() { return hostType; } public Optional<ApplicationId> exclusiveToApplicationId() { return exclusiveToApplicationId; } public Optional<ClusterSpec.Type> exclusiveToClusterType() { return exclusiveToClusterType; } - public List<HostName> nodeHostnames() { return nodeHostnames; } + public List<Address> nodeAddresses() { return nodeAddresses; } public NodeResources nodeResources() { return nodeResources; } public Version osVersion() { return osVersion; } public CloudAccount cloudAccount() { return cloudAccount; } - public String nodeHostname() { return nodeHostnames.get(0).value(); } + public String nodeHostname() { return nodeAddresses.get(0).hostname(); } @Override public boolean equals(Object o) { @@ -103,7 +103,7 @@ public class ProvisionedHost { hostType == that.hostType && exclusiveToApplicationId.equals(that.exclusiveToApplicationId) && exclusiveToClusterType.equals(that.exclusiveToClusterType) && - nodeHostnames.equals(that.nodeHostnames) && + nodeAddresses.equals(that.nodeAddresses) && nodeResources.equals(that.nodeResources) && osVersion.equals(that.osVersion) && cloudAccount.equals(that.cloudAccount); @@ -111,7 +111,7 @@ public class ProvisionedHost { @Override public int hashCode() { - return Objects.hash(id, hostHostname, hostFlavor, hostType, exclusiveToApplicationId, exclusiveToClusterType, nodeHostnames, nodeResources, osVersion, cloudAccount); + return Objects.hash(id, hostHostname, hostFlavor, hostType, exclusiveToApplicationId, exclusiveToClusterType, nodeAddresses, nodeResources, osVersion, cloudAccount); } @Override @@ -123,7 +123,7 @@ public class ProvisionedHost { ", hostType=" + hostType + ", exclusiveToApplicationId=" + exclusiveToApplicationId + ", exclusiveToClusterType=" + exclusiveToClusterType + - ", nodeAddresses=" + nodeHostnames + + ", nodeAddresses=" + nodeAddresses + ", nodeResources=" + nodeResources + ", osVersion=" + osVersion + ", cloudAccount=" + cloudAccount + 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 dfe01f5f1c3..582d5963cfd 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 @@ -6,7 +6,6 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.DockerImage; -import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.TenantName; @@ -20,6 +19,7 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeMutex; import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.node.Address; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.IP; @@ -245,15 +245,12 @@ public class NodePatcher { private Node applyIpconfigField(Node node, String name, Inspector value, LockedNodeList nodes) { switch (name) { - case "ipAddresses" -> { + case "ipAddresses": return IP.Config.verify(node.with(node.ipConfig().withPrimary(asStringSet(value))), nodes); - } - case "additionalIpAddresses" -> { + case "additionalIpAddresses": return IP.Config.verify(node.with(node.ipConfig().withPool(node.ipConfig().pool().withIpAddresses(asStringSet(value)))), nodes); - } - case "additionalHostnames" -> { - return IP.Config.verify(node.with(node.ipConfig().withPool(node.ipConfig().pool().withHostnames(asHostnames(value)))), nodes); - } + case "additionalHostnames": + return IP.Config.verify(node.with(node.ipConfig().withPool(node.ipConfig().pool().withAddresses(asAddressList(value)))), nodes); } throw new IllegalArgumentException("Could not apply field '" + name + "' on a node: No such modifiable field"); } @@ -320,19 +317,20 @@ public class NodePatcher { return strings; } - private List<HostName> asHostnames(Inspector field) { + private List<Address> asAddressList(Inspector field) { if ( ! field.type().equals(Type.ARRAY)) throw new IllegalArgumentException("Expected an ARRAY value, got a " + field.type()); - List<HostName> hostnames = new ArrayList<>(field.entries()); + List<Address> addresses = new ArrayList<>(field.entries()); for (int i = 0; i < field.entries(); i++) { Inspector entry = field.entry(i); if ( ! entry.type().equals(Type.STRING)) throw new IllegalArgumentException("Expected a STRING value, got a " + entry.type()); - hostnames.add(HostName.of(entry.asString())); + Address address = new Address(entry.asString()); + addresses.add(address); } - return hostnames; + return addresses; } private Node patchRequiredDiskSpeed(Node node, String value) { 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 f98c4ba1199..49e6265b6da 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 @@ -6,17 +6,18 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.CloudAccount; 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.serialization.NetworkPortsSerializer; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.restapi.SlimeJsonResponse; import com.yahoo.slime.Cursor; +import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.flags.StringFlag; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.node.Address; import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.hosted.provision.node.TrustStoreItem; @@ -51,7 +52,7 @@ class NodesResponse extends SlimeJsonResponse { private final NodeFilter filter; private final boolean recursive; - private final Function<com.yahoo.vespa.applicationmodel.HostName, Optional<HostInfo>> orchestrator; + private final Function<HostName, Optional<HostInfo>> orchestrator; private final NodeRepository nodeRepository; private final StringFlag wantedDockerTagFlag; @@ -150,7 +151,7 @@ class NodesResponse extends SlimeJsonResponse { object.setString("wantedVespaVersion", allocation.membership().cluster().vespaVersion().toFullString()); NodeResourcesSerializer.toSlime(allocation.requestedResources(), object.setObject("requestedResources")); allocation.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, object.setArray("networkPorts"))); - orchestrator.apply(new com.yahoo.vespa.applicationmodel.HostName(node.hostname())) + orchestrator.apply(new HostName(node.hostname())) .ifPresent(info -> { if (info.status() != HostStatus.NO_REMARKS) { object.setString("orchestratorStatus", info.status().asString()); @@ -179,8 +180,8 @@ class NodesResponse extends SlimeJsonResponse { toSlime(node.history().events(), object.setArray("history")); toSlime(node.history().log(), object.setArray("log")); ipAddressesToSlime(node.ipConfig().primary(), object.setArray("ipAddresses")); - ipAddressesToSlime(node.ipConfig().pool().asSet(), object.setArray("additionalIpAddresses")); - hostnamesToSlime(node.ipConfig().pool().hostnames(), object); + ipAddressesToSlime(node.ipConfig().pool().ipSet(), object.setArray("additionalIpAddresses")); + addressesToSlime(node.ipConfig().pool().getAddressList(), object); node.reports().toSlime(object, "reports"); node.modelName().ifPresent(modelName -> object.setString("modelName", modelName)); node.switchHostname().ifPresent(switchHostname -> object.setString("switchHostname", switchHostname)); @@ -248,11 +249,11 @@ class NodesResponse extends SlimeJsonResponse { ipAddresses.forEach(array::addString); } - private void hostnamesToSlime(List<HostName> hostnames, Cursor object) { - if (hostnames.isEmpty()) return; + private void addressesToSlime(List<Address> addresses, Cursor object) { + if (addresses.isEmpty()) return; // When/if Address becomes richer: add another field (e.g. "addresses") and expand to array of objects Cursor addressesArray = object.setArray("additionalHostnames"); - hostnames.forEach(hostname -> addressesArray.addString(hostname.value())); + addresses.forEach(address -> addressesArray.addString(address.hostname())); } private void trustedCertsToSlime(List<TrustStoreItem> trustStoreItems, Cursor object) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java index dadef5ce243..543901621d0 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java @@ -7,7 +7,6 @@ import com.yahoo.config.provision.ApplicationLockException; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.HostFilter; -import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; @@ -34,6 +33,7 @@ import com.yahoo.vespa.hosted.provision.NodeMutex; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.applications.Application; import com.yahoo.vespa.hosted.provision.autoscale.Load; +import com.yahoo.vespa.hosted.provision.node.Address; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter; @@ -280,12 +280,12 @@ public class NodesV2ApiHandler extends ThreadedHttpRequestHandler { Set<String> ipAddressPool = new HashSet<>(); inspector.field("additionalIpAddresses").traverse((ArrayTraverser) (i, item) -> ipAddressPool.add(item.asString())); - List<HostName> hostnames = new ArrayList<>(); + List<Address> addressPool = new ArrayList<>(); inspector.field("additionalHostnames").traverse((ArrayTraverser) (i, item) -> - hostnames.add(HostName.of(item.asString()))); + addressPool.add(new Address(item.asString()))); Node.Builder builder = Node.create(inspector.field("id").asString(), - IP.Config.of(ipAddresses, ipAddressPool, hostnames), + IP.Config.of(ipAddresses, ipAddressPool, addressPool), inspector.field("hostname").asString(), flavorFromSlime(inspector), nodeTypeFromSlime(inspector.field("type"))) 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 ad53dc8727d..b0ff82bb809 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 @@ -3,19 +3,19 @@ package com.yahoo.vespa.hosted.provision.testutils; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.Cloud; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.HostEvent; -import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeAllocationException; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.node.Address; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.provisioning.FatalProvisioningException; -import com.yahoo.vespa.hosted.provision.provisioning.HostIpConfig; import com.yahoo.vespa.hosted.provision.provisioning.HostProvisioner; import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator; import com.yahoo.vespa.hosted.provision.provisioning.ProvisionedHost; @@ -24,13 +24,12 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.IntStream; /** @@ -85,7 +84,7 @@ public class MockHostProvisioner implements HostProvisioner { hostType, sharing == HostSharing.exclusive ? Optional.of(applicationId) : Optional.empty(), Optional.empty(), - createHostnames(hostType, hostFlavor, index), + createAddressesForHost(hostType, hostFlavor, index), resources, osVersion, cloudAccount)); @@ -95,16 +94,16 @@ public class MockHostProvisioner implements HostProvisioner { } @Override - public HostIpConfig provision(Node host, Set<Node> children) throws FatalProvisioningException { + public List<Node> provision(Node host, Set<Node> children) throws FatalProvisioningException { if (behaviours.contains(Behaviour.failProvisioning)) throw new FatalProvisioningException("Failed to provision node(s)"); if (host.state() != Node.State.provisioned) throw new IllegalStateException("Host to provision must be in " + Node.State.provisioned); - Map<String, IP.Config> result = new HashMap<>(); - result.put(host.hostname(), createIpConfig(host)); + List<Node> result = new ArrayList<>(); + result.add(withIpAssigned(host)); for (var child : children) { if (child.state() != Node.State.reserved) throw new IllegalStateException("Child to provisioned must be in " + Node.State.reserved); - result.put(child.hostname(), createIpConfig(child)); + result.add(withIpAssigned(child)); } - return new HostIpConfig(result); + return result; } @Override @@ -196,21 +195,21 @@ public class MockHostProvisioner implements HostProvisioner { return flavor.resources().compatibleWith(resourcesToVerify); } - private List<HostName> createHostnames(NodeType hostType, Flavor flavor, int hostIndex) { + private List<Address> createAddressesForHost(NodeType hostType, Flavor flavor, int hostIndex) { long numAddresses = Math.max(2, Math.round(flavor.resources().bandwidthGbps())); return IntStream.range(1, (int) numAddresses) .mapToObj(i -> { String hostname = hostType == NodeType.host ? "host" + hostIndex + "-" + i : hostType.childNodeType().name() + i; - return HostName.of(hostname); + return new Address(hostname); }) .toList(); } - public IP.Config createIpConfig(Node node) { + public Node withIpAssigned(Node node) { if (!node.type().isHost()) { - return node.ipConfig().withPrimary(nameResolver.resolveAll(node.hostname())); + return node.with(node.ipConfig().withPrimary(nameResolver.resolveAll(node.hostname()))); } int hostIndex = Integer.parseInt(node.hostname().replaceAll("^[a-z]+|-\\d+$", "")); Set<String> addresses = Set.of("::" + hostIndex + ":0"); @@ -224,7 +223,7 @@ public class MockHostProvisioner implements HostProvisioner { } } IP.Pool pool = node.ipConfig().pool().withIpAddresses(ipAddressPool); - return node.ipConfig().withPrimary(addresses).withPool(pool); + return node.with(node.ipConfig().withPrimary(addresses).withPool(pool)); } public enum Behaviour { |