diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-01-24 10:37:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-24 10:37:07 +0100 |
commit | 25598180a1ae78c854208c02b47282b161d12df1 (patch) | |
tree | 4f27ab9f7ddf2e88177408d1c147efa984f54adf /node-repository/src | |
parent | a7266a1e0ee4d9463e1af93ab2565966e8b4c981 (diff) | |
parent | e3c50f15f5508140abaccb22b26fd2c881a93bf4 (diff) |
Merge pull request #8185 from vespa-engine/mpolden/lb-acl
Add load balancer networks to node ACLs
Diffstat (limited to 'node-repository/src')
15 files changed, 224 insertions, 114 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java index c870d7d830c..9c389561650 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java @@ -16,6 +16,8 @@ import com.yahoo.transaction.Mutex; import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.hosted.provision.flag.Flags; +import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; +import com.yahoo.vespa.hosted.provision.lb.LoadBalancerList; import com.yahoo.vespa.hosted.provision.maintenance.PeriodicApplicationMaintainer; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.NodeAcl; @@ -35,7 +37,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -173,6 +175,11 @@ public class NodeRepository extends AbstractComponent { return new NodeList(getNodes()); } + /** Returns a filterable list of all load balancers in this repository */ + public LoadBalancerList loadBalancers() { + return new LoadBalancerList(database().readLoadBalancers().values()); + } + public List<Node> getNodes(ApplicationId id, Node.State ... inState) { return db.getNodes(id, inState); } public List<Node> getInactive() { return db.getNodes(Node.State.inactive); } public List<Node> getFailed() { return db.getNodes(Node.State.failed); } @@ -180,14 +187,21 @@ public class NodeRepository extends AbstractComponent { /** * Returns the ACL for the node (trusted nodes, networks and ports) */ - private NodeAcl getNodeAcl(Node node, NodeList candidates) { + private NodeAcl getNodeAcl(Node node, NodeList candidates, LoadBalancerList loadBalancers) { Set<Node> trustedNodes = new TreeSet<>(Comparator.comparing(Node::hostname)); - Set<Integer> trustedPorts = new HashSet<>(); + Set<Integer> trustedPorts = new LinkedHashSet<>(); + Set<String> trustedNetworks = new LinkedHashSet<>(); // For all cases below, trust: // - nodes in same application + // - load balancers allocated to application // - ssh - node.allocation().ifPresent(allocation -> trustedNodes.addAll(candidates.owner(allocation.owner()).asList())); + node.allocation().ifPresent(allocation -> { + trustedNodes.addAll(candidates.owner(allocation.owner()).asList()); + loadBalancers.owner(allocation.owner()).asList().stream() + .map(LoadBalancer::networks) + .forEach(trustedNetworks::addAll); + }); trustedPorts.add(22); switch (node.type()) { @@ -241,7 +255,7 @@ public class NodeRepository extends AbstractComponent { node.hostname(), node.type())); } - return new NodeAcl(node, trustedNodes, Collections.emptySet(), trustedPorts); + return new NodeAcl(node, trustedNodes, trustedNetworks, trustedPorts); } /** @@ -253,13 +267,13 @@ public class NodeRepository extends AbstractComponent { */ public List<NodeAcl> getNodeAcls(Node node, boolean children) { NodeList candidates = list(); + LoadBalancerList loadBalancers = loadBalancers(); if (children) { return candidates.childrenOf(node).asList().stream() - .map(childNode -> getNodeAcl(childNode, candidates)) + .map(childNode -> getNodeAcl(childNode, candidates, loadBalancers)) .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); - } else { - return Collections.singletonList(getNodeAcl(node, candidates)); } + return Collections.singletonList(getNodeAcl(node, candidates, loadBalancers)); } public NodeFlavors getAvailableFlavors() { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java index e59e17815bf..1230c9dc41e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java @@ -1,12 +1,14 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.lb; -import com.google.common.collect.Ordering; +import com.google.common.collect.ImmutableSortedSet; import com.yahoo.config.provision.HostName; import com.yahoo.vespa.hosted.provision.maintenance.LoadBalancerExpirer; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Represents a load balancer for an application. @@ -17,15 +19,22 @@ public class LoadBalancer { private final LoadBalancerId id; private final HostName hostname; - private final List<Integer> ports; - private final List<Real> reals; + private final Set<Integer> ports; + private final Set<String> networks; + private final Set<Real> reals; private final boolean inactive; + // TODO: Remove this when no longer used by internal code public LoadBalancer(LoadBalancerId id, HostName hostname, List<Integer> ports, List<Real> reals, boolean inactive) { + this(id, hostname, ImmutableSortedSet.copyOf(ports), Collections.emptySet(), ImmutableSortedSet.copyOf(reals), inactive); + } + + public LoadBalancer(LoadBalancerId id, HostName hostname, Set<Integer> ports, Set<String> networks, Set<Real> reals, boolean inactive) { this.id = Objects.requireNonNull(id, "id must be non-null"); this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null"); - this.ports = Ordering.natural().immutableSortedCopy(requirePorts(ports)); - this.reals = Ordering.natural().immutableSortedCopy(Objects.requireNonNull(reals, "targets must be non-null")); + this.ports = ImmutableSortedSet.copyOf(requirePorts(ports)); + this.networks = ImmutableSortedSet.copyOf(Objects.requireNonNull(networks, "networks must be non-null")); + this.reals = ImmutableSortedSet.copyOf(Objects.requireNonNull(reals, "targets must be non-null")); this.inactive = inactive; } @@ -40,12 +49,17 @@ public class LoadBalancer { } /** Listening port(s) of this load balancer */ - public List<Integer> ports() { + public Set<Integer> ports() { return ports; } + /** Networks (CIDR blocks) of this load balancer */ + public Set<String> networks() { + return networks; + } + /** Real servers behind this load balancer */ - public List<Real> reals() { + public Set<Real> reals() { return reals; } @@ -59,10 +73,10 @@ public class LoadBalancer { /** Return a copy of this that is set inactive */ public LoadBalancer deactivate() { - return new LoadBalancer(id, hostname, ports, reals, true); + return new LoadBalancer(id, hostname, ports, networks, reals, true); } - private static List<Integer> requirePorts(List<Integer> ports) { + private static Set<Integer> requirePorts(Set<Integer> ports) { Objects.requireNonNull(ports, "ports must be non-null"); if (ports.isEmpty()) { throw new IllegalArgumentException("ports must be non-empty"); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerList.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerList.java new file mode 100644 index 00000000000..0bccc0d3ab6 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerList.java @@ -0,0 +1,45 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.lb; + +import com.google.common.collect.ImmutableList; +import com.yahoo.config.provision.ApplicationId; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.collectingAndThen; + +/** + * A filterable load balancer list + * + * @author mpolden + */ +public class LoadBalancerList { + + private final List<LoadBalancer> loadBalancers; + + public LoadBalancerList(Collection<LoadBalancer> loadBalancers) { + this.loadBalancers = ImmutableList.copyOf(Objects.requireNonNull(loadBalancers, "loadBalancers must be non-null")); + } + + /** Returns the subset of load balancers owned by given application */ + public LoadBalancerList owner(ApplicationId application) { + return loadBalancers.stream() + .filter(lb -> lb.id().application().equals(application)) + .collect(collectingAndThen(Collectors.toList(), LoadBalancerList::new)); + } + + /** Returns the subset of load balancers that are inactive */ + public LoadBalancerList inactive() { + return loadBalancers.stream() + .filter(LoadBalancer::inactive) + .collect(collectingAndThen(Collectors.toList(), LoadBalancerList::new)); + } + + public List<LoadBalancer> asList() { + return loadBalancers; + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java index b5f59414c65..048f3758ac5 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java @@ -4,7 +4,9 @@ package com.yahoo.vespa.hosted.provision.lb; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * A managed load balance service. @@ -14,7 +16,16 @@ import java.util.List; public interface LoadBalancerService { /** Create a load balancer for given application cluster. Implementations are expected to be idempotent */ - LoadBalancer create(ApplicationId application, ClusterSpec.Id cluster, List<Real> reals); + // TODO: Remove once removed from all implementations + default LoadBalancer create(ApplicationId application, ClusterSpec.Id cluster, List<Real> reals) { + return create(application, cluster, new HashSet<>(reals)); + } + + /** Create a load balancer for given application cluster. Implementations are expected to be idempotent */ + // TODO: Remove default implementation once implemented everywhere + default LoadBalancer create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals) { + throw new UnsupportedOperationException(); + } /** Permanently remove load balancer with given ID */ void remove(LoadBalancerId loadBalancer); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java index a5a0d8cb2f8..2e01c657d22 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java @@ -1,14 +1,15 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.lb; +import com.google.common.collect.ImmutableSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Set; /** * @author mpolden @@ -27,11 +28,12 @@ public class LoadBalancerServiceMock implements LoadBalancerService { } @Override - public LoadBalancer create(ApplicationId application, ClusterSpec.Id cluster, List<Real> reals) { + public LoadBalancer create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals) { LoadBalancer loadBalancer = new LoadBalancer( new LoadBalancerId(application, cluster), HostName.from("lb-" + application.toShortString() + "-" + cluster.value()), - Collections.singletonList(4443), + Collections.singleton(4443), + ImmutableSet.of("10.2.3.0/24", "10.4.5.0/24"), reals, false); loadBalancers.put(loadBalancer.id(), loadBalancer); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java index 4b66dff3032..115581c86b0 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java @@ -5,6 +5,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.log.LogLevel; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerId; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerService; import com.yahoo.vespa.hosted.provision.persistence.CuratorDatabaseClient; @@ -12,7 +13,6 @@ import com.yahoo.vespa.hosted.provision.persistence.CuratorDatabaseClient; import java.time.Duration; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -45,15 +45,15 @@ public class LoadBalancerExpirer extends Maintainer { List<LoadBalancerId> failed = new ArrayList<>(); Exception lastException = null; try (Lock lock = db.lockLoadBalancers()) { - for (LoadBalancerId loadBalancer : inactiveLoadBlancers()) { - if (hasNodes(loadBalancer.application())) { // Defer removal if there are still nodes allocated to application + for (LoadBalancer loadBalancer : nodeRepository().loadBalancers().inactive().asList()) { + if (hasNodes(loadBalancer.id().application())) { // Defer removal if there are still nodes allocated to application continue; } try { - service.remove(loadBalancer); - db.removeLoadBalancer(loadBalancer); + service.remove(loadBalancer.id()); + db.removeLoadBalancer(loadBalancer.id()); } catch (Exception e) { - failed.add(loadBalancer); + failed.add(loadBalancer.id()); lastException = e; } } @@ -73,11 +73,4 @@ public class LoadBalancerExpirer extends Maintainer { return !nodeRepository().getNodes(application).isEmpty(); } - private List<LoadBalancerId> inactiveLoadBlancers() { - return db.readLoadBalancers().entrySet().stream() - .filter(entry -> entry.getValue().inactive()) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java index 34a8b414ef4..b24fba83b12 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.node; import com.google.common.collect.ImmutableSet; import com.yahoo.vespa.hosted.provision.Node; +import java.util.Objects; import java.util.Set; /** @@ -20,10 +21,10 @@ public class NodeAcl { private final Set<Integer> trustedPorts; public NodeAcl(Node node, Set<Node> trustedNodes, Set<String> trustedNetworks, Set<Integer> trustedPorts) { - this.node = node; - this.trustedNodes = ImmutableSet.copyOf(trustedNodes); - this.trustedNetworks = ImmutableSet.copyOf(trustedNetworks); - this.trustedPorts = ImmutableSet.copyOf(trustedPorts); + this.node = Objects.requireNonNull(node, "node must be non-null"); + this.trustedNodes = ImmutableSet.copyOf(Objects.requireNonNull(trustedNodes, "trustedNodes must be non-null")); + this.trustedNetworks = ImmutableSet.copyOf(Objects.requireNonNull(trustedNetworks, "trustedNetworks must be non-null")); + this.trustedPorts = ImmutableSet.copyOf(Objects.requireNonNull(trustedPorts, "trustedPorts must be non-null")); } public Node node() { @@ -41,4 +42,5 @@ public class NodeAcl { public Set<Integer> trustedPorts() { return trustedPorts; } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java index 139f42b0f37..cddf668fd34 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java @@ -25,7 +25,6 @@ import com.yahoo.vespa.hosted.provision.lb.LoadBalancerId; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.Status; -import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -456,12 +455,6 @@ public class CuratorDatabaseClient { Collections::unmodifiableMap)); } - public List<LoadBalancer> readLoadBalancers(ApplicationId application) { - return readLoadBalancers().values().stream() - .filter(lb -> lb.id().application().equals(application)) - .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); - } - public Optional<LoadBalancer> readLoadBalancer(LoadBalancerId id) { return read(loadBalancerPath(id), LoadBalancerSerializer::fromJson); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java index ba29fcf2920..334e851a4e5 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java @@ -12,8 +12,8 @@ import com.yahoo.vespa.hosted.provision.lb.Real; import java.io.IOException; import java.io.UncheckedIOException; -import java.util.ArrayList; -import java.util.List; +import java.util.LinkedHashSet; +import java.util.Set; /** * Serializer for load balancers. @@ -26,6 +26,7 @@ public class LoadBalancerSerializer { private static final String hostnameField = "hostname"; private static final String inactiveField = "inactive"; private static final String portsField = "ports"; + private static final String networksField = "networks"; private static final String realsField = "reals"; private static final String ipAddressField = "ipAddress"; private static final String portField = "port"; @@ -38,6 +39,8 @@ public class LoadBalancerSerializer { root.setString(hostnameField, loadBalancer.hostname().toString()); Cursor portArray = root.setArray(portsField); loadBalancer.ports().forEach(portArray::addLong); + Cursor networkArray = root.setArray(networksField); + loadBalancer.networks().forEach(networkArray::addString); Cursor realArray = root.setArray(realsField); loadBalancer.reals().forEach(real -> { Cursor realObject = realArray.addObject(); @@ -57,7 +60,7 @@ public class LoadBalancerSerializer { public static LoadBalancer fromJson(byte[] data) { Cursor object = SlimeUtils.jsonToSlime(data).get(); - List<Real> reals = new ArrayList<>(); + Set<Real> reals = new LinkedHashSet<>(); object.field(realsField).traverse((ArrayTraverser) (i, realObject) -> { reals.add(new Real(HostName.from(realObject.field(hostnameField).asString()), realObject.field(ipAddressField).asString(), @@ -65,12 +68,18 @@ public class LoadBalancerSerializer { }); - List<Integer> ports = new ArrayList<>(); + Set<Integer> ports = new LinkedHashSet<>(); object.field(portsField).traverse((ArrayTraverser) (i, port) -> ports.add((int) port.asLong())); + Set<String> networks = new LinkedHashSet<>(); + if (object.field(networksField).valid()) { // TODO: Remove check after 2019-03-01 + object.field(networksField).traverse((ArrayTraverser) (i, network) -> networks.add(network.asString())); + } + return new LoadBalancer(LoadBalancerId.fromSerializedForm(object.field(idField).asString()), HostName.from(object.field(hostnameField).asString()), ports, + networks, reals, object.field(inactiveField).asBool()); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java index 27ab25be3d5..e486f76c727 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java @@ -42,11 +42,6 @@ public class LoadBalancerProvisioner { this.service = service; } - /** Get load balancers assigned to given application */ - public List<LoadBalancer> get(ApplicationId application) { - return db.readLoadBalancers(application); - } - /** * Provision load balancer(s) for given application. * @@ -73,9 +68,9 @@ public class LoadBalancerProvisioner { public void deactivate(ApplicationId application, NestedTransaction transaction) { try (Mutex applicationLock = nodeRepository.lock(application)) { try (Mutex loadBalancersLock = db.lockLoadBalancers()) { - List<LoadBalancer> deactivatedLoadBalancers = db.readLoadBalancers(application).stream() - .map(LoadBalancer::deactivate) - .collect(Collectors.toList()); + List<LoadBalancer> deactivatedLoadBalancers = nodeRepository.loadBalancers().owner(application).asList().stream() + .map(LoadBalancer::deactivate) + .collect(Collectors.toList()); db.writeLoadBalancers(deactivatedLoadBalancers, transaction); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java index 04a1cdaeeda..e3d0eb10a2c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java @@ -9,11 +9,11 @@ import com.yahoo.slime.JsonFormat; import com.yahoo.slime.Slime; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; +import com.yahoo.vespa.hosted.provision.lb.LoadBalancerList; import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter; import java.io.IOException; import java.io.OutputStream; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -32,12 +32,15 @@ public class LoadBalancersResponse extends HttpResponse { } private Optional<ApplicationId> application() { - return Optional.ofNullable(request.getProperty("application")).map(ApplicationFilter::toApplicationId); + return Optional.ofNullable(request.getProperty("application")) + .map(ApplicationFilter::toApplicationId); } private List<LoadBalancer> loadBalancers() { - return application().map(nodeRepository.database()::readLoadBalancers) - .orElseGet(() -> new ArrayList<>(nodeRepository.database().readLoadBalancers().values())); + LoadBalancerList loadBalancers = nodeRepository.loadBalancers(); + return application().map(loadBalancers::owner) + .map(LoadBalancerList::asList) + .orElseGet(loadBalancers::asList); } @Override diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeAclResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeAclResponse.java index 5dcb4ab6a71..9947d2d5108 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeAclResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeAclResponse.java @@ -4,8 +4,8 @@ package com.yahoo.vespa.hosted.provision.restapi.v2; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.slime.Cursor; +import com.yahoo.slime.JsonFormat; import com.yahoo.slime.Slime; -import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.NodeAcl; @@ -33,8 +33,8 @@ public class NodeAclResponse extends HttpResponse { this.slime = new Slime(); this.aclsForChildren = request.getBooleanProperty(CHILDREN_REQUEST_PROPERTY); - final Cursor root = slime.setObject(); - final String hostname = baseName(request.getUri().getPath()); + Cursor root = slime.setObject(); + String hostname = baseName(request.getUri().getPath()); toSlime(hostname, root); } @@ -81,8 +81,8 @@ public class NodeAclResponse extends HttpResponse { } @Override - public void render(OutputStream outputStream) throws IOException { - outputStream.write(SlimeUtils.toJsonBytes(slime)); + public void render(OutputStream stream) throws IOException { + new JsonFormat(true).encode(stream, slime); } @Override diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java index 5fcbab64429..e0e62fbe628 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.persistence; +import com.google.common.collect.ImmutableSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; @@ -9,8 +10,6 @@ import com.yahoo.vespa.hosted.provision.lb.LoadBalancerId; import com.yahoo.vespa.hosted.provision.lb.Real; import org.junit.Test; -import java.util.Arrays; - import static org.junit.Assert.assertEquals; /** @@ -25,19 +24,21 @@ public class LoadBalancerSerializerTest { "default"), ClusterSpec.Id.from("qrs")), HostName.from("lb-host"), - Arrays.asList(4080, 4443), - Arrays.asList(new Real(HostName.from("real-1"), - "127.0.0.1", - 4080), - new Real(HostName.from("real-2"), - "127.0.0.2", - 4080)), + ImmutableSet.of(4080, 4443), + ImmutableSet.of("10.2.3.4/24"), + ImmutableSet.of(new Real(HostName.from("real-1"), + "127.0.0.1", + 4080), + new Real(HostName.from("real-2"), + "127.0.0.2", + 4080)), false); LoadBalancer serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer)); assertEquals(loadBalancer.id(), serialized.id()); assertEquals(loadBalancer.hostname(), serialized.hostname()); assertEquals(loadBalancer.ports(), serialized.ports()); + assertEquals(loadBalancer.networks(), serialized.networks()); assertEquals(loadBalancer.inactive(), serialized.inactive()); assertEquals(loadBalancer.reals(), serialized.reals()); } 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 37327d45845..3e69a5391af 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 @@ -10,6 +10,7 @@ import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.flag.FlagId; import com.yahoo.vespa.hosted.provision.node.NodeAcl; import org.junit.Before; import org.junit.Test; @@ -21,13 +22,14 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import static com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester.createConfig; +import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; /** * @author mpolden @@ -52,15 +54,24 @@ public class AclProvisioningTest { List<Node> proxyNodes = tester.makeReadyNodes(3, "default", NodeType.proxy); // Allocate 2 nodes - List<Node> activeNodes = allocateNodes(2); + ApplicationId application = tester.makeApplicationId(); + List<Node> activeNodes = deploy(application, 2); assertEquals(2, activeNodes.size()); // Get trusted nodes for the first active node Node node = activeNodes.get(0); - List<NodeAcl> nodeAcls = tester.nodeRepository().getNodeAcls(node, false); + Supplier<List<NodeAcl>> nodeAcls = () -> tester.nodeRepository().getNodeAcls(node, false); // Trusted nodes is active nodes in same application, proxy nodes and config servers - assertAcls(Arrays.asList(activeNodes, proxyNodes, configServers, dockerHost), nodeAcls); + assertAcls(Arrays.asList(activeNodes, proxyNodes, configServers, dockerHost), nodeAcls.get()); + + // Allocate load balancer + tester.nodeRepository().flags().setEnabled(FlagId.exclusiveLoadBalancer, application, true); + deploy(application, 2); + + // Load balancer networks are added to ACLs + assertAcls(Arrays.asList(activeNodes, proxyNodes, configServers, dockerHost), + ImmutableSet.of("10.2.3.0/24", "10.4.5.0/24"), nodeAcls.get()); } @Test @@ -72,7 +83,7 @@ public class AclProvisioningTest { List<Node> proxyNodes = tester.makeReadyNodes(3, "default", NodeType.proxy); // Allocate 2 nodes to an application - allocateNodes(2); + deploy(2); // Get trusted nodes for a ready tenant node Node node = tester.nodeRepository().getNodes(NodeType.tenant, Node.State.ready).get(0); @@ -92,7 +103,7 @@ public class AclProvisioningTest { List<Node> proxyNodes = tester.makeReadyNodes(3, "default", NodeType.proxy); // Allocate 2 nodes - allocateNodes(4); + deploy(4); List<Node> tenantNodes = tester.nodeRepository().getNodes(NodeType.tenant); // Get trusted nodes for the first config server @@ -114,7 +125,7 @@ public class AclProvisioningTest { // Deploy zone application ApplicationId zoneApplication = tester.makeApplicationId(); - allocateNodes(Capacity.fromRequiredNodeType(NodeType.proxy), zoneApplication); + deploy(zoneApplication, Capacity.fromRequiredNodeType(NodeType.proxy)); // Get trusted nodes for first proxy node List<Node> proxyNodes = tester.nodeRepository().getNodes(zoneApplication); @@ -156,7 +167,7 @@ public class AclProvisioningTest { // Allocate ApplicationId controllerApplication = tester.makeApplicationId(); - List<Node> controllers = allocateNodes(Capacity.fromRequiredNodeType(NodeType.controller), controllerApplication); + List<Node> controllers = deploy(controllerApplication, Capacity.fromRequiredNodeType(NodeType.controller)); // Controllers and hosts all trust each other List<NodeAcl> controllerAcls = tester.nodeRepository().getNodeAcls(controllers.get(0), false); @@ -165,6 +176,16 @@ public class AclProvisioningTest { } @Test + public void trusted_nodes_for_application_with_load_balancer() { + // Populate repo + tester.makeReadyNodes(10, "default"); + + // Allocate 2 nodes + List<Node> activeNodes = deploy(2); + assertEquals(2, activeNodes.size()); + } + + @Test public void resolves_hostnames_from_connection_spec() { tester.makeConfigServers(3, "default", Version.fromString("6.123.456")); @@ -178,16 +199,20 @@ public class AclProvisioningTest { assertEquals(singleton("127.0.1.3"), trustedNodes.next().ipAddresses()); } - private List<Node> allocateNodes(int nodeCount) { - return allocateNodes(Capacity.fromNodeCount(nodeCount), tester.makeApplicationId()); + private List<Node> deploy(int nodeCount) { + return deploy(tester.makeApplicationId(), nodeCount); } - private List<Node> allocateNodes(Capacity capacity, ApplicationId applicationId) { - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), + private List<Node> deploy(ApplicationId application, int nodeCount) { + return deploy(application, Capacity.fromNodeCount(nodeCount)); + } + + private List<Node> deploy(ApplicationId application, Capacity capacity) { + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), 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(); + List<HostSpec> prepared = tester.prepare(application, cluster, capacity, 1); + tester.activate(application, new HashSet<>(prepared)); + return tester.getNodes(application, Node.State.active).asList(); } private static void assertAcls(List<List<Node>> expected, NodeAcl actual) { @@ -195,6 +220,10 @@ public class AclProvisioningTest { } private static void assertAcls(List<List<Node>> expectedNodes, List<NodeAcl> actual) { + assertAcls(expectedNodes, emptySet(), actual); + } + + private static void assertAcls(List<List<Node>> expectedNodes, Set<String> expectedNetworks, List<NodeAcl> actual) { Set<Node> expectedTrustedNodes = expectedNodes.stream() .flatMap(Collection::stream) .collect(Collectors.toSet()); @@ -206,6 +235,6 @@ public class AclProvisioningTest { Set<String> actualTrustedNetworks = actual.stream() .flatMap(acl -> acl.trustedNetworks().stream()) .collect(Collectors.toSet()); - assertTrue("No networks are trusted", actualTrustedNetworks.isEmpty()); + assertEquals(expectedNetworks, actualTrustedNetworks); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java index e340abfa5c1..da99188b113 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.provisioning; +import com.google.common.collect.Iterators; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; @@ -11,7 +12,6 @@ import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.flag.FlagId; import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; -import com.yahoo.vespa.hosted.provision.lb.LoadBalancerService; import com.yahoo.vespa.hosted.provision.lb.Real; import com.yahoo.vespa.hosted.provision.node.Agent; import org.junit.Before; @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; @@ -35,14 +36,10 @@ public class LoadBalancerProvisionerTest { private final ApplicationId app2 = ApplicationId.from("tenant2", "application2", "default"); private ProvisioningTester tester; - private LoadBalancerService service; - private LoadBalancerProvisioner loadBalancerProvisioner; @Before public void before() { tester = new ProvisioningTester(Zone.defaultZone()); - service = tester.loadBalancerService(); - loadBalancerProvisioner = new LoadBalancerProvisioner(tester.nodeRepository(), service); } @Test @@ -57,16 +54,16 @@ public class LoadBalancerProvisionerTest { clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("qrs")))); // Provision a load balancer for each application - List<LoadBalancer> loadBalancers = loadBalancerProvisioner.get(app1); - assertEquals(1, loadBalancers.size()); + Supplier<List<LoadBalancer>> loadBalancers = () -> tester.nodeRepository().loadBalancers().owner(app1).asList(); + assertEquals(1, loadBalancers.get().size()); - assertEquals(loadBalancers.get(0).id().application(), app1); - assertEquals(loadBalancers.get(0).id().cluster(), containerCluster1); - assertEquals(loadBalancers.get(0).ports(), Collections.singletonList(4443)); - assertEquals(loadBalancers.get(0).reals().get(0).ipAddress(), "127.0.0.1"); - assertEquals(loadBalancers.get(0).reals().get(0).port(), 4080); - assertEquals(loadBalancers.get(0).reals().get(1).ipAddress(), "127.0.0.2"); - assertEquals(loadBalancers.get(0).reals().get(1).port(), 4080); + assertEquals(app1, loadBalancers.get().get(0).id().application()); + assertEquals(containerCluster1, loadBalancers.get().get(0).id().cluster()); + assertEquals(Collections.singleton(4443), loadBalancers.get().get(0).ports()); + assertEquals("127.0.0.1", get(loadBalancers.get().get(0).reals(), 0).ipAddress()); + assertEquals(4080, get(loadBalancers.get().get(0).reals(), 0).port()); + assertEquals("127.0.0.2", get(loadBalancers.get().get(0).reals(), 1).ipAddress()); + assertEquals(4080, get(loadBalancers.get().get(0).reals(), 1).port()); // A container is failed List<Node> containers = tester.getNodes(app1).type(ClusterSpec.Type.container).asList(); @@ -79,10 +76,10 @@ public class LoadBalancerProvisionerTest { // Redeploy removed replaced failed node in load balancer containers = tester.getNodes(app1).type(ClusterSpec.Type.container).asList(); - LoadBalancer loadBalancer = loadBalancerProvisioner.get(app1).get(0); + LoadBalancer loadBalancer = tester.nodeRepository().loadBalancers().owner(app1).asList().get(0); assertEquals(2, loadBalancer.reals().size()); - assertEquals(containers.get(0).hostname(), loadBalancer.reals().get(0).hostname().value()); - assertEquals(containers.get(1).hostname(), loadBalancer.reals().get(1).hostname().value()); + assertEquals(containers.get(0).hostname(), get(loadBalancer.reals(), 0).hostname().value()); + assertEquals(containers.get(1).hostname(), get(loadBalancer.reals(), 1).hostname().value()); // Add another container cluster ClusterSpec.Id containerCluster2 = ClusterSpec.Id.from("qrs2"); @@ -92,8 +89,7 @@ public class LoadBalancerProvisionerTest { clusterRequest(ClusterSpec.Type.content, contentCluster))); // Load balancer is provisioned for second container cluster - loadBalancers = loadBalancerProvisioner.get(app1); - assertEquals(2, loadBalancers.size()); + assertEquals(2, loadBalancers.get().size()); List<HostName> activeContainers = tester.getNodes(app1, Node.State.active) .type(ClusterSpec.Type.container).asList() .stream() @@ -101,7 +97,7 @@ public class LoadBalancerProvisionerTest { .map(HostName::from) .sorted() .collect(Collectors.toList()); - List<HostName> reals = loadBalancers.stream() + List<HostName> reals = loadBalancers.get().stream() .flatMap(lb -> lb.reals().stream()) .map(Real::hostname) .sorted() @@ -113,9 +109,8 @@ public class LoadBalancerProvisionerTest { tester.provisioner().remove(removeTransaction, app1); removeTransaction.commit(); - List<LoadBalancer> assignedLoadBalancer = tester.nodeRepository().database().readLoadBalancers(app1); - assertEquals(2, loadBalancers.size()); - assertTrue("Deactivated load balancers", assignedLoadBalancer.stream().allMatch(LoadBalancer::inactive)); + assertEquals(2, loadBalancers.get().size()); + assertTrue("Deactivated load balancers", loadBalancers.get().stream().allMatch(LoadBalancer::inactive)); } private ClusterSpec clusterRequest(ClusterSpec.Type type, ClusterSpec.Id id) { @@ -131,4 +126,8 @@ public class LoadBalancerProvisionerTest { return allNodes; } + private static <T> T get(Set<T> set, int position) { + return Iterators.get(set.iterator(), position, null); + } + } |