summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java111
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java19
2 files changed, 78 insertions, 52 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
index b1b7e80e9a0..9baa1ad37a1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
@@ -13,14 +13,13 @@ import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
-import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
* Expires unused tenants from Vespa Cloud.
- *
+ * <p>
* TODO: Should support sending notifications some time before the various expiry events happen.
*
* @author ogronnesby
@@ -39,38 +38,43 @@ public class CloudTrialExpirer extends ControllerMaintainer {
@Override
protected double maintain() {
- tombstoneNonePlanTenants();
- moveInactiveTenantsToNonePlan();
- return 1.0;
+ var a = tombstoneNonePlanTenants();
+ var b = moveInactiveTenantsToNonePlan();
+ return (a ? 0.5 : 0.0) + (b ? 0.5 : 0.0);
}
- private void moveInactiveTenantsToNonePlan() {
- var predicate = tenantReadersNotLoggedIn(nonePlanAfter)
- .and(this::tenantHasTrialPlan);
-
- forTenant("'none' plan", predicate, this::setPlanNone);
- }
+ private boolean moveInactiveTenantsToNonePlan() {
+ var idleTrialTenants = controller().tenants().asList().stream()
+ .filter(this::tenantIsCloudTenant)
+ .filter(this::tenantIsNotExemptFromExpiry)
+ .filter(this::tenantHasNoDeployments)
+ .filter(this::tenantHasTrialPlan)
+ .filter(tenantReadersNotLoggedIn(nonePlanAfter))
+ .toList();
+
+ if (! idleTrialTenants.isEmpty()) {
+ var tenants = idleTrialTenants.stream().map(Tenant::name).map(TenantName::value).collect(Collectors.joining(", "));
+ log.info("Setting tenants to 'none' plan: " + tenants);
+ }
- private void tombstoneNonePlanTenants() {
- var predicate = tenantReadersNotLoggedIn(tombstoneAfter).and(this::tenantHasNonePlan);
- forTenant("tombstoned", predicate, this::tombstoneTenants);
+ return setPlanNone(idleTrialTenants);
}
- private void forTenant(String name, Predicate<Tenant> p, Consumer<List<Tenant>> c) {
- var predicate = p.and(this::tenantIsCloudTenant)
- .and(this::tenantIsNotExemptFromExpiry)
- .and(this::tenantHasNoDeployments);
-
- var tenants = controller().tenants().asList().stream()
- .filter(predicate)
- .collect(Collectors.toList());
-
- if (! tenants.isEmpty()) {
- var tenantNames = tenants.stream().map(Tenant::name).map(TenantName::value).collect(Collectors.joining(", "));
- log.info("Setting tenants as " + name + ": " + tenantNames);
+ private boolean tombstoneNonePlanTenants() {
+ var idleOldPlanTenants = controller().tenants().asList().stream()
+ .filter(this::tenantIsCloudTenant)
+ .filter(this::tenantIsNotExemptFromExpiry)
+ .filter(this::tenantHasNoDeployments)
+ .filter(this::tenantHasNonePlan)
+ .filter(tenantReadersNotLoggedIn(tombstoneAfter))
+ .toList();
+
+ if (! idleOldPlanTenants.isEmpty()) {
+ var tenants = idleOldPlanTenants.stream().map(Tenant::name).map(TenantName::value).collect(Collectors.joining(", "));
+ log.info("Setting tenants as tombstoned: " + tenants);
}
- c.accept(tenants);
+ return tombstoneTenants(idleOldPlanTenants);
}
private boolean tenantIsCloudTenant(Tenant tenant) {
@@ -98,7 +102,7 @@ public class CloudTrialExpirer extends ControllerMaintainer {
}
private boolean tenantIsNotExemptFromExpiry(Tenant tenant) {
- return ! extendedTrialTenants.value().contains(tenant.name().value());
+ return !extendedTrialTenants.value().contains(tenant.name().value());
}
private boolean tenantHasNoDeployments(Tenant tenant) {
@@ -108,23 +112,46 @@ public class CloudTrialExpirer extends ControllerMaintainer {
.sum() == 0;
}
- private void setPlanNone(List<Tenant> tenants) {
- tenants.forEach(tenant -> {
- controller().serviceRegistry().billingController().setPlan(tenant.name(), PlanId.from("none"), false, false);
- });
+ private boolean setPlanNone(List<Tenant> tenants) {
+ var success = true;
+ for (var tenant : tenants) {
+ try {
+ controller().serviceRegistry().billingController().setPlan(tenant.name(), PlanId.from("none"), false, false);
+ } catch (RuntimeException e) {
+ log.info("Could not change plan for " + tenant.name() + ": " + e.getMessage());
+ success = false;
+ }
+ }
+ return success;
}
- private void tombstoneTenants(List<Tenant> tenants) {
- tenants.forEach(tenant -> {
- deleteApplicationsWithNoDeployments(tenant);
- controller().tenants().delete(tenant.name(), Optional.empty(), false);
- });
+ private boolean tombstoneTenants(List<Tenant> tenants) {
+ var success = true;
+ for (var tenant : tenants) {
+ success &= deleteApplicationsWithNoDeployments(tenant);
+ log.fine("Tombstoning empty tenant: " + tenant.name());
+ try {
+ controller().tenants().delete(tenant.name(), Optional.empty(), false);
+ } catch (RuntimeException e) {
+ log.info("Could not tombstone tenant " + tenant.name() + ": " + e.getMessage());
+ success = false;
+ }
+ }
+ return success;
}
- private void deleteApplicationsWithNoDeployments(Tenant tenant) {
- controller().applications().asList(tenant.name()).forEach(application -> {
- // this only removes applications with no active deployments
- controller().applications().deleteApplication(application.id(), Optional.empty());
- });
+ private boolean deleteApplicationsWithNoDeployments(Tenant tenant) {
+ // this method only removes applications with no active deployments in them
+ var success = true;
+ for (var application : controller().applications().asList(tenant.name())) {
+ try {
+ log.fine("Removing empty application: " + application.id());
+ controller().applications().deleteApplication(application.id(), Optional.empty());
+ } catch (RuntimeException e) {
+ log.info("Could not removing application " + application.id() + ": " + e.getMessage());
+ success = false;
+ }
+ }
+ return success;
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
index 5ecf28ce7e6..8022067b29f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
@@ -9,7 +9,6 @@ import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
-import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import org.junit.jupiter.api.Test;
@@ -32,28 +31,28 @@ public class CloudTrialExpirerTest {
@Test
void expire_inactive_tenant() {
registerTenant("trial-tenant", "trial", Duration.ofDays(14).plusMillis(1));
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertPlan("trial-tenant", "none");
}
@Test
void tombstone_inactive_none() {
registerTenant("none-tenant", "none", Duration.ofDays(365).plusMillis(1));
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertEquals(Tenant.Type.deleted, tester.controller().tenants().get(TenantName.from("none-tenant"), true).get().type());
}
@Test
void keep_inactive_nontrial_tenants() {
registerTenant("not-a-trial-tenant", "pay-as-you-go", Duration.ofDays(30));
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertPlan("not-a-trial-tenant", "pay-as-you-go");
}
@Test
void keep_active_trial_tenants() {
registerTenant("active-trial-tenant", "trial", Duration.ofHours(14).minusMillis(1));
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertPlan("active-trial-tenant", "trial");
}
@@ -61,7 +60,7 @@ public class CloudTrialExpirerTest {
void keep_inactive_exempt_tenants() {
registerTenant("exempt-trial-tenant", "trial", Duration.ofDays(40));
((InMemoryFlagSource) tester.controller().flagSource()).withListFlag(PermanentFlags.EXTENDED_TRIAL_TENANTS.id(), List.of("exempt-trial-tenant"), String.class);
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertPlan("exempt-trial-tenant", "trial");
}
@@ -69,7 +68,7 @@ public class CloudTrialExpirerTest {
void keep_inactive_trial_tenants_with_deployments() {
registerTenant("with-deployments", "trial", Duration.ofDays(30));
registerDeployment("with-deployments", "my-app", "default");
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertPlan("with-deployments", "trial");
}
@@ -77,16 +76,16 @@ public class CloudTrialExpirerTest {
void delete_tenants_with_applications_with_no_deployments() {
registerTenant("with-apps", "trial", Duration.ofDays(366));
tester.createApplication("with-apps", "app1", "instance1");
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertPlan("with-apps", "none");
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertTrue(tester.controller().tenants().get("with-apps").isEmpty());
}
@Test
void keep_tenants_without_applications_that_are_idle() {
registerTenant("active", "none", Duration.ofDays(364));
- expirer.maintain();
+ assertEquals(1.0, expirer.maintain());
assertPlan("active", "none");
}