summaryrefslogtreecommitdiffstats
path: root/node-repository
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2018-11-19 12:06:47 +0100
committerMartin Polden <mpolden@mpolden.no>2018-11-19 12:30:59 +0100
commit7a2ae4a07106a69d3b6a8eb62366d7ee9b9f493b (patch)
tree0eb0e0dadf4660c1f05955fed480dec5ee7f156c /node-repository
parent74b5eb63c2c2c7dac2320335051017c736eb72cc (diff)
Add support for dual-stack Docker containers
Diffstat (limited to 'node-repository')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java60
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java155
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java17
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java29
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNameResolver.java15
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java25
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java12
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/SerializationTest.java14
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java10
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisioningTest.java47
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java48
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());
}