diff options
Diffstat (limited to 'controller-server')
13 files changed, 215 insertions, 76 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java index 4772dbeaab1..6ef0df9f099 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java @@ -35,9 +35,10 @@ import com.yahoo.vespa.hosted.controller.routing.RoutingPolicies; import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext; import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext.ExclusiveDeploymentRoutingContext; import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext.SharedDeploymentRoutingContext; -import com.yahoo.vespa.hosted.controller.routing.context.ExclusiveRoutingContext; +import com.yahoo.vespa.hosted.controller.routing.context.ExclusiveZoneRoutingContext; import com.yahoo.vespa.hosted.controller.routing.context.RoutingContext; -import com.yahoo.vespa.hosted.controller.routing.context.SharedRoutingContext; +import com.yahoo.vespa.hosted.controller.routing.context.SharedZoneRoutingContext; +import com.yahoo.vespa.hosted.controller.routing.rotation.Rotation; import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock; import com.yahoo.vespa.hosted.controller.routing.rotation.RotationRepository; import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; @@ -53,6 +54,8 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; @@ -95,9 +98,9 @@ public class RoutingController { /** Create a routing context for given zone */ public RoutingContext of(ZoneId zone) { if (usesSharedRouting(zone)) { - return new SharedRoutingContext(zone, controller.serviceRegistry().configServer()); + return new SharedZoneRoutingContext(zone, controller.serviceRegistry().configServer()); } - return new ExclusiveRoutingContext(zone, routingPolicies); + return new ExclusiveZoneRoutingContext(zone, routingPolicies); } public RoutingPolicies policies() { @@ -257,7 +260,6 @@ public class RoutingController { EndpointList endpoints = declaredEndpointsOf(application.get()).targets(deployment); EndpointList globalEndpoints = endpoints.scope(Endpoint.Scope.global); for (var assignedRotation : instance.rotations()) { - var names = new ArrayList<String>(); EndpointList rotationEndpoints = globalEndpoints.named(assignedRotation.endpointId()) .requiresRotation(); @@ -272,21 +274,21 @@ public class RoutingController { } // Register names in DNS - var rotation = rotationRepository.getRotation(assignedRotation.rotationId()); - if (rotation.isPresent()) { - rotationEndpoints.forEach(endpoint -> { - controller.nameServiceForwarder().createCname(RecordName.from(endpoint.dnsName()), - RecordData.fqdn(rotation.get().name()), - Priority.normal); - names.add(endpoint.dnsName()); - }); + Rotation rotation = rotationRepository.requireRotation(assignedRotation.rotationId()); + for (var endpoint : rotationEndpoints) { + controller.nameServiceForwarder().createCname(RecordName.from(endpoint.dnsName()), + RecordData.fqdn(rotation.name()), + Priority.normal); + List<String> names = List.of(endpoint.dnsName(), + // Include rotation ID as a valid name of this container endpoint + // (required by global routing health checks) + assignedRotation.rotationId().asString()); + containerEndpoints.add(new ContainerEndpoint(assignedRotation.clusterId().value(), + asString(Endpoint.Scope.global), + names, + OptionalInt.empty(), + endpoint.routingMethod())); } - - // Include rotation ID as a valid name of this container endpoint (required by global routing health checks) - names.add(assignedRotation.rotationId().asString()); - containerEndpoints.add(new ContainerEndpoint(assignedRotation.clusterId().value(), - asString(Endpoint.Scope.global), - names)); } // Add endpoints not backed by a rotation (i.e. other routing methods so that the config server always knows // about global names, even when not using rotations) @@ -295,7 +297,9 @@ public class RoutingController { .forEach((clusterId, clusterEndpoints) -> { containerEndpoints.add(new ContainerEndpoint(clusterId.value(), asString(Endpoint.Scope.global), - clusterEndpoints.mapToList(Endpoint::dnsName))); + clusterEndpoints.mapToList(Endpoint::dnsName), + OptionalInt.empty(), + RoutingMethod.exclusive)); }); // Add application endpoints EndpointList applicationEndpoints = endpoints.scope(Endpoint.Scope.application); @@ -313,12 +317,22 @@ public class RoutingController { RecordData.fqdn(vipHostname), Priority.normal); } - applicationEndpoints.groupingBy(Endpoint::cluster) - .forEach((clusterId, clusterEndpoints) -> { - containerEndpoints.add(new ContainerEndpoint(clusterId.value(), - asString(Endpoint.Scope.application), - clusterEndpoints.mapToList(Endpoint::dnsName))); - }); + Map<ClusterSpec.Id, EndpointList> applicationEndpointsByCluster = applicationEndpoints.groupingBy(Endpoint::cluster); + for (var kv : applicationEndpointsByCluster.entrySet()) { + ClusterSpec.Id clusterId = kv.getKey(); + EndpointList clusterEndpoints = kv.getValue(); + for (var endpoint : clusterEndpoints) { + Optional<Endpoint.Target> matchingTarget = endpoint.targets().stream() + .filter(t -> t.routesTo(deployment)) + .findFirst(); + if (matchingTarget.isEmpty()) throw new IllegalStateException("No target found routing to " + deployment + " in " + endpoint); + containerEndpoints.add(new ContainerEndpoint(clusterId.value(), + asString(Endpoint.Scope.application), + List.of(endpoint.dnsName()), + OptionalInt.of(matchingTarget.get().weight()), + endpoint.routingMethod())); + } + } return Collections.unmodifiableSet(containerEndpoints); } @@ -376,8 +390,8 @@ public class RoutingController { var deploymentsByMethod = new HashMap<RoutingMethod, Set<DeploymentId>>(); for (var deployment : deployments) { for (var method : controller.zoneRegistry().routingMethods(deployment.zoneId())) { - deploymentsByMethod.putIfAbsent(method, new LinkedHashSet<>()); - deploymentsByMethod.get(method).add(deployment); + deploymentsByMethod.computeIfAbsent(method, k -> new LinkedHashSet<>()) + .add(deployment); } } var routingMethods = new ArrayList<RoutingMethod>(); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java index aee7c1052be..544822e3be3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java @@ -482,6 +482,11 @@ public class Endpoint { return weight; } + /** Returns whether this routes to given deployment */ + public boolean routesTo(DeploymentId deployment) { + return this.deployment.equals(deployment); + } + } public static class EndpointBuilder { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java index f9fd02fbf56..7fe8d554998 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java @@ -53,7 +53,7 @@ public class EndpointList extends AbstractFilteringList<Endpoint, EndpointList> return matching(endpoint -> endpoint.deployments().containsAll(deployments)); } - /** Returns the subset of endpoints which target the given deployments */ + /** Returns the subset of endpoints which target the given deployment */ public EndpointList targets(DeploymentId deployment) { return targets(List.of(deployment)); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java index 0d12b283543..7e9ae036cc7 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java @@ -25,6 +25,7 @@ import com.yahoo.vespa.flags.IntFlag; import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.LockedTenant; +import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; import com.yahoo.vespa.hosted.controller.api.integration.user.Roles; import com.yahoo.vespa.hosted.controller.api.integration.user.User; @@ -176,6 +177,7 @@ public class UserApiHandler extends LoggingRequestHandler { .sorted() .forEach(tenant -> { Cursor tenantObject = tenants.setObject(tenant.value()); + tenantObject.setBool("supported", hasSupportedPlan(tenant)); Cursor tenantRolesObject = tenantObject.setArray("roles"); tenantRolesByTenantName.getOrDefault(tenant, List.of()) @@ -405,4 +407,11 @@ public class UserApiHandler extends LoggingRequestHandler { .map(clazz::cast) .orElseThrow(() -> new IllegalArgumentException("Attribute '" + attributeName + "' was not set on request")); } + + private boolean hasSupportedPlan(TenantName tenantName) { + var planId = controller.serviceRegistry().billingController().getPlan(tenantName); + return controller.serviceRegistry().planRegistry().plan(planId) + .map(Plan::isSupported) + .orElse(false); + } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveZoneRoutingContext.java index e949c45f2fd..e29fb5ab404 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveRoutingContext.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveZoneRoutingContext.java @@ -13,12 +13,12 @@ import java.util.Objects; * * @author mpolden */ -public class ExclusiveRoutingContext implements RoutingContext { +public class ExclusiveZoneRoutingContext implements RoutingContext { private final RoutingPolicies policies; private final ZoneId zone; - public ExclusiveRoutingContext(ZoneId zone, RoutingPolicies policies) { + public ExclusiveZoneRoutingContext(ZoneId zone, RoutingPolicies policies) { this.policies = Objects.requireNonNull(policies); this.zone = Objects.requireNonNull(zone); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedZoneRoutingContext.java index e38212d7f80..2923c8dff5c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedRoutingContext.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedZoneRoutingContext.java @@ -15,12 +15,12 @@ import java.util.Objects; * * @author mpolden */ -public class SharedRoutingContext implements RoutingContext { +public class SharedZoneRoutingContext implements RoutingContext { private final ConfigServer configServer; private final ZoneId zone; - public SharedRoutingContext(ZoneId zone, ConfigServer configServer) { + public SharedZoneRoutingContext(ZoneId zone, ConfigServer configServer) { this.configServer = Objects.requireNonNull(configServer); this.zone = Objects.requireNonNull(zone); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java index 961fdc6dd9c..39a0b6a8858 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java @@ -21,7 +21,6 @@ import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.logging.Logger; @@ -56,9 +55,11 @@ public class RotationRepository { return new RotationLock(curator.lockRotations()); } - /** Get rotation by given rotationId */ - public Optional<Rotation> getRotation(RotationId rotationId) { - return Optional.of(allRotations.get(rotationId)); + /** Get rotation with given id */ + public Rotation requireRotation(RotationId id) { + Rotation rotation = allRotations.get(id); + if (rotation == null) throw new IllegalArgumentException("No such rotation: '" + id.asString() + "'"); + return rotation; } /** diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java index 30cdd1b8466..1215ddbc2ad 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java @@ -13,7 +13,6 @@ import com.yahoo.config.provision.CloudName; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.RoutingMethod; @@ -37,10 +36,10 @@ 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.controller.persistence.MockCuratorDb; -import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId; -import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock; import com.yahoo.vespa.hosted.controller.routing.RoutingStatus; import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext; +import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId; +import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock; import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; import org.junit.Test; @@ -50,6 +49,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -233,22 +233,41 @@ public class ControllerTest { public void testDnsUpdatesForGlobalEndpoint() { var betaContext = tester.newDeploymentContext("tenant1", "app1", "beta"); var defaultContext = tester.newDeploymentContext("tenant1", "app1", "default"); + + ZoneId usWest = ZoneId.from("prod.us-west-1"); + ZoneId usCentral = ZoneId.from("prod.us-central-1"); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service")) .instances("beta,default") .endpoint("default", "foo") - .region("us-west-1") - .region("us-central-1") // Two deployments should result in each DNS alias being registered once + .region(usWest.region()) + .region(usCentral.region()) // Two deployments should result in each DNS alias being registered once .build(); + tester.controllerTester().zoneRegistry().setRoutingMethod(List.of(ZoneApiMock.from(usWest), ZoneApiMock.from(usCentral)), + RoutingMethod.shared, + RoutingMethod.sharedLayer4); betaContext.submit(applicationPackage).deploy(); { // Expected rotation names are passed to beta instance deployments Collection<Deployment> betaDeployments = betaContext.instance().deployments().values(); assertFalse(betaDeployments.isEmpty()); + Set<ContainerEndpoint> containerEndpoints = Set.of(new ContainerEndpoint("foo", + "global", + List.of("beta--app1--tenant1.global.vespa.oath.cloud", + "rotation-id-01"), + OptionalInt.empty(), + RoutingMethod.shared), + new ContainerEndpoint("foo", + "global", + List.of("beta.app1.tenant1.global.vespa.oath.cloud", + "rotation-id-01"), + OptionalInt.empty(), + RoutingMethod.sharedLayer4)); + for (Deployment deployment : betaDeployments) { - assertEquals("Rotation names are passed to config server in " + deployment.zone(), - Set.of("rotation-id-01", - "beta--app1--tenant1.global.vespa.oath.cloud"), - tester.configServer().containerEndpointNames(betaContext.deploymentIdIn(deployment.zone()))); + assertEquals(containerEndpoints, + tester.configServer().containerEndpoints() + .get(betaContext.deploymentIdIn(deployment.zone()))); } betaContext.flushDnsUpdates(); } @@ -256,11 +275,21 @@ public class ControllerTest { { // Expected rotation names are passed to default instance deployments Collection<Deployment> defaultDeployments = defaultContext.instance().deployments().values(); assertFalse(defaultDeployments.isEmpty()); + Set<ContainerEndpoint> containerEndpoints = Set.of(new ContainerEndpoint("foo", + "global", + List.of("app1--tenant1.global.vespa.oath.cloud", + "rotation-id-02"), + OptionalInt.empty(), + RoutingMethod.shared), + new ContainerEndpoint("foo", + "global", + List.of("app1.tenant1.global.vespa.oath.cloud", + "rotation-id-02"), + OptionalInt.empty(), + RoutingMethod.sharedLayer4)); for (Deployment deployment : defaultDeployments) { - assertEquals("Rotation names are passed to config server in " + deployment.zone(), - Set.of("rotation-id-02", - "app1--tenant1.global.vespa.oath.cloud"), - tester.configServer().containerEndpointNames(defaultContext.deploymentIdIn(deployment.zone()))); + assertEquals(containerEndpoints, + tester.configServer().containerEndpoints().get(defaultContext.deploymentIdIn(deployment.zone()))); } defaultContext.flushDnsUpdates(); } @@ -274,13 +303,17 @@ public class ControllerTest { assertEquals(data, record.get().data().asString()); }); - Map<ApplicationId, List<String>> globalDnsNamesByInstance = Map.of(betaContext.instanceId(), List.of("beta--app1--tenant1.global.vespa.oath.cloud"), - defaultContext.instanceId(), List.of("app1--tenant1.global.vespa.oath.cloud")); + Map<ApplicationId, Set<String>> globalDnsNamesByInstance = Map.of(betaContext.instanceId(), Set.of("beta--app1--tenant1.global.vespa.oath.cloud", + "beta.app1.tenant1.global.vespa.oath.cloud"), + defaultContext.instanceId(), Set.of("app1--tenant1.global.vespa.oath.cloud", + "app1.tenant1.global.vespa.oath.cloud")); globalDnsNamesByInstance.forEach((instance, dnsNames) -> { - List<String> actualDnsNames = tester.controller().routing().readDeclaredEndpointsOf(instance) - .scope(Endpoint.Scope.global) - .mapToList(Endpoint::dnsName); + Set<String> actualDnsNames = tester.controller().routing().readDeclaredEndpointsOf(instance) + .scope(Endpoint.Scope.global) + .asList().stream() + .map(Endpoint::dnsName) + .collect(Collectors.toSet()); assertEquals("Global DNS names for " + instance, dnsNames, actualDnsNames); }); } @@ -617,33 +650,46 @@ public class ControllerTest { @Test public void testDnsUpdatesForApplicationEndpoint() { - var context = tester.newDeploymentContext("tenant1", "app1", "beta"); + ApplicationId beta = ApplicationId.from("tenant1", "app1", "beta"); + ApplicationId main = ApplicationId.from("tenant1", "app1", "main"); + var context = tester.newDeploymentContext(beta); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .instances("beta,main") .region("us-west-1") .region("us-east-3") .applicationEndpoint("a", "default", "us-west-1", - Map.of(InstanceName.from("beta"), 2, - InstanceName.from("main"), 8)) + Map.of(beta.instance(), 2, + main.instance(), 8)) .applicationEndpoint("b", "default", "us-west-1", - Map.of(InstanceName.from("beta"), 1, - InstanceName.from("main"), 1)) + Map.of(beta.instance(), 1, + main.instance(), 1)) .applicationEndpoint("c", "default", "us-east-3", - Map.of(InstanceName.from("beta"), 4, - InstanceName.from("main"), 6)) + Map.of(beta.instance(), 4, + main.instance(), 6)) .build(); context.submit(applicationPackage).deploy(); - // Endpoint names are passed to each deployment - DeploymentId usWest = context.deploymentIdIn(ZoneId.from("prod", "us-west-1")); - DeploymentId usEast = context.deploymentIdIn(ZoneId.from("prod", "us-east-3")); - Map<DeploymentId, List<String>> deploymentEndpoints = Map.of(usWest, List.of("a.app1.tenant1.us-west-1-r.vespa.oath.cloud", "b.app1.tenant1.us-west-1-r.vespa.oath.cloud"), - usEast, List.of("c.app1.tenant1.us-east-3-r.vespa.oath.cloud")); - deploymentEndpoints.forEach((zone, endpointNames) -> { - assertEquals("Endpoint names are passed to config server in " + zone, - Set.of(new ContainerEndpoint("default", "application", - endpointNames)), - tester.configServer().containerEndpoints().get(zone)); + ZoneId usWest = ZoneId.from("prod", "us-west-1"); + ZoneId usEast = ZoneId.from("prod", "us-east-3"); + // Expected container endpoints are passed to each deployment + Map<DeploymentId, Map<String, Integer>> deploymentEndpoints = Map.of( + new DeploymentId(beta, usWest), Map.of("a.app1.tenant1.us-west-1-r.vespa.oath.cloud", 2, + "b.app1.tenant1.us-west-1-r.vespa.oath.cloud", 1), + new DeploymentId(main, usWest), Map.of("a.app1.tenant1.us-west-1-r.vespa.oath.cloud", 8, + "b.app1.tenant1.us-west-1-r.vespa.oath.cloud", 1), + new DeploymentId(beta, usEast), Map.of("c.app1.tenant1.us-east-3-r.vespa.oath.cloud", 4), + new DeploymentId(main, usEast), Map.of("c.app1.tenant1.us-east-3-r.vespa.oath.cloud", 6) + ); + deploymentEndpoints.forEach((deployment, endpoints) -> { + Set<ContainerEndpoint> expected = endpoints.entrySet().stream() + .map(kv -> new ContainerEndpoint("default", "application", + List.of(kv.getKey()), + OptionalInt.of(kv.getValue()), + RoutingMethod.sharedLayer4)) + .collect(Collectors.toSet()); + assertEquals("Endpoint names for " + deployment + " are passed to config server", + expected, + tester.configServer().containerEndpoints().get(deployment)); }); context.flushDnsUpdates(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java index 1d41beb8a99..537f6c48bdf 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java @@ -8,6 +8,7 @@ import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.hosted.controller.ControllerTester; +import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; import com.yahoo.vespa.hosted.controller.api.integration.user.User; import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; @@ -270,4 +271,27 @@ public class UserApiTest extends ControllerContainerCloudTest { new File("user-without-trial-capacity-cloud.json")); } } + + @Test + public void supportTenant() { + try (Flags.Replacer ignored = Flags.clearFlagsForTesting(PermanentFlags.MAX_TRIAL_TENANTS.id(), PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.id())) { + ContainerTester tester = new ContainerTester(container, responseFiles); + ((InMemoryFlagSource) tester.controller().flagSource()) + .withIntFlag(PermanentFlags.MAX_TRIAL_TENANTS.id(), 10) + .withBooleanFlag(PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.id(), true); + ControllerTester controller = new ControllerTester(tester); + User user = new User("dev@domail", "Joe Developer", "dev", null); + + var tenant1 = controller.createTenant("tenant1", Tenant.Type.cloud); + var tenant2 = controller.createTenant("tenant2", Tenant.Type.cloud); + controller.serviceRegistry().billingController().setPlan(tenant2, PlanId.from("paid"), false); + + tester.assertResponse( + request("/user/v1/user") + .roles(Role.reader(tenant1), Role.reader(tenant2)) + .user(user), + new File("user-with-supported-tenant.json")); + } + + } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json index 006c3b98a4d..0211f595ce7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json @@ -10,6 +10,7 @@ }, "tenants": { "sandbox": { + "supported": (ignore), "roles": [ "administrator", "developer", @@ -17,6 +18,7 @@ ] }, "tenant1": { + "supported": (ignore), "roles": [ "administrator", "developer", @@ -24,6 +26,7 @@ ] }, "tenant2": { + "supported": (ignore), "roles": [ "administrator", "developer", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json index 4ae55e97baa..76904bf9bb4 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json @@ -10,17 +10,20 @@ }, "tenants": { "sandbox": { + "supported": false, "roles": [ "developer", "reader" ] }, "tenant1": { + "supported": false, "roles": [ "administrator" ] }, "tenant2": { + "supported": false, "roles": [ "developer" ] diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-supported-tenant.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-supported-tenant.json new file mode 100644 index 00000000000..a40354a9e71 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-supported-tenant.json @@ -0,0 +1,35 @@ +{ + "isPublic": true, + "isCd": false, + "hasTrialCapacity": true, + "user": { + "name": "Joe Developer", + "email": "dev@domail", + "nickname": "dev", + "verified": false + }, + "tenants": { + "tenant1": { + "supported": false, + "roles": [ + "reader" + ] + }, + "tenant2": { + "supported": true, + "roles": [ + "reader" + ] + } + }, + "flags": [ + { + "id": "enable-public-signup-flow", + "rules": [ + { + "value": false + } + ] + } + ] +}
\ No newline at end of file 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 index 9a3ac8b547d..9a56123e8e3 100644 --- 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 @@ -205,10 +205,9 @@ public class RotationRepositoryTest { 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()); + RotationId rotationId = assignedRotations.get(0).rotationId(); + Rotation rotation = repository.requireRotation(rotationId); + assertEquals(expected, rotation); } private static List<RotationId> rotationIds(List<AssignedRotation> assignedRotations) { |