diff options
author | Martin Polden <mpolden@mpolden.no> | 2018-11-19 12:06:47 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2018-11-19 12:30:59 +0100 |
commit | 7a2ae4a07106a69d3b6a8eb62366d7ee9b9f493b (patch) | |
tree | 0eb0e0dadf4660c1f05955fed480dec5ee7f156c /node-repository | |
parent | 74b5eb63c2c2c7dac2320335051017c736eb72cc (diff) |
Add support for dual-stack Docker containers
Diffstat (limited to 'node-repository')
17 files changed, 321 insertions, 145 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 455494b3d49..497d7cb9d7e 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 @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision; import com.google.common.collect.ImmutableSet; @@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.Generation; import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.hosted.provision.node.Status; +import com.yahoo.vespa.hosted.provision.node.IP; import java.time.Instant; import java.util.Objects; @@ -30,7 +31,7 @@ public final class Node { private final String id; private final Set<String> ipAddresses; - private final Set<String> additionalIpAddresses; + private final IP.AddressPool ipAddressPool; private final String hostname; private final String openStackId; private final Optional<String> parentHostname; @@ -46,24 +47,24 @@ public final class Node { private Optional<Allocation> allocation; /** Temporary method until we can merge it with the other create method */ - public static Node createDockerNode(String openStackId, Set<String> ipAddresses, Set<String> additionalIpAddresses, String hostname, Optional<String> parentHostname, Flavor flavor, NodeType type) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, Status.initial(), State.reserved, + public static Node createDockerNode(String openStackId, Set<String> ipAddresses, Set<String> ipAddressPool, String hostname, Optional<String> parentHostname, Flavor flavor, NodeType type) { + return new Node(openStackId, ipAddresses, ipAddressPool, hostname, parentHostname, flavor, Status.initial(), State.reserved, Optional.empty(), History.empty(), type); } /** Creates a node in the initial state (reserved for docker containers, provisioned otherwise) */ - public static Node create(String openStackId, Set<String> ipAddresses, Set<String> additionalIpAddresses, String hostname, Optional<String> parentHostname, Flavor flavor, NodeType type) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, Status.initial(), State.provisioned, + public static Node create(String openStackId, Set<String> ipAddresses, Set<String> ipAddressPool, String hostname, Optional<String> parentHostname, Flavor flavor, NodeType type) { + return new Node(openStackId, ipAddresses, ipAddressPool, hostname, parentHostname, flavor, Status.initial(), State.provisioned, Optional.empty(), History.empty(), type); } /** Do not use. Construct nodes by calling {@link NodeRepository#createNode} */ - private Node(String openStackId, Set<String> ipAddresses, Set<String> additionalIpAddresses, String hostname, Optional<String> parentHostname, + private Node(String openStackId, Set<String> ipAddresses, Set<String> ipAddressPool, String hostname, Optional<String> parentHostname, Flavor flavor, Status status, State state, Allocation allocation, History history, NodeType type) { - this(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, Optional.of(allocation), history, type); + this(openStackId, ipAddresses, ipAddressPool, hostname, parentHostname, flavor, status, state, Optional.of(allocation), history, type); } - public Node(String openStackId, Set<String> ipAddresses, Set<String> additionalIpAddresses, String hostname, Optional<String> parentHostname, + public Node(String openStackId, Set<String> ipAddresses, Set<String> ipAddressPool, String hostname, Optional<String> parentHostname, Flavor flavor, Status status, State state, Optional<Allocation> allocation, History history, NodeType type) { Objects.requireNonNull(openStackId, "A node must have an openstack id"); @@ -79,7 +80,7 @@ public final class Node { this.id = hostname; this.ipAddresses = ImmutableSet.copyOf(ipAddresses); - this.additionalIpAddresses = ImmutableSet.copyOf(additionalIpAddresses); + this.ipAddressPool = new IP.AddressPool(this, ipAddressPool); this.hostname = hostname; this.parentHostname = parentHostname; this.openStackId = openStackId; @@ -100,8 +101,9 @@ public final class Node { /** Returns the IP addresses of this node */ public Set<String> ipAddresses() { return ipAddresses; } - /** Returns the additional IP addresses of this node (used to 'child' nodes) */ - public Set<String> additionalIpAddresses() { return additionalIpAddresses; } + /** Returns the IP address pool available on this node. These IP addresses are available for use by containers + * running on this node */ + public IP.AddressPool ipAddressPool() { return ipAddressPool; } /** Returns the host name of this node */ public String hostname() { return hostname; } @@ -165,28 +167,28 @@ public final class Node { /** Returns a node with the status assigned to the given value */ public Node with(Status status) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, allocation, history, type); + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, parentHostname, flavor, status, state, allocation, history, type); } /** Returns a node with the type assigned to the given value */ public Node with(NodeType type) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, allocation, history, type); + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, parentHostname, flavor, status, state, allocation, history, type); } /** Returns a node with the flavor assigned to the given value */ public Node with(Flavor flavor) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, allocation, history, type); + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, parentHostname, flavor, status, state, allocation, history, type); } /** Returns a copy of this with the reboot generation set to generation */ public Node withReboot(Generation generation) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status.withReboot(generation), state, + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, parentHostname, flavor, status.withReboot(generation), state, allocation, history, type); } /** Returns a copy of this with the openStackId set */ public Node withOpenStackId(String openStackId) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, allocation, history, type); + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, parentHostname, flavor, status, state, allocation, history, type); } /** Returns a copy of this with a history record saying it was detected to be down at this instant */ @@ -210,24 +212,24 @@ public final class Node { * Do not use this to allocate a node. */ public Node with(Allocation allocation) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, allocation, history, type); + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, parentHostname, flavor, status, state, allocation, history, type); } /** Returns a copy of this node with the IP addresses set to the given value. */ public Node withIpAddresses(Set<String> ipAddresses) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, - allocation, history, type); + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, parentHostname, flavor, status, state, + allocation, history, type); } - /** Returns a copy of this node with the additional IP addresses set to the given value. */ - public Node withAdditionalIpAddresses(Set<String> additionalIpAddresses) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, - allocation, history, type); + /** Returns a copy of this node with IP address pool set to the given value. */ + public Node withIpAddressPool(Set<String> ipAddressPool) { + return new Node(openStackId, ipAddresses, ipAddressPool, hostname, parentHostname, flavor, status, state, + allocation, history, type); } /** Returns a copy of this node with the parent hostname assigned to the given value. */ public Node withParentHostname(String parentHostname) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, Optional.of(parentHostname), flavor, status, state, + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, Optional.of(parentHostname), flavor, status, state, allocation, history, type); } @@ -242,22 +244,22 @@ public final class Node { /** Returns a copy of this node with the given history. */ public Node with(History history) { - return new Node(openStackId, ipAddresses, additionalIpAddresses, hostname, parentHostname, flavor, status, state, allocation, history, type); + return new Node(openStackId, ipAddresses, ipAddressPool.asSet(), hostname, parentHostname, flavor, status, state, allocation, history, type); } - private void requireNonEmptyString(Optional<String> value, String message) { + private static void requireNonEmptyString(Optional<String> value, String message) { Objects.requireNonNull(value, message); if (value.isPresent()) requireNonEmptyString(value.get(), message); } - private void requireNonEmptyString(String value, String message) { + private static void requireNonEmptyString(String value, String message) { Objects.requireNonNull(value, message); if (value.trim().isEmpty()) throw new IllegalArgumentException(message + ", but was '" + value + "'"); } - private void requireIpAddresses(Set<String> values, String message) { + private static void requireIpAddresses(Set<String> values, String message) { if (values.isEmpty()) { throw new IllegalArgumentException(message); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java index 5060510be20..1c5e7eb1906 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision; import com.google.common.collect.ImmutableSet; @@ -262,13 +262,13 @@ public class NodeRepository extends AbstractComponent { // ----------------- Node lifecycle ----------------------------------------------------------- /** Creates a new node object, without adding it to the node repo. If no IP address is given, it will be resolved */ - public Node createNode(String openStackId, String hostname, Set<String> ipAddresses, Set<String> additionalIpAddresses, Optional<String> parentHostname, + public Node createNode(String openStackId, String hostname, Set<String> ipAddresses, Set<String> ipAddressPool, Optional<String> parentHostname, Flavor flavor, NodeType type) { if (ipAddresses.isEmpty()) { ipAddresses = nameResolver.getAllByNameOrThrow(hostname); } - return Node.create(openStackId, ImmutableSet.copyOf(ipAddresses), additionalIpAddresses, hostname, parentHostname, flavor, type); + return Node.create(openStackId, ImmutableSet.copyOf(ipAddresses), ipAddressPool, hostname, parentHostname, flavor, type); } public Node createNode(String openStackId, String hostname, Set<String> ipAddresses, Optional<String> parentHostname, 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 new file mode 100644 index 00000000000..2dfb1e12df6 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java @@ -0,0 +1,155 @@ +// Copyright 2018 Yahoo Holdings. 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.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeList; +import com.yahoo.vespa.hosted.provision.persistence.NameResolver; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * Represents IP addresses owned by a node. + * + * @author mpolden + */ +public class IP { + + /** A pool of available IP addresses */ + public static class AddressPool { + + private final Node owner; + private final Set<String> addresses; + + public AddressPool(Node owner, Set<String> addresses) { + this.owner = Objects.requireNonNull(owner, "owner must be non-null"); + this.addresses = ImmutableSet.copyOf(requireAddresses(addresses)); + } + + /** + * Find a free allocation in this pool + * + * @param nodes All nodes in the repository + * @return An allocation from the pool, if any can be made + */ + public Optional<Allocation> findAllocation(NodeList nodes) { + Set<String> unusedAddresses = findUnused(nodes); + Optional<String> ipv6Address = unusedAddresses.stream() + .filter(addr -> InetAddresses.forString(addr) instanceof Inet6Address) + .findFirst(); + Optional<String> ipv4Address = unusedAddresses.stream() + .filter(addr -> InetAddresses.forString(addr) instanceof Inet4Address) + .findFirst(); + + // An allocation must contain one IPv6 address, but IPv4 is optional. All hosts have IPv6 addresses that + // can be used by containers, while the availability of IPv4 addresses depends on the cloud provider. + return ipv6Address.map(address -> new Allocation(address, ipv4Address)); + } + + /** Find all unused addresses in this pool + * + * @param nodes All nodes in the repository + */ + public Set<String> findUnused(NodeList nodes) { + Set<String> unusedAddresses = new LinkedHashSet<>(addresses); + nodes.childrenOf(owner).asList().forEach(node -> unusedAddresses.removeAll(node.ipAddresses())); + return Collections.unmodifiableSet(unusedAddresses); + } + + public Set<String> asSet() { + return addresses; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AddressPool that = (AddressPool) o; + return Objects.equals(addresses, that.addresses); + } + + @Override + public int hashCode() { + return Objects.hash(addresses); + } + + private static Set<String> requireAddresses(Set<String> addresses) { + Objects.requireNonNull(addresses, "ipAddressPool must be non-null"); + + long ipv6AddrCount = addresses.stream() + .filter(addr -> InetAddresses.forString(addr) instanceof Inet6Address) + .count(); + if (ipv6AddrCount == addresses.size()) { + return addresses; // IPv6-only pool is valid + } + + long ipv4AddrCount = addresses.stream() + .filter(addr -> InetAddresses.forString(addr) instanceof Inet4Address) + .count(); + if (ipv4AddrCount == ipv6AddrCount) { + return addresses; + } + + throw new IllegalArgumentException(String.format("Dual-stacked IP address pool must have an " + + "equal number of addresses of each version " + + "[IPv6 address count = %d, IPv4 address count = %d]", + ipv6AddrCount, ipv4AddrCount)); + } + + } + + /** An IP address allocation from a pool */ + public static class Allocation { + + private final String ipv6Address; + private final Optional<String> ipv4Address; + + private Allocation(String ipv6Address, Optional<String> ipv4Address) { + this.ipv6Address = Objects.requireNonNull(ipv6Address, "ipv6Address must be non-null"); + this.ipv4Address = Objects.requireNonNull(ipv4Address, "ipv4Address must be non-null"); + } + + /** + * Resolve and return the hostname of this allocation + * + * @param resolver DNS name resolver to use when resolving the hostname + * @throws IllegalArgumentException if DNS is misconfigured + * @return The hostname + */ + public String resolveHostname(NameResolver resolver) { + String hostname6 = resolver.getHostname(ipv6Address).orElseThrow(() -> new IllegalArgumentException("Could not resolve IP address: " + ipv6Address)); + if (ipv4Address.isPresent()) { + String hostname4 = resolver.getHostname(ipv4Address.get()).orElseThrow(() -> new IllegalArgumentException("Could not resolve IP address: " + ipv4Address)); + if (!hostname6.equals(hostname4)) { + throw new IllegalArgumentException(String.format("Hostnames resolved from each IP address do not " + + "point to the same hostname [%s -> %s, %s -> %s]", + ipv6Address, hostname6, ipv4Address.get(), hostname4)); + } + } + return hostname6; + } + + /** All IP addresses in this */ + public Set<String> addresses() { + ImmutableSet.Builder<String> builder = ImmutableSet.builder(); + builder.add(ipv6Address); + ipv4Address.ifPresent(builder::add); + return builder.build(); + } + + @Override + public String toString() { + return "ipv6Address='" + ipv6Address + '\'' + + ", ipv4Address='" + ipv4Address.orElse("none") + '\''; + } + + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java index 7291add1cf1..632244cec69 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.persistence; import com.google.common.util.concurrent.UncheckedTimeoutException; @@ -190,7 +190,7 @@ public class CuratorDatabaseClient { CuratorTransaction curatorTransaction = curatorDatabase.newCuratorTransactionIn(transaction); for (Node node : nodes) { - Node newNode = new Node(node.openStackId(), node.ipAddresses(), node.additionalIpAddresses(), node.hostname(), + Node newNode = new Node(node.openStackId(), node.ipAddresses(), node.ipAddressPool().asSet(), node.hostname(), node.parentHostname(), node.flavor(), newNodeStatus(node, toState), toState, 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 ddaa0e4173f..61425080680 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 @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.persistence; import com.google.common.collect.ImmutableSet; @@ -44,7 +44,7 @@ public class NodeSerializer { // Node fields private static final String hostnameKey = "hostname"; private static final String ipAddressesKey = "ipAddresses"; - private static final String additionalIpAddressesKey = "additionalIpAddresses"; + private static final String ipAddressPoolKey = "additionalIpAddresses"; private static final String openStackIdKey = "openStackId"; private static final String parentHostnameKey = "parentHostname"; private static final String historyKey = "history"; @@ -99,7 +99,7 @@ public class NodeSerializer { private void toSlime(Node node, Cursor object) { object.setString(hostnameKey, node.hostname()); toSlime(node.ipAddresses(), object.setArray(ipAddressesKey)); - toSlime(node.additionalIpAddresses(), object.setArray(additionalIpAddressesKey)); + toSlime(node.ipAddressPool().asSet(), object.setArray(ipAddressPoolKey)); object.setString(openStackIdKey, node.openStackId()); node.parentHostname().ifPresent(hostname -> object.setString(parentHostnameKey, hostname)); object.setString(flavorKey, node.flavor().name()); @@ -153,7 +153,7 @@ public class NodeSerializer { private Node nodeFromSlime(Node.State state, Inspector object) { return new Node(object.field(openStackIdKey).asString(), ipAddressesFromSlime(object, ipAddressesKey), - ipAddressesFromSlime(object, additionalIpAddressesKey), + ipAddressesFromSlime(object, ipAddressPoolKey), object.field(hostnameKey).asString(), parentHostnameFromSlime(object), flavorFromSlime(object), diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java index 82895511488..c7bfb9178ce 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. 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.config.provision.Flavor; @@ -6,9 +6,7 @@ import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; -import java.util.HashSet; import java.util.List; -import java.util.Set; /** * Capacity calculation for docker hosts. @@ -70,7 +68,7 @@ public class DockerHostCapacity { * Number of free (not allocated) IP addresses assigned to the dockerhost. */ int freeIPs(Node dockerHost) { - return findFreeIps(dockerHost, allNodes.asList()).size(); + return dockerHost.ipAddressPool().findUnused(allNodes).size(); } public ResourceCapacity getFreeCapacityTotal() { @@ -138,15 +136,4 @@ public class DockerHostCapacity { return isInactive || isRetired; } - /** - * Compare the additional ip addresses against the set of used addresses on - * child nodes. - */ - static Set<String> findFreeIps(Node dockerHost, List<Node> allNodes) { - Set<String> freeIPAddresses = new HashSet<>(dockerHost.additionalIpAddresses()); - for (Node child : new NodeList(allNodes).childrenOf(dockerHost).asList()) { - freeIPAddresses.removeAll(child.ipAddresses()); - } - return freeIPAddresses; - } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java index 4126a95de2c..4a06c7593f6 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. 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.config.provision.ApplicationId; @@ -8,6 +8,7 @@ import com.yahoo.config.provision.NodeType; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; +import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.persistence.NameResolver; import java.util.ArrayList; @@ -132,18 +133,24 @@ public class NodePrioritizer { if (!hostHasCapacityForWantedFlavor || conflictingCluster) continue; log.log(LogLevel.DEBUG, "Trying to add new Docker node on " + node); - Set<String> ipAddresses = DockerHostCapacity.findFreeIps(node, allNodes); - if (ipAddresses.isEmpty()) continue; - String ipAddress = ipAddresses.stream().findFirst().get(); - Optional<String> hostname = nameResolver.getHostname(ipAddress); - if (!hostname.isPresent()) { - log.log(LogLevel.DEBUG, "Could not find hostname for " + ipAddress + ", skipping it"); + + Optional<IP.Allocation> allocation = node.ipAddressPool().findAllocation(new NodeList(allNodes)); + if (!allocation.isPresent()) continue; // No free addresses in this pool + + String hostname; + try { + hostname = allocation.get().resolveHostname(nameResolver); + } catch (IllegalArgumentException e) { + log.log(LogLevel.WARNING, "Failed to resolve hostname for allocation: " + allocation.get() + ", skipping", e); continue; } - Node newNode = Node.createDockerNode("fake-" + hostname.get(), - Collections.singleton(ipAddress), - Collections.emptySet(), hostname.get(), - Optional.of(node.hostname()), getFlavor(requestedNodes), + + Node newNode = Node.createDockerNode("fake-" + hostname, + allocation.get().addresses(), + Collections.emptySet(), + hostname, + Optional.of(node.hostname()), + getFlavor(requestedNodes), NodeType.tenant); PrioritizableNode nodePri = toNodePriority(newNode, false, true); if (!nodePri.violatesSpares || isAllocatingForReplacement) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java index 910da4e90bf..d9e505c77e5 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.restapi.v2; import com.yahoo.component.Version; @@ -127,7 +127,7 @@ public class NodePatcher { case "ipAddresses" : return node.withIpAddresses(asStringSet(value)); case "additionalIpAddresses" : - return node.withAdditionalIpAddresses(asStringSet(value)); + return node.withIpAddressPool(asStringSet(value)); case WANT_TO_RETIRE : return node.with(node.status().withWantToRetire(asBoolean(value))); case WANT_TO_DEPROVISION : diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java index 6b31adf10fa..df6bd7d0f15 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.restapi.v2; import com.yahoo.component.Version; @@ -215,14 +215,14 @@ public class NodesApiHandler extends LoggingRequestHandler { Optional<String> parentHostname = optionalString(inspector.field("parentHostname")); Set<String> ipAddresses = new HashSet<>(); inspector.field("ipAddresses").traverse((ArrayTraverser) (i, item) -> ipAddresses.add(item.asString())); - Set<String> additionalIpAddresses = new HashSet<>(); - inspector.field("additionalIpAddresses").traverse((ArrayTraverser) (i, item) -> additionalIpAddresses.add(item.asString())); + Set<String> ipAddressPool = new HashSet<>(); + inspector.field("additionalIpAddresses").traverse((ArrayTraverser) (i, item) -> ipAddressPool.add(item.asString())); return nodeRepository.createNode( inspector.field("openStackId").asString(), inspector.field("hostname").asString(), ipAddresses, - additionalIpAddresses, + ipAddressPool, parentHostname, nodeFlavors.getFlavorOrThrow(inspector.field("flavor").asString()), nodeTypeFromSlime(inspector.field("type"))); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java index 26294f84040..2a5673df1d2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.restapi.v2; import com.yahoo.config.provision.ApplicationId; @@ -185,7 +185,7 @@ class NodesResponse extends HttpResponse { object.setBool("wantToDeprovision", node.status().wantToDeprovision()); toSlime(node.history(), object.setArray("history")); ipAddressesToSlime(node.ipAddresses(), object.setArray("ipAddresses")); - ipAddressesToSlime(node.additionalIpAddresses(), object.setArray("additionalIpAddresses")); + ipAddressesToSlime(node.ipAddressPool().asSet(), object.setArray("additionalIpAddresses")); node.status().hardwareDivergence().ifPresent(hardwareDivergence -> object.setString("hardwareDivergence", hardwareDivergence)); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java index d2d2da813d8..e1b1fc85f0a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java @@ -1,13 +1,13 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.testutils; -import com.google.common.collect.ImmutableSet; import com.yahoo.vespa.hosted.provision.persistence.NameResolver; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -27,8 +27,15 @@ public class MockNameResolver implements NameResolver { private final Map<String, Set<String>> records = new HashMap<>(); public MockNameResolver addRecord(String hostname, String... ipAddress) { - Arrays.stream(ipAddress).forEach(Objects::requireNonNull); - records.put(hostname, ImmutableSet.copyOf(ipAddress)); + Objects.requireNonNull(hostname, "hostname must be non-null"); + Arrays.stream(ipAddress).forEach(ip -> Objects.requireNonNull(ip, "ipAddress must be non-null")); + records.compute(hostname, (ignored, ipAddresses) -> { + if (ipAddresses == null) { + ipAddresses = new LinkedHashSet<>(); + } + ipAddresses.addAll(Arrays.asList(ipAddress)); + return ipAddresses; + }); return this; } 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 de14bc7e480..e3fca286830 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 @@ -1,6 +1,7 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.testutils; +import com.google.common.collect.ImmutableSet; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; @@ -25,11 +26,10 @@ import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; /** * A mock repository prepopulated with some applications. @@ -61,13 +61,8 @@ public class MockNodeRepository extends NodeRepository { List<Node> nodes = new ArrayList<>(); // Regular nodes - final List<String> ipAddressesForAllHost = Arrays.asList("127.0.0.1", "::1"); - Collections.sort(ipAddressesForAllHost); - final HashSet<String> ipAddresses = new HashSet<>(ipAddressesForAllHost); - - final List<String> additionalIpAddressesForAllHost = Arrays.asList("::2", "::3", "::4"); - Collections.sort(additionalIpAddressesForAllHost); - final HashSet<String> additionalIpAddresses = new HashSet<>(additionalIpAddressesForAllHost); + Set<String> ipAddresses = ImmutableSet.of("::1", "127.0.0.1"); + Set<String> ipAddressPool = ImmutableSet.of("::2", "::3", "::4"); nodes.add(createNode("node1", "host1.yahoo.com", ipAddresses, Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.tenant)); nodes.add(createNode("node2", "host2.yahoo.com", ipAddresses, Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.tenant)); @@ -95,11 +90,11 @@ public class MockNodeRepository extends NodeRepository { nodes.add(node55.with(node55.status().withWantToRetire(true).withWantToDeprovision(true))); /* Setup docker hosts (two of these will be reserved for spares */ - nodes.add(createNode("dockerhost1", "dockerhost1.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); - nodes.add(createNode("dockerhost2", "dockerhost2.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); - nodes.add(createNode("dockerhost3", "dockerhost3.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); - nodes.add(createNode("dockerhost4", "dockerhost4.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); - nodes.add(createNode("dockerhost5", "dockerhost5.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); + nodes.add(createNode("dockerhost1", "dockerhost1.yahoo.com", ipAddresses, ipAddressPool, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); + nodes.add(createNode("dockerhost2", "dockerhost2.yahoo.com", ipAddresses, ipAddressPool, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); + nodes.add(createNode("dockerhost3", "dockerhost3.yahoo.com", ipAddresses, ipAddressPool, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); + nodes.add(createNode("dockerhost4", "dockerhost4.yahoo.com", ipAddresses, ipAddressPool, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); + nodes.add(createNode("dockerhost5", "dockerhost5.yahoo.com", ipAddresses, ipAddressPool, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); // Config servers nodes.add(createNode("cfg1", "cfg1.yahoo.com", Collections.singleton("127.0.1.1"), Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.config)); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java index 248299e7991..2b91262d291 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java @@ -1,6 +1,7 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.monitoring; +import com.google.common.collect.ImmutableSet; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterMembership; @@ -31,7 +32,6 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -120,13 +120,9 @@ public class MetricsReporterTest { true); // Allow 4 containers - Set<String> additionalIps = new HashSet<>(); - additionalIps.add("::2"); - additionalIps.add("::3"); - additionalIps.add("::4"); - additionalIps.add("::5"); + Set<String> ipAddressPool = ImmutableSet.of("::2", "::3", "::4", "::5"); - Node dockerHost = Node.create("openStackId1", Collections.singleton("::1"), additionalIps, "dockerHost", Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), NodeType.host); + Node dockerHost = Node.create("openStackId1", Collections.singleton("::1"), ipAddressPool, "dockerHost", Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), NodeType.host); nodeRepository.addNodes(Collections.singletonList(dockerHost)); nodeRepository.dirtyRecursively("dockerHost", Agent.system, getClass().getSimpleName()); nodeRepository.setReady("dockerHost", Agent.system, getClass().getSimpleName()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java index 6f7ba72ff7e..b3f1fdb3fb2 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.persistence; import com.google.common.collect.ImmutableSet; @@ -239,18 +239,18 @@ public class SerializationTest { } @Test - public void serialize_additional_ip_addresses() { + public void serialize_ip_address_pool() { Node node = createNode(); - // Test round-trip with additional addresses - node = node.withAdditionalIpAddresses(ImmutableSet.of("10.0.0.1", "10.0.0.2", "10.0.0.3")); + // Test round-trip with IP address pool + node = node.withIpAddressPool(ImmutableSet.of("::1", "::2", "::3")); Node copy = nodeSerializer.fromJson(node.state(), nodeSerializer.toJson(node)); - assertEquals(node.additionalIpAddresses(), copy.additionalIpAddresses()); + assertEquals(node.ipAddressPool(), copy.ipAddressPool()); - // Test round-trip without additional addresses (handle empty ip set) + // Test round-trip without IP address pool (handle empty pool) node = createNode(); copy = nodeSerializer.fromJson(node.state(), nodeSerializer.toJson(node)); - assertEquals(node.additionalIpAddresses(), copy.additionalIpAddresses()); + assertEquals(node.ipAddressPool(), copy.ipAddressPool()); } @Test diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java index 1e69b26d0e0..d13d3ee4945 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. 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.config.provision.Flavor; @@ -10,7 +10,7 @@ import org.junit.Test; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -124,11 +124,11 @@ public class DockerHostCapacityTest { private Set<String> generateIPs(int start, int count) { // Allow 4 containers - Set<String> additionalIps = new HashSet<>(); + Set<String> ipAddressPool = new LinkedHashSet<>(); for (int i = start; i < (start + count); i++) { - additionalIps.add("::" + i); + ipAddressPool.add("::" + i); } - return additionalIps; + return ipAddressPool; } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisioningTest.java index 223a8bc83b0..24685118041 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisioningTest.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. 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.google.common.collect.ImmutableSet; @@ -20,7 +20,6 @@ import com.yahoo.vespa.curator.transaction.CuratorTransaction; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.node.Agent; -import org.junit.Assert; import org.junit.Test; import java.time.Instant; @@ -79,8 +78,8 @@ public class DynamicDockerProvisioningTest { addAndAssignNode(application2, "2b", dockerHosts.get(3).hostname(), clusterSpec2, flavor, 1, tester); // Redeploy both applications (to be agnostic on which hosts are picked as spares) - deployapp(application1, clusterSpec1, flavor, tester, 2); - deployapp(application2, clusterSpec2, flavor, tester, 2); + deployApp(application1, clusterSpec1, flavor, tester, 2); + deployApp(application2, clusterSpec2, flavor, tester, 2); // Assert that we have two spare nodes (two hosts that are don't have allocations) Set<String> hostsWithChildren = new HashSet<>(); @@ -89,7 +88,7 @@ public class DynamicDockerProvisioningTest { hostsWithChildren.add(node.parentHostname().get()); } } - Assert.assertEquals(4 - tester.provisioner().getSpareCapacityProd(), hostsWithChildren.size()); + assertEquals(4 - tester.provisioner().getSpareCapacityProd(), hostsWithChildren.size()); } @@ -112,26 +111,26 @@ public class DynamicDockerProvisioningTest { // Application 1 ApplicationId application1 = makeApplicationId("t1", "a1"); ClusterSpec clusterSpec1 = clusterSpec("myContent.t1.a1"); - deployapp(application1, clusterSpec1, flavor, tester, 3); + deployApp(application1, clusterSpec1, flavor, tester, 3); // Application 2 ApplicationId application2 = makeApplicationId("t2", "a2"); ClusterSpec clusterSpec2 = clusterSpec("myContent.t2.a2"); - deployapp(application2, clusterSpec2, flavor, tester, 2); + deployApp(application2, clusterSpec2, flavor, tester, 2); // Application 3 ApplicationId application3 = makeApplicationId("t3", "a3"); ClusterSpec clusterSpec3 = clusterSpec("myContent.t3.a3"); - deployapp(application3, clusterSpec3, flavor, tester, 2); + deployApp(application3, clusterSpec3, flavor, tester, 2); // App 2 and 3 should have been allocated to the same nodes - fail one of the parent hosts from there String parent = tester.nodeRepository().getNodes(application2).stream().findAny().get().parentHostname().get(); tester.nodeRepository().failRecursively(parent, Agent.system, "Testing"); // Redeploy all applications - deployapp(application1, clusterSpec1, flavor, tester, 3); - deployapp(application2, clusterSpec2, flavor, tester, 2); - deployapp(application3, clusterSpec3, flavor, tester, 2); + deployApp(application1, clusterSpec1, flavor, tester, 3); + deployApp(application2, clusterSpec2, flavor, tester, 2); + deployApp(application3, clusterSpec3, flavor, tester, 2); Map<Integer, Integer> numberOfChildrenStat = new HashMap<>(); for (Node node : dockerHosts) { @@ -173,7 +172,7 @@ public class DynamicDockerProvisioningTest { addAndAssignNode(application1, "1b", dockerHosts.get(1).hostname(), clusterSpec1, flavor, 1, tester); // Redeploy both applications (to be agnostic on which hosts are picked as spares) - deployapp(application1, clusterSpec1, flavor, tester, 2); + deployApp(application1, clusterSpec1, flavor, tester, 2); // Assert that we have two spare nodes (two hosts that are don't have allocations) Set<String> hostsWithChildren = new HashSet<>(); @@ -182,7 +181,7 @@ public class DynamicDockerProvisioningTest { hostsWithChildren.add(node.parentHostname().get()); } } - Assert.assertEquals(2, hostsWithChildren.size()); + assertEquals(2, hostsWithChildren.size()); } @Test(expected = OutOfCapacityException.class) @@ -271,12 +270,28 @@ public class DynamicDockerProvisioningTest { tester.prepare(application, clusterSpec("myContent.t2.a2"), 2, 1, flavor.canonicalName()); } + @Test + public void provision_dual_stack_containers() { + ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east")), flavorsConfig()); + tester.makeReadyNodes(2, "host-large", NodeType.host, 10, true); + deployZoneApp(tester); + + ApplicationId application = tester.makeApplicationId(); + Flavor flavor = tester.nodeRepository().getAvailableFlavors().getFlavorOrThrow("d-3"); + List<HostSpec> hosts = tester.prepare(application, clusterSpec("myContent.t1.a1"), 2, 1, flavor.canonicalName()); + tester.activate(application, hosts); + + List<Node> activeNodes = tester.nodeRepository().getNodes(application); + assertEquals(ImmutableSet.of("::12", "127.0.127.12"), activeNodes.get(0).ipAddresses()); + assertEquals(ImmutableSet.of("::2", "127.0.127.2"), activeNodes.get(1).ipAddresses()); + } + private ApplicationId makeApplicationId(String tenant, String appName) { return ApplicationId.from(tenant, appName, "default"); } - private void deployapp(ApplicationId id, ClusterSpec spec, Flavor flavor, ProvisioningTester tester, int nodecount) { - List<HostSpec> hostSpec = tester.prepare(id, spec, nodecount, 1, flavor.canonicalName()); + private void deployApp(ApplicationId id, ClusterSpec spec, Flavor flavor, ProvisioningTester tester, int nodeCount) { + List<HostSpec> hostSpec = tester.prepare(id, spec, nodeCount, 1, flavor.canonicalName()); tester.activate(id, new HashSet<>(hostSpec)); } @@ -338,6 +353,6 @@ public class DynamicDockerProvisioningTest { } private ClusterSpec clusterSpec(String clusterId) { - return ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from(clusterId), Version.fromString("6.100"), false); + return ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from(clusterId), Version.fromString("6.42"), false); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java index d5ef20a63df..81414c0ac2d 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. 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.component.Version; @@ -38,6 +38,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -151,7 +152,7 @@ public class ProvisioningTester { return hosts2; } - public void activate(ApplicationId application, Set<HostSpec> hosts) { + public void activate(ApplicationId application, Collection<HostSpec> hosts) { NestedTransaction transaction = new NestedTransaction(); transaction.add(new CuratorTransaction(curator)); provisioner.activate(transaction, application, hosts); @@ -165,7 +166,7 @@ public class ProvisioningTester { deactivateTransaction.commit(); } - Set<String> toHostNames(Set<HostSpec> hosts) { + Collection<String> toHostNames(Collection<HostSpec> hosts) { return hosts.stream().map(HostSpec::hostname).collect(Collectors.toSet()); } @@ -231,9 +232,12 @@ public class ProvisioningTester { return makeReadyNodes(n, flavor, type, 0); } - List<Node> makeProvisionedNodes(int n, String flavor, NodeType type, int additionalIps) { - List<Node> nodes = new ArrayList<>(n); + List<Node> makeProvisionedNodes(int count, String flavor, NodeType type, int ipAddressPoolSize) { + return makeProvisionedNodes(count, flavor, type, ipAddressPoolSize, false); + } + List<Node> makeProvisionedNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) { + List<Node> nodes = new ArrayList<>(n); for (int i = 0; i < n; i++) { nextHost++; @@ -257,21 +261,26 @@ public class ProvisioningTester { hostIps.add(ipv4); hostIps.add(ipv6); - Set<String> addips = new HashSet<>(); - for (int ipSeq = 1; ipSeq < additionalIps; ipSeq++) { + Set<String> ipAddressPool = new LinkedHashSet<>(); + for (int poolIp = 1; poolIp < ipAddressPoolSize; poolIp++) { nextIP++; - String ipv6node = String.format("::%d", nextIP); - addips.add(ipv6node); - nameResolver.addRecord(String.format("node-%d-of-%s",ipSeq, hostname), ipv6node); + String ipv6Addr = String.format("::%d", nextIP); + ipAddressPool.add(ipv6Addr); + nameResolver.addRecord(String.format("node-%d-of-%s", poolIp, hostname), ipv6Addr); + if (dualStack) { + String ipv4Addr = String.format("127.0.127.%d", nextIP); + ipAddressPool.add(ipv4Addr); + nameResolver.addRecord(String.format("node-%d-of-%s", poolIp, hostname), ipv4Addr); + } } nodes.add(nodeRepository.createNode(hostname, - hostname, - hostIps, - addips, - Optional.empty(), - nodeFlavors.getFlavorOrThrow(flavor), - type)); + hostname, + hostIps, + ipAddressPool, + Optional.empty(), + nodeFlavors.getFlavorOrThrow(flavor), + type)); } nodes = nodeRepository.addNodes(nodes); return nodes; @@ -309,9 +318,12 @@ public class ProvisioningTester { return nodeRepository.getNodes(application.getApplicationId(), Node.State.active); } + List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize) { + return makeReadyNodes(n, flavor, type, ipAddressPoolSize, false); + } - List<Node> makeReadyNodes(int n, String flavor, NodeType type, int additionalIps) { - List<Node> nodes = makeProvisionedNodes(n, flavor, type, additionalIps); + List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) { + List<Node> nodes = makeProvisionedNodes(n, flavor, type, ipAddressPoolSize, dualStack); nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName()); return nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName()); } |