diff options
author | Martin Polden <mpolden@mpolden.no> | 2018-11-22 14:07:40 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2018-11-23 11:16:57 +0100 |
commit | 6f2dc7361f36eeb661da6fc8195be502855e4ec5 (patch) | |
tree | 1198c915dc43cda26e023d877de39332a119342a /node-repository/src/test | |
parent | 0713e24de6a12c3584112c9af60dc4105d2056d8 (diff) |
Add load balancer provisioner
Diffstat (limited to 'node-repository/src/test')
3 files changed, 199 insertions, 0 deletions
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 new file mode 100644 index 00000000000..2dc3288194b --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.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.persistence; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.HostName; +import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; +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; + +/** + * @author mpolden + */ +public class LoadBalancerSerializerTest { + + @Test + public void test_serialization() { + LoadBalancer loadBalancer = new LoadBalancer(new LoadBalancerId(ApplicationId.from("tenant1", + "application1", + "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)), + 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.deleted(), serialized.deleted()); + assertEquals(loadBalancer.reals(), serialized.reals()); + } + +} 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 new file mode 100644 index 00000000000..e4b3d0be526 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java @@ -0,0 +1,150 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.provisioning; + +import com.yahoo.component.Version; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.Zone; +import com.yahoo.transaction.NestedTransaction; +import com.yahoo.vespa.hosted.provision.Node; +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.lb.Real; +import com.yahoo.vespa.hosted.provision.node.Agent; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author mpolden + */ +public class LoadBalancerProvisionerTest { + + private final ApplicationId app1 = ApplicationId.from("tenant1", "application1", "default"); + 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 + public void provision_load_balancer() { + ClusterSpec.Id containerCluster1 = ClusterSpec.Id.from("qrs1"); + ClusterSpec.Id contentCluster = ClusterSpec.Id.from("content"); + tester.activate(app1, prepare(app1, + clusterRequest(ClusterSpec.Type.container, containerCluster1), + clusterRequest(ClusterSpec.Type.content, contentCluster))); + tester.activate(app2, prepare(app2, + clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("qrs")))); + + // Provision a load balancer for each application + Map<LoadBalancerId, LoadBalancer> loadBalancers = loadBalancerProvisioner.provision(app1); + loadBalancerProvisioner.provision(app2); + assertEquals(1, loadBalancers.size()); + + LoadBalancer loadBalancer = loadBalancers.values().iterator().next(); + assertEquals(loadBalancer.id().application(), app1); + assertEquals(loadBalancer.id().cluster(), containerCluster1); + assertEquals(loadBalancer.ports(), Collections.singletonList(4443)); + assertEquals(loadBalancer.reals().get(0).ipAddress(), "127.0.0.1"); + assertEquals(loadBalancer.reals().get(0).port(), 4443); + assertEquals(loadBalancer.reals().get(1).ipAddress(), "127.0.0.2"); + assertEquals(loadBalancer.reals().get(1).port(), 4443); + + // A container is failed + List<Node> containers = tester.getNodes(app1).type(ClusterSpec.Type.container).asList(); + Node container1 = containers.get(0); + Node container2 = containers.get(1); + tester.nodeRepository().fail(container1.hostname(), Agent.system, "Failed by unit test"); + + // Reprovisioning load balancer removes failed container + loadBalancer = loadBalancerProvisioner.provision(app1).values().iterator().next(); + assertEquals(1, loadBalancer.reals().size()); + assertEquals(container2.hostname(), loadBalancer.reals().get(0).hostname().value()); + + // Redeploying replaces failed node + tester.activate(app1, prepare(app1, + clusterRequest(ClusterSpec.Type.container, containerCluster1), + clusterRequest(ClusterSpec.Type.content, contentCluster))); + + // Reprovisioning load balancer adds the new node + Node container3 = tester.getNodes(app1).type(ClusterSpec.Type.container).asList().get(1); + loadBalancer = loadBalancerProvisioner.provision(app1).values().iterator().next(); + assertEquals(2, loadBalancer.reals().size()); + assertEquals(container3.hostname(), loadBalancer.reals().get(1).hostname().value()); + + // Add another container cluster + ClusterSpec.Id containerCluster2 = ClusterSpec.Id.from("qrs2"); + tester.activate(app1, prepare(app1, + clusterRequest(ClusterSpec.Type.container, containerCluster1), + clusterRequest(ClusterSpec.Type.container, containerCluster2), + clusterRequest(ClusterSpec.Type.content, contentCluster))); + + // Load balancer is provisioned for second container cluster + loadBalancers = loadBalancerProvisioner.provision(app1); + assertEquals(2, loadBalancers.size()); + List<HostName> activeContainers = tester.getNodes(app1, Node.State.active) + .type(ClusterSpec.Type.container).asList() + .stream() + .map(Node::hostname) + .map(HostName::from) + .sorted() + .collect(Collectors.toList()); + List<HostName> reals = loadBalancers.values().stream() + .flatMap(lb -> lb.reals().stream()) + .map(Real::hostname) + .sorted() + .collect(Collectors.toList()); + assertEquals(activeContainers, reals); + + // Removing load balancer with active containers fails + try { + loadBalancerProvisioner.remove(app1); + fail("Expected exception"); + } catch (IllegalArgumentException ignored) {} + + // Application and load balancer is removed + NestedTransaction removeTransaction = new NestedTransaction(); + tester.provisioner().remove(removeTransaction, app1); + removeTransaction.commit(); + + loadBalancerProvisioner.remove(app1); + List<LoadBalancer> assignedLoadBalancer = tester.nodeRepository().database().readLoadBalancers(app1); + assertEquals(2, loadBalancers.size()); + assertTrue("Load balancers marked for deletion", assignedLoadBalancer.stream().allMatch(LoadBalancer::deleted)); + } + + private ClusterSpec clusterRequest(ClusterSpec.Type type, ClusterSpec.Id id) { + return ClusterSpec.request(type, id, Version.fromString("6.42"), false); + } + + private Set<HostSpec> prepare(ApplicationId application, ClusterSpec... specs) { + tester.makeReadyNodes(specs.length * 2, "default"); + Set<HostSpec> allNodes = new LinkedHashSet<>(); + for (ClusterSpec spec : specs) { + allNodes.addAll(tester.prepare(application, spec, 2, 1, "default")); + } + return allNodes; + } + +} 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 81414c0ac2d..8c0937d34b8 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 @@ -28,6 +28,7 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.filter.NodeHostFilter; import com.yahoo.vespa.hosted.provision.persistence.NameResolver; +import com.yahoo.vespa.hosted.provision.testutils.LoadBalancerServiceMock; import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; import com.yahoo.vespa.orchestrator.Orchestrator; import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; @@ -68,6 +69,7 @@ public class ProvisioningTester { private final NodeRepositoryProvisioner provisioner; private final CapacityPolicies capacityPolicies; private final ProvisionLogger provisionLogger; + private final LoadBalancerServiceMock loadBalancerService; private int nextHost = 0; private int nextIP = 0; @@ -90,6 +92,7 @@ public class ProvisioningTester { this.orchestrator = mock(Orchestrator.class); doThrow(new RuntimeException()).when(orchestrator).acquirePermissionToRemove(any()); this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, zone); + this.loadBalancerService = new LoadBalancerServiceMock(); this.capacityPolicies = new CapacityPolicies(zone, nodeFlavors); this.provisionLogger = new NullProvisionLogger(); } @@ -126,6 +129,7 @@ public class ProvisioningTester { public Orchestrator orchestrator() { return orchestrator; } public ManualClock clock() { return clock; } public NodeRepositoryProvisioner provisioner() { return provisioner; } + public LoadBalancerServiceMock loadBalancerService() { return loadBalancerService; } public CapacityPolicies capacityPolicies() { return capacityPolicies; } public NodeList getNodes(ApplicationId id, Node.State ... inState) { return new NodeList(nodeRepository.getNodes(id, inState)); } |