Skip to content

Commit

Permalink
Merge pull request #32295 from vespa-engine/mpolden/allocate-by-hostname
Browse files Browse the repository at this point in the history
Allocate IP by hostname in dynamically provisioned clouds
  • Loading branch information
mpolden authored Aug 29, 2024
2 parents 24e6f9d + ed36a5b commit 8df6ba8
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ private List<Node> findCandidates(ApplicationId application, ClusterCapacity clu
ClusterSpec cluster = asSpec(Optional.ofNullable(clusterCapacity.clusterType()), clusterIndex);
NodeSpec nodeSpec = NodeSpec.from(clusterCapacity.count(), 1, nodeResources, false, true,
nodeRepository().zone().cloud().account(), Duration.ZERO);
var allocationContext = IP.Allocation.Context.from(nodeRepository().zone().cloud().name(),
var allocationContext = IP.Allocation.Context.from(nodeRepository().zone().cloud(),
nodeSpec.cloudAccount().isExclave(nodeRepository().zone()),
nodeRepository().nameResolver());
NodePrioritizer prioritizer = new NodePrioritizer(allNodes, application, cluster, nodeSpec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import ai.vespa.net.InetAddressUtil;
import com.google.common.net.InetAddresses;
import com.yahoo.config.provision.Cloud;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.HostName;
Expand Down Expand Up @@ -312,27 +313,18 @@ public record Allocation(String hostname, Optional<String> ipv4Address, Optional
Objects.requireNonNull(ipv6Address, "ipv6Address must be non-null");
}

public static class Context {
private final CloudName cloudName;
private final boolean exclave;
private final NameResolver resolver;
public record Context(Cloud cloud, boolean exclave, NameResolver resolver) {

private Context(CloudName cloudName, boolean exclave, NameResolver resolver) {
this.cloudName = cloudName;
this.exclave = exclave;
this.resolver = resolver;
}

public static Context from(CloudName cloudName, boolean exclave, NameResolver resolver) {
return new Context(cloudName, exclave, resolver);
public static Context from(Cloud cloud, boolean exclave, NameResolver resolver) {
return new Context(cloud, exclave, resolver);
}

public NameResolver resolver() { return resolver; }

public boolean allocateFromUnusedHostname() { return exclave || cloudName == CloudName.AZURE; }
public boolean allocateFromUnusedHostname() { return exclave || cloud.dynamicProvisioning(); }

public boolean hasIpNotInDns(Version version) {
if (exclave && cloudName == CloudName.GCP && version.is4()) {
if (exclave && cloud.name() == CloudName.GCP && version.is4()) {
// Exclave nodes in GCP have IPv4, because load balancers backends are required to be IPv4,
// but it's private (10.x). The hostname only resolves to the public IPv6 address.
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ static void validateParentHosts(ApplicationId application, NodeList allNodes, No
Set<String> nonActiveApplicationHosts = new HashSet<>(nonActiveHosts);
nonActiveApplicationHosts.removeIf(host -> potentialChildren.childrenOf(host).type(Type.combined, Type.container, Type.content).isEmpty());

if (nonActiveHosts.size() > 0) {
if (!nonActiveHosts.isEmpty()) {
int numActiveApplication = applicationParentHostnames.size() - nonActiveApplicationHosts.size();
int numActiveAdmin = parentHostnames.size() - nonActiveHosts.size() - numActiveApplication;
var messageBuilder = new StringBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ private NodeAllocation prepareAllocation(ApplicationId application, ClusterSpec
Supplier<Integer> nextIndex, LockedNodeList allNodes) {
validateAccount(requested.cloudAccount(), application, allNodes);
NodeAllocation allocation = new NodeAllocation(allNodes, application, cluster, requested, nextIndex, nodeRepository);
IP.Allocation.Context allocationContext = IP.Allocation.Context.from(nodeRepository.zone().cloud().name(),
IP.Allocation.Context allocationContext = IP.Allocation.Context.from(nodeRepository.zone().cloud(),
requested.cloudAccount().isExclave(nodeRepository.zone()),
nodeRepository.nameResolver());
NodePrioritizer prioritizer = new NodePrioritizer(allNodes,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Vespa.ai. 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.yahoo.config.provision.Cloud;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeFlavors;
Expand Down Expand Up @@ -30,8 +31,8 @@ public class IPTest {
private static final LockedNodeList emptyList = new LockedNodeList(List.of(), () -> {});
private final MockNameResolver resolver = new MockNameResolver().explicitReverseRecords();

private IP.Allocation.Context contextOf(boolean exclave) {
return IP.Allocation.Context.from(CloudName.AWS, exclave, resolver);
private IP.Allocation.Context contextOf(boolean exclave, boolean dynamicProvisioning) {
return IP.Allocation.Context.from(Cloud.builder().name(CloudName.AWS).dynamicProvisioning(dynamicProvisioning).build(), exclave, resolver);
}

@Test
Expand All @@ -49,7 +50,7 @@ public void test_find_allocation_ipv6_only() {
resolver.addReverseRecord("::1", "host3");
resolver.addReverseRecord("::2", "host1");

var context = contextOf(false);
var context = contextOf(false, false);
Optional<IP.Allocation> allocation = pool.findAllocation(context, emptyList);
assertEquals(Optional.of("::1"), allocation.get().ipv6Address());
assertFalse(allocation.get().ipv4Address().isPresent());
Expand All @@ -68,7 +69,7 @@ public void test_find_allocation_ipv6_only() {
@Test
public void test_find_allocation_ipv4_only() {
var pool = testPool(false);
var allocation = pool.findAllocation(contextOf(false), emptyList);
var allocation = pool.findAllocation(contextOf(false, false), emptyList);
assertFalse("Found allocation", allocation.isEmpty());
assertEquals(Optional.of("127.0.0.1"), allocation.get().ipv4Address());
assertTrue("No IPv6 address", allocation.get().ipv6Address().isEmpty());
Expand All @@ -77,7 +78,7 @@ public void test_find_allocation_ipv4_only() {
@Test
public void test_find_allocation_dual_stack() {
IP.Pool pool = testPool(true);
Optional<IP.Allocation> allocation = pool.findAllocation(contextOf(false), emptyList);
Optional<IP.Allocation> allocation = pool.findAllocation(contextOf(false, false), emptyList);
assertEquals(Optional.of("::1"), allocation.get().ipv6Address());
assertEquals("127.0.0.2", allocation.get().ipv4Address().get());
assertEquals("host3", allocation.get().hostname());
Expand All @@ -88,7 +89,7 @@ public void test_find_allocation_multiple_ipv4_addresses() {
IP.Pool pool = testPool(true);
resolver.addRecord("host3", "127.0.0.127");
try {
pool.findAllocation(contextOf(false), emptyList);
pool.findAllocation(contextOf(false, false), emptyList);
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertEquals("Hostname host3 resolved to more than 1 IPv4 address: [127.0.0.2, 127.0.0.127]",
Expand All @@ -102,7 +103,7 @@ public void test_find_allocation_invalid_ipv4_reverse_record() {
resolver.removeRecord("127.0.0.2")
.addReverseRecord("127.0.0.2", "host5");
try {
pool.findAllocation(contextOf(false), emptyList);
pool.findAllocation(contextOf(false, false), emptyList);
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertEquals("Hostnames resolved from each IP address do not point to the same hostname " +
Expand All @@ -123,7 +124,7 @@ public void test_enclave() {
List.of("2600:1f10:::2", "2600:1f10:::3"),
List.of(HostName.of("node1"), HostName.of("node2")));
IP.Pool pool = config.pool();
Optional<IP.Allocation> allocation = pool.findAllocation(contextOf(true), emptyList);
Optional<IP.Allocation> allocation = pool.findAllocation(contextOf(true, true), emptyList);
}

private IP.Pool testPool(boolean dualStack) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import com.yahoo.config.provision.NodeResources.StorageType;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SharedHosts;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.flags.InMemoryFlagSource;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.HostFilter;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeFlavors;
Expand Down Expand Up @@ -447,14 +448,16 @@ public List<Node> makeProvisionedNodes(int n, Function<Integer, String> nodeName
String ipv6 = String.format("::%x", nextIP);

nameResolver.addRecord(hostname, ipv4, ipv6);
var hostIps = new ArrayList<String>();
List<String> hostIps = new ArrayList<>();
hostIps.add(ipv4);
hostIps.add(ipv6);

var ipAddressPool = new ArrayList<String>();
List<String> ipAddressPool = new ArrayList<>();
List<HostName> nodeHostnames = new ArrayList<>();
for (int poolIp = 1; poolIp <= ipAddressPoolSize; poolIp++) {
nextIP++;
String nodeHostname = hostnameParts[0] + "-" + poolIp + (hostnameParts.length > 1 ? "." + hostnameParts[1] : "");
nodeHostnames.add(HostName.of(nodeHostname));
String ipv6Addr = String.format("::%x", nextIP);
ipAddressPool.add(ipv6Addr);
nameResolver.addRecord(nodeHostname, ipv6Addr);
Expand All @@ -464,7 +467,7 @@ public List<Node> makeProvisionedNodes(int n, Function<Integer, String> nodeName
nameResolver.addRecord(nodeHostname, ipv4Addr);
}
}
Node.Builder builder = Node.create(hostname, IP.Config.of(hostIps, ipAddressPool), hostname, flavor, type)
Node.Builder builder = Node.create(hostname, IP.Config.of(hostIps, ipAddressPool, nodeHostnames), hostname, flavor, type)
.cloudAccount(cloudAccount);
reservedTo.ifPresent(builder::reservedTo);
nodes.add(builder.build());
Expand Down

0 comments on commit 8df6ba8

Please sign in to comment.