diff options
author | Jon Bratseth <bratseth@oath.com> | 2018-03-20 21:23:51 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@oath.com> | 2018-03-20 21:23:51 +0100 |
commit | 75df844174e47bec91a3067ab50b16a9833fd5f0 (patch) | |
tree | 50770bc317f9cff020751ae4c8f6b1919423fd9b | |
parent | 601b4777257e9d1694bdc6987d2b63ee6ccf52dc (diff) |
Allow applications to request exlcusive access to hosts
45 files changed, 562 insertions, 296 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java index fb58a991012..2e1d507eea1 100644 --- a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java +++ b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java @@ -4,9 +4,24 @@ package com.yahoo.config.model.provision; import com.yahoo.collections.ListMap; import com.yahoo.collections.Pair; import com.yahoo.config.model.api.HostProvisioner; -import com.yahoo.config.provision.*; - -import java.util.*; +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.ProvisionLogger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; /** * In memory host provisioner. NB! ATM cannot be reused after allocate has been called. @@ -111,11 +126,14 @@ public class InMemoryProvisioner implements HostProvisioner { List<HostSpec> allocation = new ArrayList<>(); if (groups == 1) { - allocation.addAll(allocateHostGroup(cluster, flavor, capacity, startIndexForClusters)); + allocation.addAll(allocateHostGroup(cluster.with(Optional.of(ClusterSpec.Group.from(0))), + flavor, + capacity, + startIndexForClusters)); } else { for (int i = 0; i < groups; i++) { - allocation.addAll(allocateHostGroup(cluster.changeGroup(Optional.of(ClusterSpec.Group.from(i))), + allocation.addAll(allocateHostGroup(cluster.with(Optional.of(ClusterSpec.Group.from(i))), flavor, capacity / groups, allocation.size())); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java index 61dee04a444..f7ed7241133 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java @@ -38,16 +38,20 @@ public class NodesSpecification { /** The flavor the nodes should have, or empty to use the default */ private final Optional<String> flavor; + private final boolean exclusive; + /** The identifier of the custom docker image layer to use (not supported yet) */ private final Optional<String> dockerImage; private NodesSpecification(boolean dedicated, int count, int groups, Version version, boolean required, + boolean exclusive, Optional<String> flavor, Optional<String> dockerImage) { this.dedicated = dedicated; this.count = count; this.groups = groups; this.version = version; this.required = required; + this.exclusive = exclusive; this.flavor = flavor; this.dockerImage = dockerImage; } @@ -58,6 +62,7 @@ public class NodesSpecification { nodesElement.getIntegerAttribute("groups", 1), version, nodesElement.getBooleanAttribute("required", false), + nodesElement.getBooleanAttribute("exclusive", false), Optional.ofNullable(nodesElement.getStringAttribute("flavor")), Optional.ofNullable(nodesElement.getStringAttribute("docker-image"))); } @@ -90,12 +95,14 @@ public class NodesSpecification { if (parentElement == null) return Optional.empty(); ModelElement nodesElement = parentElement.getChild("nodes"); if (nodesElement == null) return Optional.empty(); - return Optional.of(new NodesSpecification(nodesElement.getBooleanAttribute("dedicated", false), version, nodesElement)); + return Optional.of(new NodesSpecification(nodesElement.getBooleanAttribute("dedicated", false), + version, nodesElement)); } /** Returns a requirement from <code>count</code> nondedicated nodes in one group */ public static NodesSpecification nonDedicated(int count, Version version) { - return new NodesSpecification(false, count, 1, version, false, Optional.empty(), Optional.empty()); + return new NodesSpecification(false, count, 1, version, false, false, + Optional.empty(), Optional.empty()); } /** @@ -104,6 +111,13 @@ public class NodesSpecification { */ public boolean isDedicated() { return dedicated; } + /** + * Returns whether the physical hosts running the nodes of this application can + * also run nodes of other applications. Using exclusive nodes for containers increases security + * and increases cost. + */ + public boolean isExclusive() { return exclusive; } + /** Returns the number of nodes required */ public int count() { return count; } @@ -111,7 +125,7 @@ public class NodesSpecification { public int groups() { return groups; } public Map<HostResource, ClusterMembership> provision(HostSystem hostSystem, ClusterSpec.Type clusterType, ClusterSpec.Id clusterId, DeployLogger logger) { - ClusterSpec cluster = ClusterSpec.request(clusterType, clusterId, version); + ClusterSpec cluster = ClusterSpec.request(clusterType, clusterId, version, exclusive); return hostSystem.allocateHosts(cluster, Capacity.fromNodeCount(count, flavor, required), groups, logger); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 4caf0baf012..39aba9cfdf9 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -509,7 +509,10 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { return singleContentHost.get(); } else { // request 1 node - ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from(cluster.getName()), context.getDeployState().getWantedNodeVespaVersion()); + ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.container, + ClusterSpec.Id.from(cluster.getName()), + context.getDeployState().getWantedNodeVespaVersion(), + false); return cluster.getHostSystem().allocateHosts(clusterSpec, Capacity.fromNodeCount(1), 1, logger).keySet().iterator().next(); } } else { @@ -531,7 +534,8 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { NodeType type = NodeType.valueOf(nodesElement.getAttribute("type")); ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from(cluster.getName()), - context.getDeployState().getWantedNodeVespaVersion()); + context.getDeployState().getWantedNodeVespaVersion(), + false); Map<HostResource, ClusterMembership> hosts = cluster.getRoot().getHostSystem().allocateHosts(clusterSpec, Capacity.fromRequiredNodeType(type), 1, log); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/HostResourceTest.java b/config-model/src/test/java/com/yahoo/vespa/model/HostResourceTest.java index 1a0e9b9f810..28e3dd9be78 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/HostResourceTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/HostResourceTest.java @@ -144,7 +144,7 @@ public class HostResourceTest { } private static ClusterSpec clusterSpec(ClusterSpec.Type type, String id) { - return ClusterSpec.from(type, ClusterSpec.Id.from(id), ClusterSpec.Group.from(0), Version.fromString("6.42")); + return ClusterSpec.from(type, ClusterSpec.Id.from(id), ClusterSpec.Group.from(0), Version.fromString("6.42"), false); } private HostResource mockHostResource() { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java b/config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java index b5934da3178..cabe9c0969b 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java @@ -117,7 +117,7 @@ public class VespaModelFactoryTest { ClusterMembership.from(ClusterSpec.from(ClusterSpec.Type.admin, new ClusterSpec.Id(routingClusterName), ClusterSpec.Group.from(0), - Version.fromString("6.42")), + Version.fromString("6.42"), false), 0)); } @@ -128,7 +128,7 @@ public class VespaModelFactoryTest { ClusterMembership.from(ClusterSpec.from(ClusterSpec.Type.container, new ClusterSpec.Id(routingClusterName), ClusterSpec.Group.from(0), - Version.fromString("6.42")), + Version.fromString("6.42"), false), 0))); } }; diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java index 9b1fb45a548..e061c5d07ee 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java @@ -6,7 +6,7 @@ import java.util.Optional; /** * A capacity request. * - * @author lulf + * @author Ulf Lilleengen * @author bratseth */ public final class Capacity { @@ -16,28 +16,28 @@ public final class Capacity { private final boolean required; private final Optional<String> flavor; - + private final NodeType type; - private Capacity(int nodeCount, boolean required, Optional<String> flavor, NodeType type) { + private Capacity(int nodeCount, Optional<String> flavor, boolean required, NodeType type) { this.nodeCount = nodeCount; - this.flavor = flavor; this.required = required; + this.flavor = flavor; this.type = type; } /** Returns the number of nodes requested */ public int nodeCount() { return nodeCount; } - /** Returns whether the requested number of nodes must be met exactly for a request for this to succeed */ - public boolean isRequired() { return required; } - /** * The node flavor requested, or empty if no particular flavor is specified. * This may be satisfied by the requested flavor or a suitable replacement */ public Optional<String> flavor() { return flavor; } + /** Returns whether the requested number of nodes must be met exactly for a request for this to succeed */ + public boolean isRequired() { return required; } + /** * Returns the node type (role) requested. This is tenant nodes by default. * If some other type is requested the node count and flavor may be ignored @@ -52,37 +52,16 @@ public final class Capacity { /** Creates this from a desired node count: The request may be satisfied with a smaller number of nodes. */ public static Capacity fromNodeCount(int capacity) { - return fromNodeCount(capacity, Optional.empty()); - } - /** Creates this from a desired node count: The request may be satisfied with a smaller number of nodes. */ - public static Capacity fromNodeCount(int nodeCount, String flavor) { - return fromNodeCount(nodeCount, Optional.of(flavor)); - } - /** Creates this from a desired node count: The request may be satisfied with a smaller number of nodes. */ - public static Capacity fromNodeCount(int nodeCount, Optional<String> flavor) { - return new Capacity(nodeCount, false, flavor, NodeType.tenant); - } - - /** Creates this from a required node count: Requests must fail unless the node count can be satisfied exactly */ - public static Capacity fromRequiredNodeCount(int nodeCount) { - return fromRequiredNodeCount(nodeCount, Optional.empty()); - } - /** Creates this from a required node count: Requests must fail unless the node count can be satisfied exactly */ - public static Capacity fromRequiredNodeCount(int nodeCount, String flavor) { - return fromRequiredNodeCount(nodeCount, Optional.of(flavor)); - } - /** Creates this from a required node count: Requests must fail unless the node count can be satisfied exactly */ - public static Capacity fromRequiredNodeCount(int nodeCount, Optional<String> flavor) { - return new Capacity(nodeCount, true, flavor, NodeType.tenant); + return fromNodeCount(capacity, Optional.empty(), false); } public static Capacity fromNodeCount(int nodeCount, Optional<String> flavor, boolean required) { - return new Capacity(nodeCount, required, flavor, NodeType.tenant); + return new Capacity(nodeCount, flavor, required, NodeType.tenant); } /** Creates this from a node type */ public static Capacity fromRequiredNodeType(NodeType type) { - return new Capacity(0, true, Optional.empty(), type); + return new Capacity(0, Optional.empty(), true, type); } } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java index 3f0da396431..6c08a796c20 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java @@ -4,8 +4,8 @@ package com.yahoo.config.provision; import com.yahoo.component.Version; /** - * A node's membership in a cluster. - * This is a value object. + * A node's membership in a cluster. This is a value object. + * The format is "clusterType/clusterId/groupId/index[/exclusive][/retired] * * @author bratseth */ @@ -19,26 +19,20 @@ public class ClusterMembership { protected ClusterMembership() {} private ClusterMembership(String stringValue, Version vespaVersion) { - String restValue; - if (stringValue.endsWith("/retired")) { - retired = true; - restValue = stringValue.substring(0, stringValue.length() - "/retired".length()); - } - else { - retired = false; - restValue = stringValue; - } - - String[] components = restValue.split("/"); - - if ( components.length == 3) // Aug 2016: This should never happen any more - initWithoutGroup(components, vespaVersion); - else if (components.length == 4) - initWithGroup(components, vespaVersion); - else + String[] components = stringValue.split("/"); + if (components.length < 4 || components.length > 6) throw new RuntimeException("Could not parse '" + stringValue + "' to a cluster membership. " + - "Expected 'id/type.index[/group]'"); + "Expected 'clusterType/clusterId/groupId/index[/retired][/exclusive]'"); + boolean exclusive = false; + if (components.length > 4) { + exclusive = components[4].equals("exclusive"); + retired = components[components.length-1].equals("retired"); + } + + this.cluster = ClusterSpec.from(ClusterSpec.Type.valueOf(components[0]), ClusterSpec.Id.from(components[1]), + ClusterSpec.Group.from(Integer.valueOf(components[2])), vespaVersion, exclusive); + this.index = Integer.parseInt(components[3]); this.stringValue = toStringValue(); } @@ -49,23 +43,14 @@ public class ClusterMembership { this.stringValue = toStringValue(); } - private void initWithoutGroup(String[] components, Version vespaVersion) { - this.cluster = ClusterSpec.request(ClusterSpec.Type.valueOf(components[0]), - ClusterSpec.Id.from(components[1]), - vespaVersion); - this.index = Integer.parseInt(components[2]); - } - - private void initWithGroup(String[] components, Version vespaVersion) { - this.cluster = ClusterSpec.from(ClusterSpec.Type.valueOf(components[0]), ClusterSpec.Id.from(components[1]), - ClusterSpec.Group.from(Integer.valueOf(components[2])), vespaVersion); - this.index = Integer.parseInt(components[3]); - } - protected String toStringValue() { - return cluster.type().name() + "/" + cluster.id().value() + - ( cluster.group().isPresent() ? "/" + cluster.group().get().index() : "") + "/" + index + - ( retired ? "/retired" : ""); + return cluster.type().name() + + "/" + cluster.id().value() + + (cluster.group().isPresent() ? "/" + cluster.group().get().index() : "") + + "/" + index + + ( cluster.isExclusive() ? "/exclusive" : "") + + ( retired ? "/retired" : ""); + } /** Returns the cluster this node is a member of */ @@ -87,7 +72,12 @@ public class ClusterMembership { return new ClusterMembership(cluster, index, false); } + // TODO: Remove after April 2018 public ClusterMembership changeCluster(ClusterSpec newCluster) { + return with(newCluster); + } + + public ClusterMembership with(ClusterSpec newCluster) { return new ClusterMembership(newCluster, index, retired); } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java index bcc13f2c3e9..a0df2547631 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java @@ -22,11 +22,14 @@ public final class ClusterSpec { private final Version vespaVersion; - private ClusterSpec(Type type, Id id, Optional<Group> groupId, Version vespaVersion) { + private boolean exclusive; + + private ClusterSpec(Type type, Id id, Optional<Group> groupId, Version vespaVersion, boolean exclusive) { this.type = type; this.id = id; this.groupId = groupId; this.vespaVersion = vespaVersion; + this.exclusive = exclusive; } /** Returns the cluster type */ @@ -40,13 +43,40 @@ public final class ClusterSpec { /** Returns the group within the cluster this specifies, or empty to specify the whole cluster */ public Optional<Group> group() { return groupId; } - public ClusterSpec changeGroup(Optional<Group> newGroup) { return new ClusterSpec(type, id, newGroup, vespaVersion); } + /** + * Returns whether the physical hosts running the nodes of this application can + * also run nodes of other applications. Using exclusive nodes for containers increases security + * and increases cost. + */ + public boolean isExclusive() { return exclusive; } + + // TODO: Remove after April 2018 + public ClusterSpec changeGroup(Optional<Group> newGroup) { + return with(newGroup); + } + + public ClusterSpec with(Optional<Group> newGroup) { + return new ClusterSpec(type, id, newGroup, vespaVersion, exclusive); + } + + public ClusterSpec exclusive(boolean exclusive) { + return new ClusterSpec(type, id, groupId, vespaVersion, exclusive); + } + // TODO: Remove after April 2018 public static ClusterSpec request(Type type, Id id, Version vespaVersion) { - return new ClusterSpec(type, id, Optional.empty(), vespaVersion); + return request(type, id, vespaVersion, false); + } + public static ClusterSpec request(Type type, Id id, Version vespaVersion, boolean exclusive) { + return new ClusterSpec(type, id, Optional.empty(), vespaVersion, exclusive); } + + // TODO: Remove after April 2018 public static ClusterSpec from(Type type, Id id, Group groupId, Version vespaVersion) { - return new ClusterSpec(type, id, Optional.of(groupId), vespaVersion); + return new ClusterSpec(type, id, Optional.of(groupId), vespaVersion, false); + } + public static ClusterSpec from(Type type, Id id, Group groupId, Version vespaVersion, boolean exclusive) { + return new ClusterSpec(type, id, Optional.of(groupId), vespaVersion, exclusive); } @Override diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java index 675af88596a..a39b8d3ef34 100644 --- a/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java +++ b/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java @@ -18,7 +18,7 @@ public class AllocatedHostsTest { private final HostSpec h1 = new HostSpec("host1", Optional.empty()); private final HostSpec h2 = new HostSpec("host2", Optional.empty()); - private final HostSpec h3 = new HostSpec("host3", Optional.of(ClusterMembership.from("container/test/0", com.yahoo.component.Version.fromString("6.73.1")))); + private final HostSpec h3 = new HostSpec("host3", Optional.of(ClusterMembership.from("container/test/0/0", com.yahoo.component.Version.fromString("6.73.1")))); @Test public void testAllocatedHostsSerialization() throws IOException { @@ -37,7 +37,7 @@ public class AllocatedHostsTest { assertTrue(serializedAllocatedHosts.getHosts().contains(h2)); assertTrue(serializedAllocatedHosts.getHosts().contains(h3)); assertTrue(!getHost(h1.hostname(), serializedAllocatedHosts.getHosts()).membership().isPresent()); - assertEquals("container/test/0", getHost(h3.hostname(), serializedAllocatedHosts.getHosts()).membership().get().stringValue()); + assertEquals("container/test/0/0", getHost(h3.hostname(), serializedAllocatedHosts.getHosts()).membership().get().stringValue()); assertEquals(h3.membership().get().cluster().vespaVersion(), getHost(h3.hostname(), serializedAllocatedHosts.getHosts()).membership().get().cluster().vespaVersion()); } diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterMembershipTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterMembershipTest.java index bc8586d5c52..f1970326137 100644 --- a/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterMembershipTest.java +++ b/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterMembershipTest.java @@ -18,30 +18,20 @@ public class ClusterMembershipTest { @Test public void testContainerServiceInstance() { - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("id1"), Version.fromString("6.42")); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("id1"), Version.fromString("6.42"), false); assertContainerService(ClusterMembership.from(cluster, 3)); } @Test - public void testContainerServiceInstanceFromString() { - assertContainerService(ClusterMembership.from("container/id1/3", Vtag.currentVersion)); - } - - @Test public void testServiceInstance() { - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("id1"), Version.fromString("6.42")); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("id1"), Version.fromString("6.42"), false); assertContentService(ClusterMembership.from(cluster, 37)); } @Test - public void testServiceInstanceFromString() { - assertContentService(ClusterMembership.from("content/id1/37", Vtag.currentVersion)); - } - - @Test public void testServiceInstanceWithGroup() { ClusterSpec cluster = ClusterSpec.from(ClusterSpec.Type.content, ClusterSpec.Id.from("id1"), - ClusterSpec.Group.from(4), Version.fromString("6.42")); + ClusterSpec.Group.from(4), Version.fromString("6.42"), false); assertContentServiceWithGroup(ClusterMembership.from(cluster, 37)); } @@ -52,19 +42,14 @@ public class ClusterMembershipTest { @Test public void testServiceInstanceWithRetire() { - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("id1"), Version.fromString("6.42")); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("id1"), Version.fromString("6.42"), false); assertContentServiceWithRetire(ClusterMembership.retiredFrom(cluster, 37)); } @Test - public void testServiceInstanceWithRetireFromString() { - assertContentServiceWithRetire(ClusterMembership.from("content/id1/37/retired", Vtag.currentVersion)); - } - - @Test public void testServiceInstanceWithGroupAndRetire() { ClusterSpec cluster = ClusterSpec.from(ClusterSpec.Type.content, ClusterSpec.Id.from("id1"), - ClusterSpec.Group.from(4), Version.fromString("6.42")); + ClusterSpec.Group.from(4), Version.fromString("6.42"), false); assertContentServiceWithGroupAndRetire(ClusterMembership.retiredFrom(cluster, 37)); } @@ -76,7 +61,7 @@ public class ClusterMembershipTest { private void assertContainerService(ClusterMembership instance) { assertEquals(ClusterSpec.Type.container, instance.cluster().type()); assertEquals("id1", instance.cluster().id().value()); - assertEquals(Optional.<ClusterSpec.Group>empty(), instance.cluster().group()); + assertFalse(instance.cluster().group().isPresent()); assertEquals(3, instance.index()); assertEquals("container/id1/3", instance.stringValue()); } @@ -84,7 +69,7 @@ public class ClusterMembershipTest { private void assertContentService(ClusterMembership instance) { assertEquals(ClusterSpec.Type.content, instance.cluster().type()); assertEquals("id1", instance.cluster().id().value()); - assertFalse("gr4", instance.cluster().group().isPresent()); + assertFalse(instance.cluster().group().isPresent()); assertEquals(37, instance.index()); assertFalse(instance.retired()); assertEquals("content/id1/37", instance.stringValue()); @@ -99,6 +84,7 @@ public class ClusterMembershipTest { assertEquals("content/id1/4/37", instance.stringValue()); } + /** Serializing a spec without a group assigned works, but not deserialization */ private void assertContentServiceWithRetire(ClusterMembership instance) { assertEquals(ClusterSpec.Type.content, instance.cluster().type()); assertEquals("id1", instance.cluster().id().value()); diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/HostFilterTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/HostFilterTest.java index adc7105b2a6..e1d6f061446 100644 --- a/config-provisioning/src/test/java/com/yahoo/config/provision/HostFilterTest.java +++ b/config-provisioning/src/test/java/com/yahoo/config/provision/HostFilterTest.java @@ -24,25 +24,25 @@ public class HostFilterTest { HostFilter type = HostFilter.clusterType(ClusterSpec.Type.content); HostFilter id = HostFilter.clusterId(ClusterSpec.Id.from("type1")); - assertTrue( all.matches("anyhost", "flavor", membership("container/anytype/0"))); - assertFalse(hostname.matches("anyhost", "flavor", membership("container/anytype/0"))); - assertFalse(type.matches("anyhost", "flavor", membership("container/anytype/0"))); - assertFalse(id.matches("anyhost", "flavor", membership("container/anytype/0"))); + assertTrue( all.matches("anyhost", "flavor", membership("container/anytype/0/0"))); + assertFalse(hostname.matches("anyhost", "flavor", membership("container/anytype/0/0"))); + assertFalse(type.matches("anyhost", "flavor", membership("container/anytype/0/0"))); + assertFalse(id.matches("anyhost", "flavor", membership("container/anytype/0/0"))); - assertTrue( all.matches("anyhost", "flavor", membership("content/anytype/0"))); - assertFalse(hostname.matches("anyhost", "flavor", membership("content/anytype/0"))); - assertTrue( type.matches("anyhost", "flavor", membership("content/anytype/0"))); - assertFalse( id.matches("anyhost", "flavor", membership("content/anytype/0"))); + assertTrue( all.matches("anyhost", "flavor", membership("content/anytype/0/0"))); + assertFalse(hostname.matches("anyhost", "flavor", membership("content/anytype/0/0"))); + assertTrue( type.matches("anyhost", "flavor", membership("content/anytype/0/0"))); + assertFalse( id.matches("anyhost", "flavor", membership("content/anytype/0/0"))); - assertTrue( all.matches("host1", "flavor", membership("content/anytype/0"))); - assertTrue( hostname.matches("host1", "flavor", membership("content/anytype/0"))); - assertTrue( type.matches("host1", "flavor", membership("content/anytype/0"))); - assertFalse( id.matches("host1", "flavor", membership("content/anytype/0"))); + assertTrue( all.matches("host1", "flavor", membership("content/anytype/0/0"))); + assertTrue( hostname.matches("host1", "flavor", membership("content/anytype/0/0"))); + assertTrue( type.matches("host1", "flavor", membership("content/anytype/0/0"))); + assertFalse( id.matches("host1", "flavor", membership("content/anytype/0/0"))); - assertTrue( all.matches("host1", "flavor", membership("content/type1/0"))); - assertTrue( hostname.matches("host1", "flavor", membership("content/type1/0"))); - assertTrue( type.matches("host1", "flavor", membership("content/type1/0"))); - assertTrue( id.matches("host1", "flavor", membership("content/type1/0"))); + assertTrue( all.matches("host1", "flavor", membership("content/type1/0/0"))); + assertTrue( hostname.matches("host1", "flavor", membership("content/type1/0/0"))); + assertTrue( type.matches("host1", "flavor", membership("content/type1/0/0"))); + assertTrue( id.matches("host1", "flavor", membership("content/type1/0/0"))); } @Test @@ -52,22 +52,22 @@ public class HostFilterTest { Collections.singletonList(ClusterSpec.Type.content), Collections.singletonList(ClusterSpec.Id.from("type1"))); - assertFalse(typeAndId.matches("anyhost", "flavor", membership("content/anyType/0"))); - assertFalse(typeAndId.matches("anyhost", "flavor", membership("container/type1/0"))); - assertTrue(typeAndId.matches("anyhost", "flavor", membership("content/type1/0"))); + assertFalse(typeAndId.matches("anyhost", "flavor", membership("content/anyType/0/0"))); + assertFalse(typeAndId.matches("anyhost", "flavor", membership("container/type1/0/0"))); + assertTrue(typeAndId.matches("anyhost", "flavor", membership("content/type1/0/0"))); } @Test public void testMultiConditionFilterFromStrings() { HostFilter typeAndId = HostFilter.from("host1 host2, host3,host4", " , ,flavor", null, "type1 "); - assertFalse(typeAndId.matches("anotherhost", "flavor", membership("content/type1/0"))); - assertTrue(typeAndId.matches("host1", "flavor", membership("content/type1/0"))); - assertTrue(typeAndId.matches("host2", "flavor", membership("content/type1/0"))); - assertTrue(typeAndId.matches("host3", "flavor", membership("content/type1/0"))); - assertTrue(typeAndId.matches("host4", "flavor", membership("content/type1/0"))); - assertFalse(typeAndId.matches("host1", "flavor", membership("content/type2/0"))); - assertFalse(typeAndId.matches("host4", "differentflavor", membership("content/type1/0"))); + assertFalse(typeAndId.matches("anotherhost", "flavor", membership("content/type1/0/0"))); + assertTrue(typeAndId.matches("host1", "flavor", membership("content/type1/0/0"))); + assertTrue(typeAndId.matches("host2", "flavor", membership("content/type1/0/0"))); + assertTrue(typeAndId.matches("host3", "flavor", membership("content/type1/0/0"))); + assertTrue(typeAndId.matches("host4", "flavor", membership("content/type1/0/0"))); + assertFalse(typeAndId.matches("host1", "flavor", membership("content/type2/0/0"))); + assertFalse(typeAndId.matches("host4", "differentflavor", membership("content/type1/0/0"))); } private Optional<ClusterMembership> membership(String membershipString) { diff --git a/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogHandler.java b/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogHandler.java index 5d56ed13e21..a31e7b3b849 100644 --- a/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogHandler.java +++ b/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogHandler.java @@ -8,7 +8,7 @@ import static com.yahoo.container.core.AccessLogConfig.FileHandler.RotateScheme. import java.util.logging.Logger; /** - * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + * @author Bjorn Borud */ class AccessLogHandler { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/application/Application.java b/jdisc_core/src/main/java/com/yahoo/jdisc/application/Application.java index 98e107ee351..74546951f15 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/application/Application.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/application/Application.java @@ -7,35 +7,35 @@ import com.yahoo.jdisc.service.ClientProvider; import com.yahoo.jdisc.service.ServerProvider; /** - * <p>This interface defines the API of the singleton Application that runs in a jDISC instance. An Application instance + * This interface defines the API of the singleton Application that runs in a jDISC instance. An Application instance * will always have its {@link #destroy()} method called, regardless of whether {@link #start()} or {@link #stop()} - * threw any exceptions.</p> + * threw any exceptions. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public interface Application { /** - * <p>This method is called by the {@link ApplicationLoader} just after creating this Application instance. Use this + * This method is called by the {@link ApplicationLoader} just after creating this Application instance. Use this * method to start the Application's worker thread, and to activate a {@link Container}. If you attempt to call * {@link ContainerActivator#activateContainer(ContainerBuilder)} before this method is invoked, that call will * throw an {@link ApplicationNotReadyException}. If this method does not throw an exception, the {@link #stop()} - * method will be called at some time in the future.</p> + * method will be called at some time in the future. */ void start(); /** - * <p>This method is called by the {@link ApplicationLoader} after the corresponding signal has been issued by the + * This method is called by the {@link ApplicationLoader} after the corresponding signal has been issued by the * controlling start script. Once this method returns, all calls to {@link * ContainerActivator#activateContainer(ContainerBuilder)} will throw {@link ApplicationNotReadyException}s. Use - * this method to prepare for termination (see {@link #destroy()}).</p> + * this method to prepare for termination (see {@link #destroy()}). */ void stop(); /** - * <p>This method is called by the {@link ApplicationLoader} after first calling {@link #stop()}, and all previous + * This method is called by the {@link ApplicationLoader} after first calling {@link #stop()}, and all previous * {@link DeactivatedContainer}s have terminated. Use this method to shut down all Application components such as - * {@link ClientProvider}s and {@link ServerProvider}s.</p> + * {@link ClientProvider}s and {@link ServerProvider}s. */ void destroy(); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Allocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Allocation.java index 92c5fab6dc4..985a13d3511 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Allocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Allocation.java @@ -43,10 +43,14 @@ public class Allocation { public Generation restartGeneration() { return restartGeneration; } /** Returns a copy of this which is retired */ - public Allocation retire() { return new Allocation(owner, clusterMembership.retire(), restartGeneration, removable); } + public Allocation retire() { + return new Allocation(owner, clusterMembership.retire(), restartGeneration, removable); + } /** Returns a copy of this which is not retired */ - public Allocation unretire() { return new Allocation(owner, clusterMembership.unretire(), restartGeneration, removable); } + public Allocation unretire() { + return new Allocation(owner, clusterMembership.unretire(), restartGeneration, removable); + } /** Return whether this node is ready to be removed from the application */ public boolean isRemovable() { return removable; } 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 110313de197..d164252b018 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 @@ -328,6 +328,7 @@ public class NodeSerializer { default : throw new IllegalArgumentException("Unknown node type '" + typeString + "'"); } } + private String toString(NodeType type) { switch (type) { case tenant: return "tenant"; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java index 5ffa99e0de1..4ba272c2366 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java @@ -24,11 +24,9 @@ import java.util.stream.Collectors; class Activator { private final NodeRepository nodeRepository; - private final Clock clock; - public Activator(NodeRepository nodeRepository, Clock clock) { + public Activator(NodeRepository nodeRepository) { this.nodeRepository = nodeRepository; - this.clock = clock; } /** @@ -97,7 +95,7 @@ class Activator { List<Node> updated = new ArrayList<>(); for (Node node : nodes) { HostSpec hostSpec = getHost(node.hostname(), hosts); - node = hostSpec.membership().get().retired() ? node.retire(clock.instant()) : node.unretire(); + node = hostSpec.membership().get().retired() ? node.retire(nodeRepository.clock().instant()) : node.unretire(); node = node.with(node.allocation().get().with(hostSpec.membership().get())); if (hostSpec.flavor().isPresent()) // Docker nodes may change flavor node = node.with(hostSpec.flavor().get()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java index 3f392674b20..e9b86a9850b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.provisioning; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.Zone; import com.yahoo.config.provision.Flavor; @@ -55,6 +56,14 @@ public class CapacityPolicies { } /** + * Whether or not the nodes requested can share physical host with other applications. + * A security feature which only makes sense for prod. + */ + public boolean decideExclusivity(boolean requestedExclusivity) { + return requestedExclusivity && zone.environment() == Environment.prod; + } + + /** * Throw if the node count is 1 * * @return the argument node count diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java index 49d802c7058..93704d244b5 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java @@ -20,11 +20,9 @@ import java.util.List; public class GroupPreparer { private final NodeRepository nodeRepository; - private final Clock clock; - public GroupPreparer(NodeRepository nodeRepository, Clock clock) { + public GroupPreparer(NodeRepository nodeRepository) { this.nodeRepository = nodeRepository; - this.clock = clock; } /** @@ -65,7 +63,7 @@ public class GroupPreparer { prioritizer.addNewDockerNodes(); // Allocate from the prioritized list - NodeAllocation allocation = new NodeAllocation(application, cluster, requestedNodes, highestIndex, clock); + NodeAllocation allocation = new NodeAllocation(application, cluster, requestedNodes, highestIndex, nodeRepository); allocation.offer(prioritizer.prioritize()); if (! allocation.fullfilled()) throw new OutOfCapacityException("Could not satisfy " + requestedNodes + " for " + cluster + @@ -84,9 +82,11 @@ public class GroupPreparer { } private String outOfCapacityDetails(NodeAllocation allocation) { - if (allocation.wouldBeFulfilledWithClashingParentHost()) + if (allocation.wouldBeFulfilledWithoutExclusivity()) + return ": Not enough nodes available due to host exclusivity constraints."; + else if (allocation.wouldBeFulfilledWithClashingParentHost()) return ": Not enough nodes available on separate physical hosts."; - if (allocation.wouldBeFulfilledWithRetiredNodes()) + else if (allocation.wouldBeFulfilledWithRetiredNodes()) return ": Not enough nodes available due to retirement."; else return "."; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java index 21ed0d983f3..4df8b5ba04f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java @@ -6,15 +6,17 @@ import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.lang.MutableInteger; import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; +import com.yahoo.vespa.hosted.provision.node.Allocation; -import java.time.Clock; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -44,6 +46,9 @@ class NodeAllocation { /** The number of nodes rejected because of clashing parentHostname */ private int rejectedWithClashingParentHost = 0; + /** The number of nodes rejected due to exclusivity constraints */ + private int rejectedDueToExclusivity = 0; + /** The number of nodes that just now was changed to retired */ private int wasRetiredJustNow = 0; @@ -53,15 +58,15 @@ class NodeAllocation { /** The next membership index to assign to a new node */ private final MutableInteger highestIndex; - /** Used to record event timestamps **/ - private final Clock clock; + private final NodeRepository nodeRepository; - NodeAllocation(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, MutableInteger highestIndex, Clock clock) { + NodeAllocation(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, MutableInteger highestIndex, + NodeRepository nodeRepository) { this.application = application; this.cluster = cluster; this.requestedNodes = requestedNodes; this.highestIndex = highestIndex; - this.clock = clock; + this.nodeRepository = nodeRepository; } /** @@ -93,6 +98,7 @@ class NodeAllocation { if ( ! hasCompatibleFlavor(offered)) wantToRetireNode = true; if ( offered.flavor().isRetired()) wantToRetireNode = true; if ( offered.status().wantToRetire()) wantToRetireNode = true; + if ( requestedNodes.isExclusive() && ! hostHasOnly(application, offered.parentHostname())) wantToRetireNode = true; if (( ! saturated() && hasCompatibleFlavor(offered)) || acceptToRetire(offered) ) { accepted.add(acceptNode(offeredPriority, wantToRetireNode)); @@ -103,13 +109,23 @@ class NodeAllocation { ++rejectedWithClashingParentHost; continue; } + if ( ! hostCanHost(application, offered.parentHostname())) { + ++rejectedDueToExclusivity; + continue; + } + if ( requestedNodes.isExclusive() && ! hostHasOnly(application, offered.parentHostname())) { + ++rejectedDueToExclusivity; + continue; + } if (offered.flavor().isRetired()) { continue; } if (offered.status().wantToRetire()) { continue; } - offeredPriority.node = offered.allocate(application, ClusterMembership.from(cluster, highestIndex.add(1)), clock.instant()); + offeredPriority.node = offered.allocate(application, + ClusterMembership.from(cluster, highestIndex.add(1)), + nodeRepository.clock().instant()); accepted.add(acceptNode(offeredPriority, false)); } } @@ -128,6 +144,33 @@ class NodeAllocation { } /** + * If a parent host is given, and it hosts another application which requires exclusive access to the physical + * host, then we cannot host this application on it. + */ + private boolean hostCanHost(ApplicationId application, Optional<String> parentHostname) { + if ( ! parentHostname.isPresent()) return true; + for (Node nodeOnHost : nodeRepository.getChildNodes(parentHostname.get())) { + if ( ! nodeOnHost.allocation().isPresent()) continue; + + if ( nodeOnHost.allocation().get().membership().cluster().isExclusive() && + ! nodeOnHost.allocation().get().owner().equals(application)) + return false; + } + return true; + } + + private boolean hostHasOnly(ApplicationId application, Optional<String> parentHostname) { + if ( ! parentHostname.isPresent()) return true; // yes, as host is exclusive + + for (Node nodeOnHost : nodeRepository.getChildNodes(parentHostname.get())) { + if ( ! nodeOnHost.allocation().isPresent()) continue; + if ( ! nodeOnHost.allocation().get().owner().equals(application)) + return false; + } + return true; + } + + /** * Returns whether this node should be accepted into the cluster even if it is not currently desired * (already enough nodes, or wrong flavor). * Such nodes will be marked retired during finalization of the list of accepted nodes. @@ -166,7 +209,7 @@ class NodeAllocation { } else { ++wasRetiredJustNow; // Retire nodes which are of an unwanted flavor, retired flavor or have an overlapping parent host - node = node.retire(clock.instant()); + node = node.retire(nodeRepository.clock().instant()); prioritizableNode.node= node; } if ( ! node.allocation().get().membership().cluster().equals(cluster)) { @@ -181,7 +224,7 @@ class NodeAllocation { } private Node setCluster(ClusterSpec cluster, Node node) { - ClusterMembership membership = node.allocation().get().membership().changeCluster(cluster); + ClusterMembership membership = node.allocation().get().membership().with(cluster); return node.with(node.allocation().get().with(membership)); } @@ -203,6 +246,10 @@ class NodeAllocation { return requestedNodes.fulfilledBy(acceptedOfRequestedFlavor + rejectedWithClashingParentHost); } + boolean wouldBeFulfilledWithoutExclusivity() { + return requestedNodes.fulfilledBy(acceptedOfRequestedFlavor + rejectedDueToExclusivity); + } + /** * Make the number of <i>non-retired</i> nodes in the list equal to the requested number * of nodes, and retire the rest of the list. Only retire currently active nodes. @@ -219,7 +266,7 @@ class NodeAllocation { if (deltaRetiredCount > 0) { // retire until deltaRetiredCount is 0, prefer to retire higher indexes to minimize redistribution for (PrioritizableNode node : byDecreasingIndex(nodes)) { if ( ! node.node.allocation().get().membership().retired() && node.node.state().equals(Node.State.active)) { - node.node = node.node.retire(Agent.application, clock.instant()); + node.node = node.node.retire(Agent.application, nodeRepository.clock().instant()); surplusNodes.add(node.node); // offer this node to other groups if (--deltaRetiredCount == 0) break; } @@ -234,10 +281,13 @@ class NodeAllocation { } } - // Update flavor of allocated docker nodes as we can change it in place for (PrioritizableNode node : nodes) { - if (node.node.allocation().isPresent()) - node.node = requestedNodes.assignRequestedFlavor(node.node); + node.node = requestedNodes.assignRequestedFlavor(node.node); + + // Set whether the node is exclusive + Allocation allocation = node.node.allocation().get(); + node.node = node.node.with(allocation.with(allocation.membership() + .with(allocation.membership().cluster().exclusive(requestedNodes.isExclusive())))); } return nodes.stream().map(n -> n.node).collect(Collectors.toList()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java index f45c10f97f1..01651293c81 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java @@ -56,17 +56,17 @@ public class NodeRepositoryProvisioner implements Provisioner { @Inject public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone) { - this(nodeRepository, flavors, zone, Clock.systemUTC(), (x, y) -> {}); + this(nodeRepository, flavors, zone, (x, y) -> {}); } - public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone, Clock clock, BiConsumer<List<Node>, String> debugRecorder) { + public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone, BiConsumer<List<Node>, String> debugRecorder) { this.nodeRepository = nodeRepository; this.capacityPolicies = new CapacityPolicies(zone, flavors); this.zone = zone; - this.preparer = new Preparer(nodeRepository, clock, zone.environment().equals(Environment.prod) + this.preparer = new Preparer(nodeRepository, zone.environment().equals(Environment.prod) ? SPARE_CAPACITY_PROD : SPARE_CAPACITY_NONPROD); - this.activator = new Activator(nodeRepository, clock); + this.activator = new Activator(nodeRepository); this.debugRecorder = debugRecorder; } @@ -95,8 +95,9 @@ public class NodeRepositoryProvisioner implements Provisioner { Optional<String> defaultFlavorOverride = nodeRepository.getDefaultFlavorOverride(application); Flavor flavor = capacityPolicies.decideFlavor(requestedCapacity, cluster, defaultFlavorOverride); log.log(LogLevel.DEBUG, () -> "Decided flavor for requested tenant nodes: " + flavor); + boolean exclusive = capacityPolicies.decideExclusivity(cluster.isExclusive()); effectiveGroups = wantedGroups > nodeCount ? nodeCount : wantedGroups; // cannot have more groups than nodes - requestedNodes = NodeSpec.from(nodeCount, flavor); + requestedNodes = NodeSpec.from(nodeCount, flavor, exclusive); } else { requestedNodes = NodeSpec.from(requestedCapacity.type()); @@ -132,4 +133,5 @@ public class NodeRepositoryProvisioner implements Provisioner { } return hosts; } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java index dc3f4a64421..b2572a781fe 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java @@ -19,6 +19,12 @@ public interface NodeSpec { /** The node type this requests */ NodeType type(); + /** + * Returns whether the physical hosts running the nodes of this application can + * also run nodes of other applications. + */ + boolean isExclusive(); + /** Returns whether the given flavor is compatible with this spec */ boolean isCompatible(Flavor flavor); @@ -47,8 +53,8 @@ public interface NodeSpec { */ Node assignRequestedFlavor(Node node); - static NodeSpec from(int nodeCount, Flavor flavor) { - return new CountNodeSpec(nodeCount, flavor); + static NodeSpec from(int nodeCount, Flavor flavor, boolean exclusive) { + return new CountNodeSpec(nodeCount, flavor, exclusive); } static NodeSpec from(NodeType type) { @@ -60,11 +66,13 @@ public interface NodeSpec { private final int count; private final Flavor requestedFlavor; + private final boolean exclusive; - public CountNodeSpec(int count, Flavor flavor) { + public CountNodeSpec(int count, Flavor flavor, boolean exclusive) { Objects.requireNonNull(flavor, "A flavor must be specified"); this.count = count; this.requestedFlavor = flavor; + this.exclusive = exclusive; } // TODO: Remove usage of this @@ -76,6 +84,9 @@ public interface NodeSpec { public int getCount() { return count; } @Override + public boolean isExclusive() { return exclusive; } + + @Override public NodeType type() { return NodeType.tenant; } @Override @@ -100,7 +111,7 @@ public interface NodeSpec { public int idealRetiredCount(int acceptedCount, int currentRetiredCount) { return acceptedCount - this.count; } @Override - public NodeSpec fraction(int divisor) { return new CountNodeSpec(count/divisor, requestedFlavor); } + public NodeSpec fraction(int divisor) { return new CountNodeSpec(count/divisor, requestedFlavor, exclusive); } @Override public Node assignRequestedFlavor(Node node) { @@ -137,6 +148,9 @@ public interface NodeSpec { public NodeType type() { return type; } @Override + public boolean isExclusive() { return false; } + + @Override public boolean isCompatible(Flavor flavor) { return true; } @Override diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java index e7a6014816e..969b1d0a687 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java @@ -9,7 +9,6 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; -import java.time.Clock; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; @@ -23,15 +22,13 @@ import java.util.Optional; class Preparer { private final NodeRepository nodeRepository; - private final Clock clock; private final GroupPreparer groupPreparer; private final int spareCount; - public Preparer(NodeRepository nodeRepository, Clock clock, int spareCount) { + public Preparer(NodeRepository nodeRepository, int spareCount) { this.nodeRepository = nodeRepository; - this.clock = clock; this.spareCount = spareCount; - this.groupPreparer = new GroupPreparer(nodeRepository, clock); + this.groupPreparer = new GroupPreparer(nodeRepository); } /** @@ -48,7 +45,7 @@ class Preparer { MutableInteger highestIndex = new MutableInteger(findHighestIndex(application, cluster)); List<Node> acceptedNodes = new ArrayList<>(); for (int groupIndex = 0; groupIndex < wantedGroups; groupIndex++) { - ClusterSpec clusterGroup = cluster.changeGroup(Optional.of(ClusterSpec.Group.from(groupIndex))); + ClusterSpec clusterGroup = cluster.with(Optional.of(ClusterSpec.Group.from(groupIndex))); List<Node> accepted = groupPreparer.prepare(application, clusterGroup, requestedNodes.fraction(wantedGroups), surplusNodes, highestIndex, spareCount); @@ -83,7 +80,7 @@ class Preparer { ClusterSpec cluster = membership.cluster(); if (cluster.group().get().index() >= wantedGroups) { ClusterSpec.Group newGroup = targetGroup.orElse(ClusterSpec.Group.from(0)); - ClusterMembership newGroupMembership = membership.changeCluster(cluster.changeGroup(Optional.of(newGroup))); + ClusterMembership newGroupMembership = membership.with(cluster.with(Optional.of(newGroup))); i.set(node.with(node.allocation().get().with(newGroupMembership))); } } @@ -122,7 +119,7 @@ class Preparer { List<Node> retired = new ArrayList<>(nodes.size()); for (Node node : nodes) { if ( ! node.allocation().get().isRemovable()) - retired.add(node.retire(Agent.application, clock.instant())); + retired.add(node.retire(Agent.application, nodeRepository.clock().instant())); } return retired; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java index 807fbfae1c9..40f76125064 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java @@ -99,4 +99,5 @@ class PrioritizableNode implements Comparable<PrioritizableNode> { if (nodePri.isNewNode) return false; return nodePri.node.state().equals(Node.State.reserved); } + } 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 b7392f3aee6..5386e3f07ff 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 @@ -111,21 +111,31 @@ public class MockNodeRepository extends NodeRepository { ApplicationId zoneApp = ApplicationId.from(TenantName.from("zoneapp"), ApplicationName.from("zoneapp"), InstanceName.from("zoneapp")); ClusterSpec zoneCluster = ClusterSpec.request(ClusterSpec.Type.container, - ClusterSpec.Id.from("node-admin"), - Version.fromString("6.42")); + ClusterSpec.Id.from("node-admin"), + Version.fromString("6.42"), + false); activate(provisioner.prepare(zoneApp, zoneCluster, Capacity.fromRequiredNodeType(NodeType.host), 1, null), zoneApp, provisioner); ApplicationId app1 = ApplicationId.from(TenantName.from("tenant1"), ApplicationName.from("application1"), InstanceName.from("instance1")); - ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("id1"), Version.fromString("6.42")); + ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.container, + ClusterSpec.Id.from("id1"), + Version.fromString("6.42"), + false); provisioner.prepare(app1, cluster1, Capacity.fromNodeCount(2), 1, null); ApplicationId app2 = ApplicationId.from(TenantName.from("tenant2"), ApplicationName.from("application2"), InstanceName.from("instance2")); - ClusterSpec cluster2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("id2"), Version.fromString("6.42")); + ClusterSpec cluster2 = ClusterSpec.request(ClusterSpec.Type.content, + ClusterSpec.Id.from("id2"), + Version.fromString("6.42"), + false); activate(provisioner.prepare(app2, cluster2, Capacity.fromNodeCount(2), 1, null), app2, provisioner); ApplicationId app3 = ApplicationId.from(TenantName.from("tenant3"), ApplicationName.from("application3"), InstanceName.from("instance3")); - ClusterSpec cluster3 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("id3"), Version.fromString("6.42")); - activate(provisioner.prepare(app3, cluster3, Capacity.fromNodeCount(2, "docker"), 1, null), app3, provisioner); + ClusterSpec cluster3 = ClusterSpec.request(ClusterSpec.Type.content, + ClusterSpec.Id.from("id3"), + Version.fromString("6.42"), + false); + activate(provisioner.prepare(app3, cluster3, Capacity.fromNodeCount(2, Optional.of("docker"), false), 1, null), app3, provisioner); } private void activate(List<HostSpec> hosts, ApplicationId application, NodeRepositoryProvisioner provisioner) { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java index 7c064c5faf1..18881c11a64 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java @@ -218,7 +218,7 @@ public class FailedExpirerTest { this.nodeRepository = new NodeRepository(nodeFlavors, curator, clock, zone, new MockNameResolver().mockAnyLookup(), new DockerImage("docker-image")); - this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, Zone.defaultZone(), clock, + this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, Zone.defaultZone(), (x, y) -> {}); this.expirer = new FailedExpirer(nodeRepository, zone, clock, Duration.ofMinutes(30), new JobControl(nodeRepository.database())); @@ -289,9 +289,11 @@ public class FailedExpirerTest { Set<HostSpec> hosts = Stream.of(hostname) .map(h -> new HostSpec(h, Optional.empty())) .collect(Collectors.toSet()); - ClusterSpec clusterSpec = ClusterSpec.request(clusterType, ClusterSpec.Id.from("test"), - Version.fromString("6.42")); - provisioner.prepare(applicationId, clusterSpec, Capacity.fromNodeCount(hostname.length, flavor.name()), + ClusterSpec clusterSpec = ClusterSpec.request(clusterType, + ClusterSpec.Id.from("test"), + Version.fromString("6.42"), + false); + provisioner.prepare(applicationId, clusterSpec, Capacity.fromNodeCount(hostname.length, Optional.of(flavor.name()), false), 1, null); NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(curator)); provisioner.activate(transaction, applicationId, hosts); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java index 3d5de590bf4..f870257c111 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java @@ -51,7 +51,7 @@ public class InactiveAndFailedExpirerTest { List<Node> nodes = tester.makeReadyNodes(2, "default"); // Allocate then deallocate 2 nodes - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); tester.prepare(applicationId, cluster, Capacity.fromNodeCount(2), 1); tester.activate(applicationId, ProvisioningTester.toHostSpecs(nodes)); assertEquals(2, tester.getNodes(applicationId, Node.State.active).size()); @@ -91,7 +91,8 @@ public class InactiveAndFailedExpirerTest { // Allocate and deallocate a single node ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), - Version.fromString("6.42")); + Version.fromString("6.42"), + false); tester.prepare(applicationId, cluster, Capacity.fromNodeCount(2), 1); tester.activate(applicationId, ProvisioningTester.toHostSpecs(nodes)); assertEquals(2, tester.getNodes(applicationId, Node.State.active).size()); @@ -118,7 +119,7 @@ public class InactiveAndFailedExpirerTest { public void node_that_wants_to_retire_is_moved_to_parked() throws OrchestrationException { ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east"))); ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), - Version.fromString("6.42")); + Version.fromString("6.42"), false); tester.makeReadyNodes(5, "default"); // Allocate two nodes @@ -144,7 +145,9 @@ public class InactiveAndFailedExpirerTest { Collections.singletonMap( applicationId, new MockDeployer.ApplicationContext(applicationId, cluster, - Capacity.fromNodeCount(2, Optional.of("default")), + Capacity.fromNodeCount(2, + Optional.of("default"), + false), 1) ) ); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java index a03b06fda13..f7592bd7422 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java @@ -99,8 +99,8 @@ public class NodeFailTester { tester.createHostNodes(3); // Create applications - ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.42")); - ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); + ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); int wantedNodesApp1 = 5; int wantedNodesApp2 = 7; tester.activate(app1, clusterApp1, wantedNodesApp1); @@ -109,8 +109,8 @@ public class NodeFailTester { assertEquals(wantedNodesApp2, tester.nodeRepository.getNodes(app2, Node.State.active).size()); Map<ApplicationId, MockDeployer.ApplicationContext> apps = new HashMap<>(); - apps.put(app1, new MockDeployer.ApplicationContext(app1, clusterApp1, Capacity.fromNodeCount(wantedNodesApp1, Optional.of("default")), 1)); - apps.put(app2, new MockDeployer.ApplicationContext(app2, clusterApp2, Capacity.fromNodeCount(wantedNodesApp2, Optional.of("default")), 1)); + apps.put(app1, new MockDeployer.ApplicationContext(app1, clusterApp1, Capacity.fromNodeCount(wantedNodesApp1, Optional.of("default"), false), 1)); + apps.put(app2, new MockDeployer.ApplicationContext(app2, clusterApp2, Capacity.fromNodeCount(wantedNodesApp2, Optional.of("default"), false), 1)); tester.deployer = new MockDeployer(tester.provisioner, apps); tester.serviceMonitor = new ServiceMonitorStub(apps, tester.nodeRepository); tester.metric = new MetricsReporterTest.TestMetric(); @@ -129,12 +129,12 @@ public class NodeFailTester { } // Create applications - ClusterSpec clusterNodeAdminApp = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin"), Version.fromString("6.42")); - ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.75.0")); - ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.75.0")); + ClusterSpec clusterNodeAdminApp = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin"), Version.fromString("6.42"), false); + ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.75.0"), false); + ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.75.0"), false); Capacity allHosts = Capacity.fromRequiredNodeType(NodeType.host); - Capacity capacity1 = Capacity.fromNodeCount(3, Optional.of("docker")); - Capacity capacity2 = Capacity.fromNodeCount(5, Optional.of("docker")); + Capacity capacity1 = Capacity.fromNodeCount(3, Optional.of("docker"), false); + Capacity capacity2 = Capacity.fromNodeCount(5, Optional.of("docker"), false); tester.activate(nodeAdminApp, clusterNodeAdminApp, allHosts); tester.activate(app1, clusterApp1, capacity1); tester.activate(app2, clusterApp2, capacity2); @@ -163,7 +163,8 @@ public class NodeFailTester { Capacity allProxies = Capacity.fromRequiredNodeType(NodeType.proxy); ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), - Version.fromString("6.42")); + Version.fromString("6.42"), + false); tester.activate(app1, clusterApp1, allProxies); assertEquals(16, tester.nodeRepository.getNodes(NodeType.proxy, Node.State.active).size()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTester.java index b05f4178419..4e9a8f4b608 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTester.java @@ -111,8 +111,8 @@ public class NodeRetirerTester { for (int i = 0; i < flavorIds.length; i++) { Flavor flavor = flavors.get(flavorIds[i]); - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("cluster-" + i), Version.fromString("6.99")); - Capacity capacity = Capacity.fromNodeCount(numNodes[i], flavor.name()); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("cluster-" + i), Version.fromString("6.99"), false); + Capacity capacity = Capacity.fromNodeCount(numNodes[i], Optional.of(flavor.name()), false); // If the number of node the app wants is divisible by 2, make it into 2 groups, otherwise as 1 int numGroups = numNodes[i] % 2 == 0 ? 2 : 1; clusterContexts.add(new MockDeployer.ClusterContext(applicationId, cluster, capacity, numGroups)); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java index b6afc74b81d..f7abd823c77 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java @@ -112,8 +112,8 @@ public class OperatorChangeApplicationMaintainerTest { final ApplicationId app1 = ApplicationId.from(TenantName.from("foo1"), ApplicationName.from("bar"), InstanceName.from("fuz")); final ApplicationId app2 = ApplicationId.from(TenantName.from("foo2"), ApplicationName.from("bar"), InstanceName.from("fuz")); - final ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.42")); - final ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + final ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); + final ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); final int wantedNodesApp1 = 5; final int wantedNodesApp2 = 7; MockDeployer deployer; // created on activation @@ -132,9 +132,9 @@ public class OperatorChangeApplicationMaintainerTest { Map<ApplicationId, MockDeployer.ApplicationContext> apps = new HashMap<>(); apps.put(app1, new MockDeployer.ApplicationContext(app1, clusterApp1, - Capacity.fromNodeCount(wantedNodesApp1, Optional.of("default")), 1)); + Capacity.fromNodeCount(wantedNodesApp1, Optional.of("default"), false), 1)); apps.put(app2, new MockDeployer.ApplicationContext(app2, clusterApp2, - Capacity.fromNodeCount(wantedNodesApp2, Optional.of("default")), 1)); + Capacity.fromNodeCount(wantedNodesApp2, Optional.of("default"), false), 1)); this.deployer = new MockDeployer(provisioner, apps); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java index f6a040a1d7a..fe87f34a3a5 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java @@ -154,8 +154,8 @@ public class PeriodicApplicationMaintainerTest { final ApplicationId app1 = ApplicationId.from(TenantName.from("foo1"), ApplicationName.from("bar"), InstanceName.from("fuz")); final ApplicationId app2 = ApplicationId.from(TenantName.from("foo2"), ApplicationName.from("bar"), InstanceName.from("fuz")); - final ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.42")); - final ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + final ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); + final ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); final int wantedNodesApp1 = 5; final int wantedNodesApp2 = 7; @@ -192,9 +192,9 @@ public class PeriodicApplicationMaintainerTest { void runApplicationMaintainer(Optional<List<Node>> overriddenNodesNeedingMaintenance) { Map<ApplicationId, MockDeployer.ApplicationContext> apps = new HashMap<>(); apps.put(app1, new MockDeployer.ApplicationContext(app1, clusterApp1, - Capacity.fromNodeCount(wantedNodesApp1, Optional.of("default")), 1)); + Capacity.fromNodeCount(wantedNodesApp1, Optional.of("default"), false), 1)); apps.put(app2, new MockDeployer.ApplicationContext(app2, clusterApp2, - Capacity.fromNodeCount(wantedNodesApp2, Optional.of("default")), 1)); + Capacity.fromNodeCount(wantedNodesApp2, Optional.of("default"), false), 1)); MockDeployer deployer = new MockDeployer(provisioner, apps); new TestablePeriodicApplicationMaintainer(deployer, nodeRepository, Duration.ofMinutes(30), overriddenNodesNeedingMaintenance).run(); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java index 3766bbba602..6091fb9a3b5 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java @@ -43,7 +43,7 @@ public class ReservationExpirerTest { NodeRepository nodeRepository = new NodeRepository(flavors, curator, clock, Zone.defaultZone(), new MockNameResolver().mockAnyLookup(), new DockerImage("docker-registry.domain.tld:8080/dist/vespa")); - NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, flavors, Zone.defaultZone(), clock, (x,y) -> {}); + NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, flavors, Zone.defaultZone(), (x,y) -> {}); List<Node> nodes = new ArrayList<>(2); nodes.add(nodeRepository.createNode(UUID.randomUUID().toString(), UUID.randomUUID().toString(), Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.tenant)); @@ -56,7 +56,7 @@ public class ReservationExpirerTest { assertEquals(2, nodeRepository.getNodes(NodeType.tenant, Node.State.dirty).size()); nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName()); ApplicationId applicationId = new ApplicationId.Builder().tenant("foo").applicationName("bar").instanceName("fuz").build(); - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); provisioner.prepare(applicationId, cluster, Capacity.fromNodeCount(2), 1, null); assertEquals(2, nodeRepository.getNodes(NodeType.tenant, Node.State.reserved).size()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java index 0cf30fce071..f49185b04ec 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java @@ -80,7 +80,7 @@ public class RetiredExpirerTest { // Allocate content cluster of sizes 7 -> 2 -> 3: // Should end up with 3 nodes in the cluster (one previously retired), and 4 retired - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); int wantedNodes; activate(applicationId, cluster, wantedNodes=7, 1, provisioner); activate(applicationId, cluster, wantedNodes=2, 1, provisioner); @@ -92,7 +92,7 @@ public class RetiredExpirerTest { clock.advance(Duration.ofHours(30)); // Retire period spent MockDeployer deployer = new MockDeployer(provisioner, - Collections.singletonMap(applicationId, new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromNodeCount(wantedNodes, Optional.of("default")), 1))); + Collections.singletonMap(applicationId, new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromNodeCount(wantedNodes, Optional.of("default"), false), 1))); createRetiredExpirer(deployer).run(); assertEquals(3, nodeRepository.getNodes(applicationId, Node.State.active).size()); assertEquals(4, nodeRepository.getNodes(applicationId, Node.State.inactive).size()); @@ -110,7 +110,7 @@ public class RetiredExpirerTest { ApplicationId applicationId = ApplicationId.from(TenantName.from("foo"), ApplicationName.from("bar"), InstanceName.from("fuz")); - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); activate(applicationId, cluster, 8, 8, provisioner); activate(applicationId, cluster, 2, 2, provisioner); assertEquals(8, nodeRepository.getNodes(applicationId, Node.State.active).size()); @@ -120,7 +120,7 @@ public class RetiredExpirerTest { clock.advance(Duration.ofHours(30)); // Retire period spent MockDeployer deployer = new MockDeployer(provisioner, - Collections.singletonMap(applicationId, new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromNodeCount(2, Optional.of("default")), 1))); + Collections.singletonMap(applicationId, new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromNodeCount(2, Optional.of("default"), false), 1))); createRetiredExpirer(deployer).run(); assertEquals(2, nodeRepository.getNodes(applicationId, Node.State.active).size()); assertEquals(6, nodeRepository.getNodes(applicationId, Node.State.inactive).size()); @@ -140,7 +140,7 @@ public class RetiredExpirerTest { // Allocate content cluster of sizes 7 -> 2 -> 3: // Should end up with 3 nodes in the cluster (one previously retired), and 4 retired - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); int wantedNodes; activate(applicationId, cluster, wantedNodes=7, 1, provisioner); activate(applicationId, cluster, wantedNodes=2, 1, provisioner); @@ -153,7 +153,7 @@ public class RetiredExpirerTest { new MockDeployer(provisioner, Collections.singletonMap( applicationId, - new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromNodeCount(wantedNodes, Optional.of("default")), 1))); + new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromNodeCount(wantedNodes, Optional.of("default"), false), 1))); // Allow the 1st and 3rd retired nodes permission to inactivate 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 b7eb1b480d9..066227e007f 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 @@ -188,7 +188,10 @@ public class MetricsReporterTest { private Optional<Allocation> allocation(Optional<String> tenant) { if (tenant.isPresent()) { - Allocation allocation = new Allocation(app(tenant.get()), ClusterMembership.from("container/id1/3", new Version()), Generation.inital(), false); + Allocation allocation = new Allocation(app(tenant.get()), + ClusterMembership.from("container/id1/0/3", new Version()), + Generation.inital(), + false); return Optional.of(allocation); } return Optional.empty(); 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 f91b4863eeb..fe216c49888 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 @@ -129,7 +129,7 @@ public class SerializationTest { " \"applicationId\" : \"myApplication\",\n" + " \"tenantId\" : \"myTenant\",\n" + " \"instanceId\" : \"myInstance\",\n" + - " \"serviceId\" : \"content/myId/0\",\n" + + " \"serviceId\" : \"content/myId/0/0\",\n" + " \"restartGeneration\" : 3,\n" + " \"currentRestartGeneration\" : 4,\n" + " \"removable\" : true,\n" + @@ -162,7 +162,7 @@ public class SerializationTest { node = node.allocate(ApplicationId.from(TenantName.from("myTenant"), ApplicationName.from("myApplication"), InstanceName.from("myInstance")), - ClusterMembership.from("content/myId/0", Vtag.currentVersion), + ClusterMembership.from("content/myId/0/0", Vtag.currentVersion), clock.instant()); assertEquals(1, node.history().events().size()); clock.advance(Duration.ofMinutes(2)); @@ -332,7 +332,7 @@ public class SerializationTest { " \"hostname\" : \"myHostname\",\n" + " \"ipAddresses\" : [\"127.0.0.1\"],\n" + " \"instance\": {\n" + - " \"serviceId\": \"content/myId/0\",\n" + + " \"serviceId\": \"content/myId/0/0\",\n" + " \"wantedVespaVersion\": \"6.42.2\"\n" + " }\n" + "}"; diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java index 492bcaa5462..cb780f08cd9 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java @@ -222,7 +222,7 @@ public class AclProvisioningTest { private List<Node> allocateNodes(Capacity capacity, ApplicationId applicationId) { ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), - Version.fromString("6.42")); + Version.fromString("6.42"), false); List<HostSpec> prepared = tester.prepare(applicationId, cluster, capacity, 1); tester.activate(applicationId, new HashSet<>(prepared)); return tester.getNodes(applicationId, Node.State.active).asList(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java index dc30f0ed1a8..943cb60bf04 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationSimulator.java @@ -34,7 +34,7 @@ import java.util.Set; public class AllocationSimulator { private AllocationVisualizer visualizer; - private NodeList nodes = new NodeList(new ArrayList<>()); + private NodeList nodes; private NodeFlavors flavors; private AllocationSimulator(AllocationVisualizer visualizer) { @@ -94,7 +94,10 @@ public class AllocationSimulator { private Optional<Allocation> allocation(Optional<String> tenant) { if (tenant.isPresent()) { - Allocation allocation = new Allocation(app(tenant.get()), ClusterMembership.from("container/id1/3", new Version()), Generation.inital(), false); + Allocation allocation = new Allocation(app(tenant.get()), + ClusterMembership.from("container/id1/3", new Version()), + Generation.inital(), + false); return Optional.of(allocation); } return Optional.empty(); @@ -108,35 +111,31 @@ public class AllocationSimulator { } private ClusterSpec cluster() { - return ClusterSpec.from(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), ClusterSpec.Group.from(1), Version.fromString("6.41")); + return ClusterSpec.from(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), ClusterSpec.Group.from(1), Version.fromString("6.41"), false); } /* ------------ Methods to add events to the system ----------------*/ public void addCluster(String task, int count, Flavor flavor, String id) { - NodeSpec.CountNodeSpec nodeSpec = new NodeSpec.CountNodeSpec(count, flavor); - NodeAllocation allocation = new NodeAllocation(app(id), cluster(), nodeSpec, new MutableInteger(0), Clock.systemUTC()); - - List<Node> accepted = new ArrayList<>(); //TODO adpot the new allocation algoritm - - accepted.addAll(nodes.asList()); - nodes = new NodeList(accepted); + // TODO: Implement + NodeSpec.CountNodeSpec nodeSpec = new NodeSpec.CountNodeSpec(count, flavor, false); + nodes = new NodeList(nodes.asList()); } public static void main(String[] args) { - AllocationVisualizer visualisator = new AllocationVisualizer(); + AllocationVisualizer visualizer = new AllocationVisualizer(); javax.swing.SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame("Allocation Simulator"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setContentPane(visualisator); + frame.setContentPane(visualizer); frame.pack(); frame.setVisible(true); }); - AllocationSimulator simulator = new AllocationSimulator(visualisator); + AllocationSimulator simulator = new AllocationSimulator(visualizer); simulator.addCluster("App1 : 3 * d-1 nodes", 3, simulator.flavors.getFlavorOrThrow("d-1"), "App1"); simulator.addCluster("App2 : 2 * d-2 nodes", 2, simulator.flavors.getFlavorOrThrow("d-2"), "App2"); simulator.addCluster("App3 : 3 * d-2 nodes", 3, simulator.flavors.getFlavorOrThrow("d-2"), "App3"); @@ -144,4 +143,5 @@ public class AllocationSimulator { simulator.addCluster("App5 : 3 * d-3 nodes", 3, simulator.flavors.getFlavorOrThrow("d-3"), "App5"); simulator.addCluster("App6 : 5 * d-2 nodes", 5, simulator.flavors.getFlavorOrThrow("d-2"), "App6"); } + } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationVisualizer.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationVisualizer.java index ff2591a02f4..24011bd4b49 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationVisualizer.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AllocationVisualizer.java @@ -19,6 +19,7 @@ import java.util.List; * @author smorgrav */ public class AllocationVisualizer extends JPanel { + // Container box's width and height private static final int BOX_WIDTH = 1024; private static final int BOX_HEIGHT = 480; @@ -141,4 +142,5 @@ public class AllocationVisualizer extends JPanel { } } } + } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java index a6760ef37ce..a85aca9d647 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.provisioning; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostSpec; @@ -14,8 +15,15 @@ import org.junit.Test; import java.util.HashSet; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Tests deployment to docker images which share the same physical host. @@ -31,14 +39,13 @@ public class DockerProvisioningTest { ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east"))); ApplicationId application1 = tester.makeApplicationId(); - for (int i = 1; i < 10; i++) { + for (int i = 1; i < 10; i++) tester.makeReadyDockerNodes(1, dockerFlavor, "dockerHost" + i); - } Version wantedVespaVersion = Version.fromString("6.39"); int nodeCount = 7; List<HostSpec> hosts = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), wantedVespaVersion), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), wantedVespaVersion, false), nodeCount, 1, dockerFlavor); tester.activate(application1, new HashSet<>(hosts)); @@ -49,15 +56,126 @@ public class DockerProvisioningTest { // Upgrade Vespa version on nodes Version upgradedWantedVespaVersion = Version.fromString("6.40"); List<HostSpec> upgradedHosts = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), upgradedWantedVespaVersion), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), upgradedWantedVespaVersion, false), nodeCount, 1, dockerFlavor); tester.activate(application1, new HashSet<>(upgradedHosts)); - final NodeList upgradedNodes = tester.getNodes(application1, Node.State.active); + NodeList upgradedNodes = tester.getNodes(application1, Node.State.active); assertEquals(nodeCount, upgradedNodes.size()); assertEquals(dockerFlavor, upgradedNodes.asList().get(0).flavor().canonicalName()); assertEquals(hosts, upgradedHosts); } + /** Exclusive app first, then non-exclusive: Should give the same result as below */ + @Test + public void docker_application_deployment_with_exclusive_app_first() { + ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east"))); + for (int i = 1; i <= 4; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host1"); + for (int i = 5; i <= 8; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host2"); + for (int i = 9; i <= 12; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host3"); + for (int i = 13; i <= 16; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host4"); + + ApplicationId application1 = tester.makeApplicationId(); + prepareAndActivate(application1, 2, true, tester); + assertEquals(setOf("host1", "host2"), hostsOf(tester.getNodes(application1, Node.State.active))); + + ApplicationId application2 = tester.makeApplicationId(); + prepareAndActivate(application2, 2, false, tester); + assertEquals("Application is assigned to separate hosts", + setOf("host3", "host4"), hostsOf(tester.getNodes(application2, Node.State.active))); + } + + /** Non-exclusive app first, then an exclusive: Should give the same result as above */ + @Test + public void docker_application_deployment_with_exclusive_app_last() { + ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east"))); + for (int i = 1; i <= 4; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host1"); + for (int i = 5; i <= 8; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host2"); + for (int i = 9; i <= 12; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host3"); + for (int i = 13; i <= 16; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host4"); + + ApplicationId application1 = tester.makeApplicationId(); + prepareAndActivate(application1, 2, false, tester); + assertEquals(setOf("host1", "host2"), hostsOf(tester.getNodes(application1, Node.State.active))); + + ApplicationId application2 = tester.makeApplicationId(); + prepareAndActivate(application2, 2, true, tester); + assertEquals("Application is assigned to separate hosts", + setOf("host3", "host4"), hostsOf(tester.getNodes(application2, Node.State.active))); + } + + /** Test making an application exclusive */ + @Test + public void docker_application_deployment_change_to_exclusive_and_back() { + ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east"))); + for (int i = 1; i <= 4; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host1"); + for (int i = 5; i <= 8; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host2"); + for (int i = 9; i <= 12; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host3"); + for (int i = 13; i <= 16; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host4"); + + ApplicationId application1 = tester.makeApplicationId(); + prepareAndActivate(application1, 2, false, tester); + for (Node node : tester.getNodes(application1, Node.State.active).asList()) + assertFalse(node.allocation().get().membership().cluster().isExclusive()); + + prepareAndActivate(application1, 2, true, tester); + assertEquals(setOf("host1", "host2"), hostsOf(tester.getNodes(application1, Node.State.active))); + for (Node node : tester.getNodes(application1, Node.State.active).asList()) + assertTrue(node.allocation().get().membership().cluster().isExclusive()); + + prepareAndActivate(application1, 2, false, tester); + assertEquals(setOf("host1", "host2"), hostsOf(tester.getNodes(application1, Node.State.active))); + for (Node node : tester.getNodes(application1, Node.State.active).asList()) + assertFalse(node.allocation().get().membership().cluster().isExclusive()); + } + + /** Non-exclusive app first, then an exclusive: Should give the same result as above */ + @Test + public void docker_application_deployment_with_exclusive_app_causing_allocation_failure() { + ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east"))); + for (int i = 1; i <= 4; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host1"); + for (int i = 5; i <= 8; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host2"); + for (int i = 9; i <= 12; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host3"); + for (int i = 13; i <= 16; i++) + tester.makeReadyVirtualNode(i, dockerFlavor, "host4"); + + ApplicationId application1 = tester.makeApplicationId(); + prepareAndActivate(application1, 2, true, tester); + assertEquals(setOf("host1", "host2"), hostsOf(tester.getNodes(application1, Node.State.active))); + + try { + ApplicationId application2 = tester.makeApplicationId(); + prepareAndActivate(application2, 3, false, tester); + fail("Expected allocation failure"); + } + catch (Exception e) { + assertEquals("No room for 3 nodes as 2 of 4 hosts are exclusive", + "Could not satisfy request for 3 nodes of flavor 'dockerSmall' for container cluster 'myContainer' group 0 6.39: Not enough nodes available due to host exclusivity constraints.", + e.getMessage()); + } + + // Adding 3 nodes of another cluster for the same application works: + Set<HostSpec> hosts = new HashSet<>(tester.prepare(application1, + ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer2"), Version.fromString("6.39"), false), + Capacity.fromNodeCount(3, Optional.of(dockerFlavor), false), + 1)); + tester.activate(application1, hosts); + } + // In dev, test and staging you get nodes with default flavor, but we should get specified flavor for docker nodes @Test public void get_specified_flavor_not_default_flavor_for_docker() { @@ -65,7 +183,7 @@ public class DockerProvisioningTest { ApplicationId application1 = tester.makeApplicationId(); tester.makeReadyDockerNodes(1, dockerFlavor, "dockerHost"); - List<HostSpec> hosts = tester.prepare(application1, ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.42")), 1, 1, dockerFlavor); + List<HostSpec> hosts = tester.prepare(application1, ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.42"), false), 1, 1, dockerFlavor); tester.activate(application1, new HashSet<>(hosts)); NodeList nodes = tester.getNodes(application1, Node.State.active); @@ -73,4 +191,20 @@ public class DockerProvisioningTest { assertEquals(dockerFlavor, nodes.asList().get(0).flavor().canonicalName()); } + private Set setOf(String ... strings) { + return Stream.of(strings).collect(Collectors.toSet()); + } + + private Set hostsOf(NodeList nodes) { + return nodes.asList().stream().map(Node::parentHostname).map(Optional::get).collect(Collectors.toSet()); + } + + private void prepareAndActivate(ApplicationId application, int nodeCount, boolean exclusive, ProvisioningTester tester) { + Set<HostSpec> hosts = new HashSet<>(tester.prepare(application, + ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer"), Version.fromString("6.39"), exclusive), + Capacity.fromNodeCount(nodeCount, Optional.of(dockerFlavor), false), + 1)); + tester.activate(application, hosts); + } + } 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 9d035304ab7..b6f262775d7 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 @@ -68,13 +68,13 @@ public class DynamicDockerProvisioningTest { // Application 1 ApplicationId application1 = makeApplicationId("t1", "a1"); - ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); addAndAssignNode(application1, "1a", dockerHosts.get(2).hostname(), flavor, 0, tester); addAndAssignNode(application1, "1b", dockerHosts.get(3).hostname(), flavor, 1, tester); // Application 2 ApplicationId application2 = makeApplicationId("t2", "a2"); - ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); addAndAssignNode(application2, "2a", dockerHosts.get(2).hostname(), flavor, 0, tester); addAndAssignNode(application2, "2b", dockerHosts.get(3).hostname(), flavor, 1, tester); @@ -115,13 +115,13 @@ public class DynamicDockerProvisioningTest { // Application 1 ApplicationId application1 = makeApplicationId("t1", "a1"); - ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); addAndAssignNode(application1, "1a", dockerHosts.get(0).hostname(), flavor, 0, tester); addAndAssignNode(application1, "1b", dockerHosts.get(1).hostname(), flavor, 1, tester); // Application 2 ApplicationId application2 = makeApplicationId("t2", "a2"); - ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); addAndAssignNode(application2, "2a", dockerHosts.get(2).hostname(), flavor, 0, tester); addAndAssignNode(application2, "2b", dockerHosts.get(3).hostname(), flavor, 1, tester); @@ -162,7 +162,7 @@ public class DynamicDockerProvisioningTest { // Application 1 ApplicationId application1 = makeApplicationId("t1", "1"); - ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); String hostParent2 = dockerHosts.get(2).hostname(); String hostParent3 = dockerHosts.get(3).hostname(); addAndAssignNode(application1, "1a", hostParent2, flavorD2, 0, tester); @@ -170,7 +170,7 @@ public class DynamicDockerProvisioningTest { // Application 2 ApplicationId application2 = makeApplicationId("t2", "2"); - ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); addAndAssignNode(application2, "2a", hostParent2, flavorD1, 0, tester); addAndAssignNode(application2, "2b", hostParent3, flavorD1, 1, tester); @@ -217,7 +217,7 @@ public class DynamicDockerProvisioningTest { // Application 1 ApplicationId application1 = makeApplicationId("t1", "1"); - ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); String hostParent2 = dockerHosts.get(2).hostname(); String hostParent3 = dockerHosts.get(3).hostname(); addAndAssignNode(application1, "1a", hostParent2, flavorD2, 0, tester); @@ -225,7 +225,7 @@ public class DynamicDockerProvisioningTest { // Application 2 ApplicationId application2 = makeApplicationId("t2", "2"); - ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); addAndAssignNode(application2, "2a", hostParent2, flavorD1, 0, tester); addAndAssignNode(application2, "2b", hostParent3, flavorD1, 1, tester); @@ -284,17 +284,17 @@ public class DynamicDockerProvisioningTest { // Application 1 ApplicationId application1 = makeApplicationId("t1", "a1"); - ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); deployapp(application1, clusterSpec1, flavor, tester, 3); // Application 2 ApplicationId application2 = makeApplicationId("t2", "a2"); - ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); deployapp(application2, clusterSpec2, flavor, tester, 2); // Application 3 ApplicationId application3 = makeApplicationId("t3", "a3"); - ClusterSpec clusterSpec3 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec3 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); deployapp(application3, clusterSpec3, flavor, tester, 2); // App 2 and 3 should have been allocated to the same nodes - fail on of the parent hosts from there @@ -342,7 +342,7 @@ public class DynamicDockerProvisioningTest { // Application 1 ApplicationId application1 = makeApplicationId("t1", "a1"); - ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")); + ClusterSpec clusterSpec1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false); addAndAssignNode(application1, "1a", dockerHosts.get(0).hostname(), flavor, 0, tester); addAndAssignNode(application1, "1b", dockerHosts.get(1).hostname(), flavor, 1, tester); @@ -369,7 +369,7 @@ public class DynamicDockerProvisioningTest { //Deploy an application of 6 nodes of 3 nodes in each cluster. We only have 3 docker hosts available ApplicationId application1 = tester.makeApplicationId(); tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false), 6, 2, flavor.canonicalName()); fail("Two groups have been allocated to the same parent host"); @@ -397,7 +397,7 @@ public class DynamicDockerProvisioningTest { // Deploy initial state (can max deploy 3 nodes due to redundancy requirements) List<HostSpec> hosts = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false), 3, 1, flavor.canonicalName()); tester.activate(application1, ImmutableSet.copyOf(hosts)); @@ -409,7 +409,7 @@ public class DynamicDockerProvisioningTest { try { hosts = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false), 4, 1, flavor.canonicalName()); fail("Was able to deploy with 4 nodes, should not be able to use spare capacity"); } catch (OutOfCapacityException e) { @@ -417,7 +417,7 @@ public class DynamicDockerProvisioningTest { tester.fail(hosts.get(0)); hosts = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false), 3, 1, flavor.canonicalName()); tester.activate(application1, ImmutableSet.copyOf(hosts)); @@ -435,7 +435,7 @@ public class DynamicDockerProvisioningTest { ApplicationId application1 = tester.makeApplicationId(); List<HostSpec> hosts = tester.prepare(application1, - ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")), + ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false), 3, 1, flavor.canonicalName()); tester.activate(application1, ImmutableSet.copyOf(hosts)); @@ -452,7 +452,7 @@ public class DynamicDockerProvisioningTest { ApplicationId application = tester.makeApplicationId(); Flavor flavor = tester.nodeRepository().getAvailableFlavors().getFlavorOrThrow("d-3"); - tester.prepare(application, ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")), + tester.prepare(application, ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false), 2, 1, flavor.canonicalName()); } @@ -467,7 +467,7 @@ public class DynamicDockerProvisioningTest { private Node addAndAssignNode(ApplicationId id, String hostname, String parentHostname, Flavor flavor, int index, ProvisioningTester tester) { Node node1a = Node.create("open1", Collections.singleton("127.0.0.100"), new HashSet<>(), hostname, Optional.of(parentHostname), flavor, NodeType.tenant); - ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100")).changeGroup(Optional.of(ClusterSpec.Group.from(0))); + ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.100"), false).with(Optional.of(ClusterSpec.Group.from(0))); ClusterMembership clusterMembership1 = ClusterMembership.from(clusterSpec, index); Node node1aAllocation = node1a.allocate(id, clusterMembership1, Instant.now()); @@ -523,7 +523,8 @@ public class DynamicDockerProvisioningTest { List<HostSpec> list = tester.prepare(applicationId, ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin"), - Version.fromString("6.42")), + Version.fromString("6.42"), + false), Capacity.fromRequiredNodeType(NodeType.host), 1); tester.activate(applicationId, ImmutableSet.copyOf(list)); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java index 9587fedb7b2..071c7549884 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java @@ -100,8 +100,8 @@ public class MultigroupProvisioningTest { tester.makeReadyNodes(10, "small"); - deploy(application1, Capacity.fromRequiredNodeCount(1, "small"), 1, tester); - deploy(application1, Capacity.fromRequiredNodeCount(2, "small"), 2, tester); + deploy(application1, Capacity.fromNodeCount(1, Optional.of("small"), true), 1, tester); + deploy(application1, Capacity.fromNodeCount(2, Optional.of("small"), true), 2, tester); } @Test @@ -113,8 +113,8 @@ public class MultigroupProvisioningTest { tester.makeReadyNodes(10, "small"); tester.makeReadyNodes(10, "large"); - deploy(application1, Capacity.fromRequiredNodeCount(1, "small"), 1, tester); - deploy(application1, Capacity.fromRequiredNodeCount(2, "large"), 2, tester); + deploy(application1, Capacity.fromNodeCount(1, Optional.of("small"), true), 1, tester); + deploy(application1, Capacity.fromNodeCount(2, Optional.of("large"), true), 2, tester); } @Test @@ -135,7 +135,7 @@ public class MultigroupProvisioningTest { new MockDeployer(tester.provisioner(), Collections.singletonMap(application1, new MockDeployer.ApplicationContext(application1, cluster(), - Capacity.fromNodeCount(8, Optional.of("large")), 1))); + Capacity.fromNodeCount(8, Optional.of("large"), false), 1))); new RetiredExpirer(tester.nodeRepository(), tester.orchestrator(), deployer, tester.clock(), Duration.ofDays(30), Duration.ofHours(12), new JobControl(tester.nodeRepository().database())).run(); @@ -144,10 +144,10 @@ public class MultigroupProvisioningTest { } private void deploy(ApplicationId application, int nodeCount, int groupCount, String flavor, ProvisioningTester tester) { - deploy(application, Capacity.fromNodeCount(nodeCount, Optional.of(flavor)), groupCount, tester); + deploy(application, Capacity.fromNodeCount(nodeCount, Optional.of(flavor), false), groupCount, tester); } private void deploy(ApplicationId application, int nodeCount, int groupCount, ProvisioningTester tester) { - deploy(application, Capacity.fromNodeCount(nodeCount, "default"), groupCount, tester); + deploy(application, Capacity.fromNodeCount(nodeCount, Optional.of("default"), false), groupCount, tester); } private void deploy(ApplicationId application, Capacity capacity, int wantedGroups, ProvisioningTester tester) { int nodeCount = capacity.nodeCount(); @@ -199,7 +199,7 @@ public class MultigroupProvisioningTest { assertEquals("No additional groups are retained containing retired nodes", wantedGroups, allGroups.size()); } - private ClusterSpec cluster() { return ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); } + private ClusterSpec cluster() { return ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); } private Set<HostSpec> prepare(ApplicationId application, Capacity capacity, int groupCount, ProvisioningTester tester) { return new HashSet<>(tester.prepare(application, cluster(), capacity, groupCount)); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizerTest.java index 47ead201509..6e07585afd4 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizerTest.java @@ -49,7 +49,7 @@ public class NodePrioritizerTest { Assert.assertTrue(NodePrioritizer.isPreferredNodeToBeReloacted(nodes, c, parent)); // Unallocated over allocated - ClusterSpec spec = ClusterSpec.from(ClusterSpec.Type.content, ClusterSpec.Id.from("mycluster"), ClusterSpec.Group.from(0), Version.fromString("6.142.22")); + ClusterSpec spec = ClusterSpec.from(ClusterSpec.Type.content, ClusterSpec.Id.from("mycluster"), ClusterSpec.Group.from(0), Version.fromString("6.142.22"), false); c = c.allocate(ApplicationId.defaultId(), ClusterMembership.from(spec, 0), Instant.now()); nodes.remove(c); nodes.add(c); @@ -59,7 +59,7 @@ public class NodePrioritizerTest { Assert.assertFalse(NodePrioritizer.isPreferredNodeToBeReloacted(nodes, c, parent)); // Container over content - ClusterSpec spec2 = ClusterSpec.from(ClusterSpec.Type.container, ClusterSpec.Id.from("mycluster"), ClusterSpec.Group.from(0), Version.fromString("6.142.22")); + ClusterSpec spec2 = ClusterSpec.from(ClusterSpec.Type.container, ClusterSpec.Id.from("mycluster"), ClusterSpec.Group.from(0), Version.fromString("6.142.22"), false); d = d.allocate(ApplicationId.defaultId(), ClusterMembership.from(spec2, 0), Instant.now()); nodes.remove(d); nodes.add(d); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java index 97fde2274f2..5f3976ab137 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java @@ -40,7 +40,7 @@ public class NodeTypeProvisioningTest { private final ApplicationId application = tester.makeApplicationId(); // application using proxy nodes private final Capacity capacity = Capacity.fromRequiredNodeType(NodeType.proxy); private final ClusterSpec clusterSpec = ClusterSpec.request( - ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.42")); + ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("6.42"), false); @Before public void setup() { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java index 26e0e714201..11691a691e8 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java @@ -742,8 +742,8 @@ public class ProvisioningTest { tester.makeReadyNodes(6, "large-variant-variant"); //cost = 11 ApplicationId applicationId = tester.makeApplicationId(); - ClusterSpec contentClusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.42")); - ClusterSpec containerClusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer"), Version.fromString("6.42")); + ClusterSpec contentClusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.42"), false); + ClusterSpec containerClusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer"), Version.fromString("6.42"), false); List<HostSpec> containerNodes = tester.prepare(applicationId, containerClusterSpec, 5, 1, "large"); List<HostSpec> contentNodes = tester.prepare(applicationId, contentClusterSpec, 10, 1, "large"); @@ -768,10 +768,10 @@ public class ProvisioningTest { private SystemState prepare(ApplicationId application, int container0Size, int container1Size, int content0Size, int content1Size, String flavor, Version wantedVersion, ProvisioningTester tester) { // "deploy prepare" with a two container clusters and a storage cluster having of two groups - ClusterSpec containerCluster0 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("container0"), wantedVersion); - ClusterSpec containerCluster1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("container1"), wantedVersion); - ClusterSpec contentCluster0 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("content0"), wantedVersion); - ClusterSpec contentCluster1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("content1"), wantedVersion); + ClusterSpec containerCluster0 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("container0"), wantedVersion, false); + ClusterSpec containerCluster1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("container1"), wantedVersion, false); + ClusterSpec contentCluster0 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("content0"), wantedVersion, false); + ClusterSpec contentCluster1 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("content1"), wantedVersion, false); Set<HostSpec> container0 = prepare(application, containerCluster0, container0Size, 1, flavor, tester); Set<HostSpec> container1 = prepare(application, containerCluster1, container1Size, 1, flavor, tester); 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 976069758ea..61032759532 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 @@ -40,6 +40,8 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Function; +import java.util.function.UnaryOperator; import java.util.logging.Level; import java.util.stream.Collectors; @@ -86,8 +88,8 @@ public class ProvisioningTester { new DockerImage("docker-registry.domain.tld:8080/dist/vespa")); this.orchestrator = mock(Orchestrator.class); doThrow(new RuntimeException()).when(orchestrator).acquirePermissionToRemove(any()); - this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, zone, clock, - (x,y) -> allocationSnapshots.add(new AllocationSnapshot(new NodeList(x), "Provision tester", y))); + this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, zone, + (x,y) -> allocationSnapshots.add(new AllocationSnapshot(new NodeList(x), "Provision tester", y))); this.capacityPolicies = new CapacityPolicies(zone, nodeFlavors); this.provisionLogger = new NullProvisionLogger(); } @@ -130,7 +132,7 @@ public class ProvisioningTester { public void patchNode(Node node) { nodeRepository.write(node); } public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, int nodeCount, int groups, String flavor) { - return prepare(application, cluster, Capacity.fromNodeCount(nodeCount, Optional.ofNullable(flavor)), groups); + return prepare(application, cluster, Capacity.fromNodeCount(nodeCount, Optional.ofNullable(flavor), false), groups); } public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity capacity, int groups) { @@ -285,9 +287,20 @@ public class ProvisioningTester { /** Creates a set of virtual nodes on a single parent host */ List<Node> makeReadyVirtualNodes(int n, String flavor, Optional<String> parentHostId) { - List<Node> nodes = new ArrayList<>(n); - for (int i = 0; i < n; i++) { - final String hostname = UUID.randomUUID().toString(); + return makeReadyVirtualNodes(n, 0, flavor, parentHostId, index -> UUID.randomUUID().toString()); + } + + /** Creates a set of virtual nodes on a single parent host */ + List<Node> makeReadyVirtualNode(int index, String flavor, String parentHostId) { + return makeReadyVirtualNodes(1, index, flavor, Optional.of(parentHostId), i -> String.format("node%03d", i)); + } + + /** Creates a set of virtual nodes on a single parent host */ + List<Node> makeReadyVirtualNodes(int count, int startIndex, String flavor, Optional<String> parentHostId, + Function<Integer, String> nodeNamer) { + List<Node> nodes = new ArrayList<>(count); + for (int i = startIndex; i < count + startIndex; i++) { + String hostname = nodeNamer.apply(i); nodes.add(nodeRepository.createNode("openstack-id", hostname, parentHostId, nodeFlavors.getFlavorOrThrow(flavor), NodeType.tenant)); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java index 4366cd641ec..9e505702270 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java @@ -32,8 +32,8 @@ import static org.junit.Assert.assertNotNull; public class VirtualNodeProvisioningTest { private static final String flavor = "v-4-8-100"; - private static final ClusterSpec contentClusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.42")); - private static final ClusterSpec containerClusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer"), Version.fromString("6.42")); + private static final ClusterSpec contentClusterSpec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("myContent"), Version.fromString("6.42"), false); + private static final ClusterSpec containerClusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer"), Version.fromString("6.42"), false); private ProvisioningTester tester; private ApplicationId applicationId; |