summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java17
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java26
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java6
5 files changed, 59 insertions, 0 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
index 0fc20095b41..1bd1faf5dd1 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
@@ -16,6 +16,8 @@ public interface BillingController {
PlanId getPlan(TenantName tenant);
+ Map<TenantName, PlanId> getPlans(List<TenantName> tenants);
+
String getPlanDisplayName(PlanId planId);
Quota getQuota(TenantName tenant);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
index 21eada37ab1..3f241510ed6 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
@@ -34,6 +34,14 @@ public class MockBillingController implements BillingController {
}
@Override
+ public Map<TenantName, PlanId> getPlans(List<TenantName> tenants) {
+ return tenants.stream().collect(Collectors.toMap(
+ (TenantName t) -> t,
+ (TenantName t) -> plans.getOrDefault(t, PlanId.from("trial"))
+ ));
+ }
+
+ @Override
public String getPlanDisplayName(PlanId planId) {
return "Plan with id: " + planId.value();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
index 6d2b2d58e78..f7b7f379129 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
@@ -7,9 +7,11 @@ import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.flags.IntFlag;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
+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.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.user.UserManagement;
@@ -34,18 +36,21 @@ public class CloudAccessControl implements AccessControl {
private final UserManagement userManagement;
private final BooleanFlag enablePublicSignup;
+ private final IntFlag maxTrialTenants;
private final BillingController billingController;
@Inject
public CloudAccessControl(UserManagement userManagement, FlagSource flagSource, ServiceRegistry serviceRegistry) {
this.userManagement = userManagement;
this.enablePublicSignup = Flags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource);
+ this.maxTrialTenants = Flags.MAX_TRIAL_TENANTS.bindTo(flagSource);
billingController = serviceRegistry.billingController();
}
@Override
public CloudTenant createTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing) {
requireTenantCreationAllowed((Auth0Credentials) credentials);
+ requireTenantTrialLimitNotReached(existing);
CloudTenantSpec spec = (CloudTenantSpec) tenantSpec;
CloudTenant tenant = CloudTenant.create(spec.tenant(), credentials.user());
@@ -62,6 +67,18 @@ public class CloudAccessControl implements AccessControl {
return tenant;
}
+ private void requireTenantTrialLimitNotReached(List<Tenant> existing) {
+ var trialPlanId = PlanId.from("trial");
+ var tenantNames = existing.stream().map(Tenant::name).collect(Collectors.toList());
+ var trialTenants = billingController.getPlans(tenantNames).values().stream()
+ .filter(trialPlanId::equals)
+ .count();
+
+ if (maxTrialTenants.value() >= 0 && maxTrialTenants.value() <= trialTenants) {
+ throw new ForbiddenException("Too many tenants with trial plans, please contact the Vespa support team");
+ }
+ }
+
private void requireTenantCreationAllowed(Auth0Credentials auth0Credentials) {
if (allowedByPrivilegedRole(auth0Credentials)) return;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
index 5b33f989163..1da7bdeca19 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.hosted.controller.ControllerTester;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
@@ -19,6 +20,7 @@ import com.yahoo.vespa.hosted.controller.security.Credentials;
import org.junit.Before;
import org.junit.Test;
+import javax.ws.rs.ForbiddenException;
import java.util.Collections;
import java.util.Set;
@@ -26,6 +28,8 @@ import static com.yahoo.application.container.handler.Request.Method.GET;
import static com.yahoo.application.container.handler.Request.Method.PUT;
import static com.yahoo.application.container.handler.Request.Method.POST;
import static com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiTest.createApplicationSubmissionData;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
@@ -94,6 +98,24 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
tester.assertResponse(infoRequest, fullInfo, 200);
}
+ @Test
+ public void trial_tenant_limit_reached() {
+ ((InMemoryFlagSource) tester.controller().flagSource()).withIntFlag(Flags.MAX_TRIAL_TENANTS.id(), 1);
+ tester.controller().serviceRegistry().billingController().setPlan(tenantName, PlanId.from("pay-as-you-go"), false);
+
+ // tests that we can create the one trial tenant the flag says we can have -- and that the tenant created
+ // in @Before does not count towards that limit.
+ tester.controller().tenants().create(tenantSpec("tenant1"), credentials("administrator"));
+
+ // tests that exceeding the limit throws a ForbiddenException
+ try {
+ tester.controller().tenants().create(tenantSpec("tenant2"), credentials("administrator"));
+ fail("Should not be allowed to create tenant that exceed trial limit");
+ } catch (ForbiddenException e) {
+ assertEquals("Too many tenants with trial plans, please contact the Vespa support team", e.getMessage());
+ }
+ }
+
private ApplicationPackageBuilder prodBuilder() {
return new ApplicationPackageBuilder()
.instances("default")
@@ -108,6 +130,10 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
tester.controller().applications().createApplication(appId, credentials("developer@scoober"));
}
+ private static CloudTenantSpec tenantSpec(String name) {
+ return new CloudTenantSpec(TenantName.from(name), "");
+ }
+
private static Credentials credentials(String name) {
return new Auth0Credentials(() -> name, Collections.emptySet());
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index f087714896b..ab9dcd6415d 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -254,6 +254,12 @@ public class Flags {
CONSOLE_USER_EMAIL
);
+ public static final UnboundIntFlag MAX_TRIAL_TENANTS = defineIntFlag(
+ "max-trial-tenants", -1,
+ "The maximum nr. of tenants with trial plan, -1 is unlimited",
+ "Takes effect immediately"
+ );
+
public static final UnboundBooleanFlag CONTROLLER_PROVISION_LB = defineFeatureFlag(
"controller-provision-lb", false,
"Provision load balancer for controller cluster",