diff options
Diffstat (limited to 'controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java')
-rw-r--r-- | controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java new file mode 100644 index 00000000000..9a3ac8b547d --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java @@ -0,0 +1,218 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.routing.rotation; + +import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.zone.RoutingMethod; +import com.yahoo.vespa.hosted.controller.ControllerTester; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.application.AssignedRotation; +import com.yahoo.vespa.hosted.controller.application.SystemApplication; +import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; +import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext; +import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; +import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock; +import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; +import org.junit.Test; + +import java.net.URI; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Oyvind Gronnesby + * @author mpolden + */ +public class RotationRepositoryTest { + + private static final RotationsConfig rotationsConfig = new RotationsConfig( + new RotationsConfig.Builder() + .rotations("foo-1", "foo-1.com") + .rotations("foo-2", "foo-2.com") + ); + + private static final RotationsConfig rotationsConfigWhitespaces = new RotationsConfig( + new RotationsConfig.Builder() + .rotations("foo-1", "\n \t foo-1.com \n") + .rotations("foo-2", "foo-2.com") + ); + + private static final ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("us-east-3") + .region("us-west-1") + .build(); + + private final DeploymentTester tester = new DeploymentTester(new ControllerTester(rotationsConfig, SystemName.main)); + private final RotationRepository repository = tester.controller().routing().rotations(); + private final DeploymentContext application = tester.newDeploymentContext("tenant1", "app1", "default"); + + @Test + public void assigns_and_reuses_rotation() { + // Deploying assigns a rotation + application.submit(applicationPackage).deploy(); + Rotation expected = new Rotation(new RotationId("foo-1"), "foo-1.com"); + + assertEquals(List.of(expected.id()), rotationIds(application.instance().rotations())); + assertEquals(URI.create("https://app1--tenant1.global.vespa.oath.cloud:4443/"), + tester.controller().routing().readDeclaredEndpointsOf(application.instanceId()).primary().get().url()); + try (RotationLock lock = repository.lock()) { + List<AssignedRotation> rotations = repository.getOrAssignRotations(application.application().deploymentSpec(), + application.instance(), + lock); + assertSingleRotation(expected, rotations, repository); + assertEquals(Set.of(RegionName.from("us-west-1"), RegionName.from("us-east-3")), + application.instance().rotations().get(0).regions()); + } + + // Submitting once more assigns same rotation + application.submit(applicationPackage).deploy(); + assertEquals(List.of(expected.id()), rotationIds(application.instance().rotations())); + + // Adding region updates rotation + var applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("us-east-3") + .region("us-west-1") + .region("us-central-1") + .build(); + application.submit(applicationPackage).deploy(); + assertEquals(Set.of(RegionName.from("us-west-1"), RegionName.from("us-east-3"), + RegionName.from("us-central-1")), + application.instance().rotations().get(0).regions()); + } + + @Test + public void strips_whitespace_in_rotation_fqdn() { + var tester = new DeploymentTester(new ControllerTester(rotationsConfigWhitespaces, SystemName.main)); + RotationRepository repository = tester.controller().routing().rotations(); + var application2 = tester.newDeploymentContext("tenant1", "app2", "default"); + + application2.submit(applicationPackage); + + try (RotationLock lock = repository.lock()) { + List<AssignedRotation> rotations = repository.getOrAssignRotations(application2.application().deploymentSpec(), application2.instance(), lock); + Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com"); + assertSingleRotation(assignedRotation, rotations, repository); + } + } + + @Test + public void out_of_rotations() { + // Assigns 1 rotation + application.submit(applicationPackage).deploy(); + + // Assigns 1 more + var application2 = tester.newDeploymentContext("tenant2", "app2", "default"); + application2.submit(applicationPackage).deploy(); + + // We're now out of rotations and next deployment fails + var application3 = tester.newDeploymentContext("tenant3", "app3", "default"); + application3.submit(applicationPackage) + .runJobExpectingFailure(JobType.systemTest, Optional.of("out of rotations")); + } + + @Test + public void too_few_zones() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("us-east-3") + .build(); + application.submit(applicationPackage).runJobExpectingFailure(JobType.systemTest, Optional.of("less than 2 prod zones are defined")); + } + + @Test + public void no_rotation_assigned_for_application_without_service_id() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .region("us-east-3") + .region("us-west-1") + .build(); + application.submit(applicationPackage); + assertTrue(application.instance().rotations().isEmpty()); + } + + @Test + public void prefixes_system_when_not_main() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("cd-us-east-1") + .region("cd-us-west-1") + .build(); + var zones = List.of( + ZoneApiMock.fromId("test.cd-us-west-1"), + ZoneApiMock.fromId("staging.cd-us-west-1"), + ZoneApiMock.fromId("prod.cd-us-east-1"), + ZoneApiMock.fromId("prod.cd-us-west-1")); + tester.controllerTester().zoneRegistry() + .setZones(zones) + .setRoutingMethod(zones, RoutingMethod.shared) + .setSystemName(SystemName.cd); + tester.configServer().bootstrap(tester.controllerTester().zoneRegistry().zones().all().ids(), SystemApplication.notController()); + var application2 = tester.newDeploymentContext("tenant2", "app2", "default"); + application2.submit(applicationPackage).deploy(); + assertEquals(List.of(new RotationId("foo-1")), rotationIds(application2.instance().rotations())); + assertEquals("https://cd--app2--tenant2.global.vespa.oath.cloud:4443/", + tester.controller().routing().readDeclaredEndpointsOf(application2.instanceId()).primary().get().url().toString()); + } + + @Test + public void multiple_instances_with_similar_global_service_id() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .instances("instance1,instance2") + .region("us-central-1") + .parallel("us-west-1", "us-east-3") + .globalServiceId("global") + .build(); + var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1") + .submit(applicationPackage) + .deploy(); + var instance2 = tester.newDeploymentContext("tenant1", "application1", "instance2"); + assertEquals(List.of(new RotationId("foo-1")), rotationIds(instance1.instance().rotations())); + assertEquals(List.of(new RotationId("foo-2")), rotationIds(instance2.instance().rotations())); + assertEquals(URI.create("https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"), + tester.controller().routing().readDeclaredEndpointsOf(instance1.instanceId()).primary().get().url()); + assertEquals(URI.create("https://instance2--application1--tenant1.global.vespa.oath.cloud:4443/"), + tester.controller().routing().readDeclaredEndpointsOf(instance2.instanceId()).primary().get().url()); + } + + @Test + public void multiple_instances_with_similar_endpoints() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .instances("instance1,instance2") + .region("us-central-1") + .parallel("us-west-1", "us-east-3") + .endpoint("default", "foo", "us-central-1", "us-west-1") + .build(); + var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1") + .submit(applicationPackage) + .deploy(); + var instance2 = tester.newDeploymentContext("tenant1", "application1", "instance2"); + + assertEquals(List.of(new RotationId("foo-1")), rotationIds(instance1.instance().rotations())); + assertEquals(List.of(new RotationId("foo-2")), rotationIds(instance2.instance().rotations())); + + assertEquals(URI.create("https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"), + tester.controller().routing().readDeclaredEndpointsOf(instance1.instanceId()).primary().get().url()); + assertEquals(URI.create("https://instance2--application1--tenant1.global.vespa.oath.cloud:4443/"), + tester.controller().routing().readDeclaredEndpointsOf(instance2.instanceId()).primary().get().url()); + } + + private void assertSingleRotation(Rotation expected, List<AssignedRotation> assignedRotations, RotationRepository repository) { + assertEquals(1, assignedRotations.size()); + var rotationId = assignedRotations.get(0).rotationId(); + var rotation = repository.getRotation(rotationId); + assertTrue(rotationId + " exists", rotation.isPresent()); + assertEquals(expected, rotation.get()); + } + + private static List<RotationId> rotationIds(List<AssignedRotation> assignedRotations) { + return assignedRotations.stream().map(AssignedRotation::rotationId).collect(Collectors.toUnmodifiableList()); + } + +} |