aboutsummaryrefslogtreecommitdiffstats
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/Controller.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java109
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/package-info.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java52
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java71
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java66
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java60
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java25
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java28
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java3
19 files changed, 470 insertions, 33 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
index abc0784396c..c6dddc0f223 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
@@ -17,6 +17,7 @@ import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.maven.MavenRepository;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
+import com.yahoo.vespa.hosted.controller.archive.CuratorArchiveBucketDb;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLogger;
import com.yahoo.vespa.hosted.controller.config.ControllerConfig;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
@@ -80,6 +81,7 @@ public class Controller extends AbstractComponent {
private final RoutingController routingController;
private final ControllerConfig controllerConfig;
private final SecretStore secretStore;
+ private final CuratorArchiveBucketDb archiveBucketDb;
/**
* Creates a controller
@@ -115,6 +117,7 @@ public class Controller extends AbstractComponent {
routingController = new RoutingController(this, Objects.requireNonNull(rotationsConfig, "RotationsConfig cannot be null"));
auditLogger = new AuditLogger(curator, clock);
jobControl = new JobControl(new JobControlFlags(curator, flagSource));
+ archiveBucketDb = new CuratorArchiveBucketDb(this);
this.controllerConfig = controllerConfig;
this.secretStore = secretStore;
@@ -302,4 +305,8 @@ public class Controller extends AbstractComponent {
public JobControl jobControl() {
return jobControl;
}
+
+ public CuratorArchiveBucketDb archiveBucketDb() {
+ return archiveBucketDb;
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
index fdcadb6301e..f3e192aef90 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
@@ -92,6 +92,17 @@ public class TenantController {
return get(name).orElseThrow(() -> new IllegalArgumentException("No such tenant '" + name + "'."));
}
+ /** Returns the tenant with the given name, and ensures the type */
+ public <T extends Tenant> T require(TenantName name, Class<T> tenantType) {
+ return get(name)
+ .map(t -> {
+ try { return tenantType.cast(t); } catch (ClassCastException e) {
+ throw new IllegalArgumentException("Tenant '" + name + "' was of type '" + t.getClass().getSimpleName() + "' and not '" + tenantType.getSimpleName() + "'");
+ }
+ })
+ .orElseThrow(() -> new IllegalArgumentException("No such tenant '" + name + "'."));
+ }
+
/** Replace and store any previous version of given tenant */
public void store(LockedTenant tenant) {
curator.writeTenant(tenant.get());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java
new file mode 100644
index 00000000000..8c68acc7c37
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java
@@ -0,0 +1,109 @@
+// Copyright 2021 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.archive;
+
+import com.google.inject.Inject;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.flags.StringFlag;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucketDb;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService;
+import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
+import org.jetbrains.annotations.NotNull;
+
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * This class decides which tenant goes in what bucket, and creates new buckets when required.
+ *
+ * @author andreer
+ */
+public class CuratorArchiveBucketDb implements ArchiveBucketDb {
+
+ /**
+ * Due to policy limits, we can't put data for more than this many tenants in a bucket.
+ * Policy size limit is 20kb, with approx. 500 bytes of policy required per tenant = 40 tenants.
+ * We set the maximum a bit lower to have a solid margin of error.
+ */
+ private final static int TENANTS_PER_BUCKET = 30;
+
+ private final ArchiveService archiveService;
+ private final CuratorDb curatorDb;
+ private final StringFlag bucketNameFlag;
+
+ @Inject
+ public CuratorArchiveBucketDb(Controller controller) {
+ this.archiveService = controller.serviceRegistry().archiveService();
+ this.curatorDb = controller.curator();
+ this.bucketNameFlag = Flags.SYNC_HOST_LOGS_TO_S3_BUCKET.bindTo(controller.flagSource());
+ }
+
+ @Override
+ public Optional<URI> archiveUriFor(ZoneId zoneId, TenantName tenant) {
+ String bucketName = bucketNameFlag
+ .with(FetchVector.Dimension.ZONE_ID, zoneId.value())
+ .with(FetchVector.Dimension.TENANT_ID, tenant.value())
+ .value();
+
+ if (bucketName.isBlank()) return Optional.empty();
+
+ if ("auto".equals(bucketName)) bucketName = findOrAssignBucket(zoneId, tenant);
+
+ return Optional.of(URI.create(String.format("s3://%s/%s/", bucketName, tenant.value())));
+ }
+
+ private String findOrAssignBucket(ZoneId zoneId, TenantName tenant) {
+ var zoneBuckets = curatorDb.readArchiveBuckets(zoneId);
+ return find(tenant, zoneBuckets).orElseGet(() -> assignToBucket(zoneId, tenant));
+ }
+
+ private String assignToBucket(ZoneId zoneId, TenantName tenant) {
+ try (var lock = curatorDb.lockArchiveBuckets(zoneId)) {
+ Set<ArchiveBucket> zoneBuckets = new HashSet<>(curatorDb.readArchiveBuckets(zoneId));
+
+ return find(tenant, zoneBuckets) // Some other thread might have assigned it before we grabbed the lock
+ .orElseGet(() -> {
+ // If not, find an existing bucket with space
+ Optional<ArchiveBucket> unfilledBucket = zoneBuckets.stream()
+ .filter(bucket -> bucket.tenants().size() < TENANTS_PER_BUCKET)
+ .findAny();
+
+ // And place the tenant in that bucket.
+ if (unfilledBucket.isPresent()) {
+ var unfilled = unfilledBucket.get();
+
+ zoneBuckets.remove(unfilled);
+ zoneBuckets.add(unfilled.withTenant(tenant));
+ curatorDb.writeArchiveBuckets(zoneId, zoneBuckets);
+
+ return unfilled.bucketName();
+ }
+
+ // We'll have to create a new bucket
+ var newBucket = archiveService.createArchiveBucketFor(zoneId).withTenant(tenant);
+ zoneBuckets.add(newBucket);
+ curatorDb.writeArchiveBuckets(zoneId, zoneBuckets);
+ return newBucket.bucketName();
+ });
+ }
+ }
+
+ @NotNull
+ private Optional<String> find(TenantName tenant, Set<ArchiveBucket> zoneBuckets) {
+ return zoneBuckets.stream()
+ .filter(bucket -> bucket.tenants().contains(tenant))
+ .findAny()
+ .map(ArchiveBucket::bucketName);
+ }
+
+ @Override
+ public Set<ArchiveBucket> buckets(ZoneId zoneId) {
+ return curatorDb.readArchiveBuckets(zoneId);
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/package-info.java
new file mode 100644
index 00000000000..c93eb56d294
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2021 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.archive;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java
new file mode 100644
index 00000000000..826b411df9e
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java
@@ -0,0 +1,52 @@
+// Copyright 2021 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root
+package com.yahoo.vespa.hosted.controller.maintenance;
+
+import com.google.common.collect.Maps;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucketDb;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService;
+import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.Tenant;
+
+import java.time.Duration;
+import java.util.stream.Collectors;
+
+/**
+ * Update archive access permissions with roles from tenants
+ *
+ * @author andreer
+ */
+public class ArchiveAccessMaintainer extends ControllerMaintainer {
+
+ private final ArchiveBucketDb archiveBucketDb;
+ private final ArchiveService archiveService;
+ private final ZoneRegistry zoneRegistry;
+
+ public ArchiveAccessMaintainer(Controller controller, Duration interval) {
+ super(controller, interval);
+ this.archiveBucketDb = controller.archiveBucketDb();
+ this.archiveService = controller.serviceRegistry().archiveService();
+ this.zoneRegistry = controller().zoneRegistry();
+ }
+
+ @Override
+ protected boolean maintain() {
+ var tenantArchiveAccessRoles = controller().tenants().asList().stream()
+ .filter(t -> t instanceof CloudTenant)
+ .map(t -> (CloudTenant) t)
+ .filter(t -> t.archiveAccessRole().isPresent())
+ .collect(Collectors.toUnmodifiableMap(
+ Tenant::name, cloudTenant -> cloudTenant.archiveAccessRole().orElseThrow()));
+
+ zoneRegistry.zones().controllerUpgraded().ids().forEach(zoneId ->
+ archiveBucketDb.buckets(zoneId).forEach(archiveBucket ->
+ archiveService.updateBucketAndKeyPolicy(zoneId, archiveBucket,
+ Maps.filterEntries(tenantArchiveAccessRoles,
+ entry -> archiveBucket.tenants().contains(entry.getKey())))
+ )
+ );
+
+ return true;
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
index 6b8f2c6b822..faa4813e6b0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
@@ -6,7 +6,7 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucketDb;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
@@ -28,13 +28,13 @@ public class ArchiveUriUpdater extends ControllerMaintainer {
private final ApplicationController applications;
private final NodeRepository nodeRepository;
- private final ArchiveService archiveService;
+ private final ArchiveBucketDb archiveBucketDb;
public ArchiveUriUpdater(Controller controller, Duration duration) {
super(controller, duration, ArchiveUriUpdater.class.getSimpleName(), SystemName.all());
this.applications = controller.applications();
this.nodeRepository = controller.serviceRegistry().configServer().nodeRepository();
- this.archiveService = controller.serviceRegistry().archiveService();
+ this.archiveBucketDb = controller.archiveBucketDb();
}
@Override
@@ -53,13 +53,13 @@ public class ArchiveUriUpdater extends ControllerMaintainer {
tenantsByZone.forEach((zone, tenants) -> {
Map<TenantName, URI> zoneArchiveUris = nodeRepository.getArchiveUris(zone);
for (TenantName tenant : tenants) {
- archiveService.archiveUriFor(zone, tenant)
+ archiveBucketDb.archiveUriFor(zone, tenant)
.filter(uri -> !uri.equals(zoneArchiveUris.get(tenant)))
.ifPresent(uri -> nodeRepository.setArchiveUri(zone, tenant, uri));
}
zoneArchiveUris.keySet().stream()
- .filter(tenant -> ! tenants.contains(tenant))
+ .filter(tenant -> !tenants.contains(tenant))
.forEach(tenant -> nodeRepository.removeArchiveUri(zone, tenant));
});
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
index 8433afaf006..ad41fc7c9e8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
@@ -66,6 +66,7 @@ public class ControllerMaintenance extends AbstractComponent {
maintainers.add(new EndpointCertificateMaintainer(controller, intervals.endpointCertificateMaintainer));
maintainers.add(new TrafficShareUpdater(controller, intervals.trafficFractionUpdater));
maintainers.add(new ArchiveUriUpdater(controller, intervals.archiveUriUpdater));
+ maintainers.add(new ArchiveAccessMaintainer(controller, intervals.archiveAccessMaintainer));
maintainers.add(new TenantRoleMaintainer(controller, intervals.tenantRoleMaintainer));
maintainers.add(new ChangeRequestMaintainer(controller, intervals.changeRequestMaintainer));
}
@@ -119,6 +120,7 @@ public class ControllerMaintenance extends AbstractComponent {
private final Duration endpointCertificateMaintainer;
private final Duration trafficFractionUpdater;
private final Duration archiveUriUpdater;
+ private final Duration archiveAccessMaintainer;
private final Duration tenantRoleMaintainer;
private final Duration changeRequestMaintainer;
@@ -149,6 +151,7 @@ public class ControllerMaintenance extends AbstractComponent {
this.endpointCertificateMaintainer = duration(12, HOURS);
this.trafficFractionUpdater = duration(5, MINUTES);
this.archiveUriUpdater = duration(5, MINUTES);
+ this.archiveAccessMaintainer = duration(10, MINUTES);
this.tenantRoleMaintainer = duration(5, MINUTES);
this.changeRequestMaintainer = duration(12, HOURS);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java
new file mode 100644
index 00000000000..3a625c5c42c
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java
@@ -0,0 +1,71 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.persistence;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Inspector;
+import com.yahoo.slime.Slime;
+import com.yahoo.slime.SlimeUtils;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * (de)serializes tenant/bucket mappings for a zone
+ * <p>
+ *
+ * @author andreer
+ */
+public class ArchiveBucketsSerializer {
+
+ // WARNING: Since there are multiple servers in a ZooKeeper cluster and they upgrade one by one
+ // (and rewrite all nodes on startup), changes to the serialized format must be made
+ // such that what is serialized on version N+1 can be read by version N:
+ // - ADDING FIELDS: Always ok
+ // - REMOVING FIELDS: Stop reading the field first. Stop writing it on a later version.
+ // - CHANGING THE FORMAT OF A FIELD: Don't do it bro.
+
+ private final static String bucketsFieldName = "buckets";
+ private final static String bucketNameFieldName = "bucketName";
+ private final static String keyArnFieldName = "keyArn";
+ private final static String tenantsFieldName = "tenantIds";
+
+ public static Slime toSlime(Set<ArchiveBucket> archiveBuckets) {
+ Slime slime = new Slime();
+ Cursor rootObject = slime.setObject();
+ Cursor bucketsArray = rootObject.setArray(bucketsFieldName);
+
+ archiveBuckets.forEach(bucket -> {
+ Cursor cursor = bucketsArray.addObject();
+ cursor.setString(bucketNameFieldName, bucket.bucketName());
+ cursor.setString(keyArnFieldName, bucket.keyArn());
+ Cursor tenants = cursor.setArray(tenantsFieldName);
+ bucket.tenants().forEach(tenantName -> tenants.addString(tenantName.value()));
+ }
+ );
+
+ return slime;
+ }
+
+ public static Set<ArchiveBucket> fromSlime(Inspector inspector) {
+ return SlimeUtils.entriesStream(inspector.field(bucketsFieldName))
+ .map(ArchiveBucketsSerializer::fromInspector)
+ .collect(Collectors.toUnmodifiableSet());
+ }
+
+ private static ArchiveBucket fromInspector(Inspector inspector) {
+ Set<TenantName> tenants = SlimeUtils.entriesStream(inspector.field(tenantsFieldName))
+ .map(i -> TenantName.from(i.asString()))
+ .collect(Collectors.toUnmodifiableSet());
+
+ return new ArchiveBucket(
+ inspector.field(bucketNameFieldName).asString(),
+ inspector.field(keyArnFieldName).asString())
+ .withTenants(tenants);
+ }
+
+ public static Set<ArchiveBucket> fromJsonString(String zkData) {
+ return fromSlime(SlimeUtils.jsonToSlime(zkData).get());
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
index 010a2e3f8e4..34741bcaedf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
@@ -15,6 +15,7 @@ import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
@@ -87,6 +88,7 @@ public class CuratorDb {
private static final Path routingPoliciesRoot = root.append("routingPolicies");
private static final Path zoneRoutingPoliciesRoot = root.append("zoneRoutingPolicies");
private static final Path endpointCertificateRoot = root.append("applicationCertificates");
+ private static final Path archiveBucketsRoot = root.append("archiveBuckets");
private final NodeVersionSerializer nodeVersionSerializer = new NodeVersionSerializer();
private final VersionStatusSerializer versionStatusSerializer = new VersionStatusSerializer(nodeVersionSerializer);
@@ -198,6 +200,10 @@ public class CuratorDb {
return tryLock(lockRoot.append("meteringRefreshTime"));
}
+ public Lock lockArchiveBuckets(ZoneId zoneId) {
+ return curator.lock(lockRoot.append("archiveBuckets").append(zoneId.value()), defaultLockTimeout);
+ }
+
// -------------- Helpers ------------------------------------------
/** Try locking with a low timeout, meaning it is OK to fail lock acquisition.
@@ -546,6 +552,17 @@ public class CuratorDb {
.orElse(0L);
}
+ // -------------- Archive buckets -----------------------------------------
+
+ public Set<ArchiveBucket> readArchiveBuckets(ZoneId zoneId) {
+ return curator.getData(archiveBucketsPath(zoneId)).map(String::new).map(ArchiveBucketsSerializer::fromJsonString)
+ .orElse(Set.of());
+ }
+
+ public void writeArchiveBuckets(ZoneId zoneid, Set<ArchiveBucket> archiveBuckets) {
+ curator.set(archiveBucketsPath(zoneid), asJson(ArchiveBucketsSerializer.toSlime(archiveBuckets)));
+ }
+
// -------------- Paths ---------------------------------------------------
private Path lockPath(TenantName tenant) {
@@ -667,4 +684,8 @@ public class CuratorDb {
return root.append("meteringRefreshTime");
}
+ private static Path archiveBucketsPath(ZoneId zoneId) {
+ return archiveBucketsRoot.append(zoneId.value());
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index d082cf6fea9..16642ecbfc7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -590,17 +590,13 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse validateSecretStore(String tenantName, String secretStoreName, HttpRequest request) {
-
var awsRegion = request.getProperty("aws-region");
var parameterName = request.getProperty("parameter-name");
var applicationId = ApplicationId.fromFullString(request.getProperty("application-id"));
var zoneId = ZoneId.from(request.getProperty("zone"));
var deploymentId = new DeploymentId(applicationId, zoneId);
- var tenant = (CloudTenant)controller.tenants().require(applicationId.tenant());
- if (tenant.type() != Tenant.Type.cloud) {
- return ErrorResponse.badRequest("Tenant '" + applicationId.tenant() + "' is not a cloud tenant");
- }
+ var tenant = controller.tenants().require(applicationId.tenant(), CloudTenant.class);
var tenantSecretStore = tenant.tenantSecretStores()
.stream()
@@ -630,7 +626,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
String pemDeveloperKey = toSlime(request.getData()).get().field("key").asString();
PublicKey developerKey = KeyUtils.fromPemEncodedPublicKey(pemDeveloperKey);
- Principal user = ((CloudTenant) controller.tenants().require(TenantName.from(tenantName))).developerKeys().get(developerKey);
+ Principal user = controller.tenants().require(TenantName.from(tenantName), CloudTenant.class).developerKeys().get(developerKey);
Slime root = new Slime();
controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, tenant -> {
tenant = tenant.withoutDeveloperKey(developerKey);
@@ -685,7 +681,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
var externalId = mandatory("externalId", data).asString();
var role = mandatory("role", data).asString();
- var tenant = (CloudTenant) controller.tenants().require(TenantName.from(tenantName));
+ var tenant = controller.tenants().require(TenantName.from(tenantName), CloudTenant.class);
var tenantSecretStore = new TenantSecretStore(name, awsId, role);
if (!tenantSecretStore.isValid()) {
@@ -703,14 +699,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
controller.tenants().store(lockedTenant);
});
- tenant = (CloudTenant) controller.tenants().require(TenantName.from(tenantName));
+ tenant = controller.tenants().require(TenantName.from(tenantName), CloudTenant.class);
var slime = new Slime();
toSlime(slime.setObject(), tenant.tenantSecretStores());
return new SlimeJsonResponse(slime);
}
private HttpResponse deleteSecretStore(String tenantName, String name, HttpRequest request) {
- var tenant = (CloudTenant) controller.tenants().require(TenantName.from(tenantName));
+ var tenant = controller.tenants().require(TenantName.from(tenantName), CloudTenant.class);
var optionalSecretStore = tenant.tenantSecretStores().stream()
.filter(secretStore -> secretStore.getName().equals(name))
@@ -727,7 +723,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
controller.tenants().store(lockedTenant);
});
- tenant = (CloudTenant) controller.tenants().require(TenantName.from(tenantName));
+ tenant = controller.tenants().require(TenantName.from(tenantName), CloudTenant.class);
var slime = new Slime();
toSlime(slime.setObject(), tenant.tenantSecretStores());
return new SlimeJsonResponse(slime);
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 039e9b64df7..6e069b2b5ec 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
@@ -21,7 +21,6 @@ import com.yahoo.slime.SlimeUtils;
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.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -76,7 +75,7 @@ public class UserApiHandler extends LoggingRequestHandler {
this.users = users;
this.controller = controller;
this.enable_public_signup_flow = PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource);
- this.maxTrialTenants = Flags.MAX_TRIAL_TENANTS.bindTo(flagSource);
+ this.maxTrialTenants = PermanentFlags.MAX_TRIAL_TENANTS.bindTo(flagSource);
}
@Override
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 563c230e4f0..48f8d3e43cb 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
@@ -6,7 +6,6 @@ import com.yahoo.config.provision.TenantName;
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.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.Application;
@@ -47,7 +46,7 @@ public class CloudAccessControl implements AccessControl {
public CloudAccessControl(UserManagement userManagement, FlagSource flagSource, ServiceRegistry serviceRegistry) {
this.userManagement = userManagement;
this.enablePublicSignup = PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource);
- this.maxTrialTenants = Flags.MAX_TRIAL_TENANTS.bindTo(flagSource);
+ this.maxTrialTenants = PermanentFlags.MAX_TRIAL_TENANTS.bindTo(flagSource);
billingController = serviceRegistry.billingController();
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java
new file mode 100644
index 00000000000..57fa7cc8e44
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java
@@ -0,0 +1,66 @@
+package com.yahoo.vespa.hosted.controller.archive;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.zone.ZoneId;
+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.archive.ArchiveBucket;
+import org.apache.curator.shaded.com.google.common.collect.Streams;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.*;
+
+public class CuratorArchiveBucketDbTest {
+
+ @Test
+ public void archiveUriFor() {
+ ControllerTester tester = new ControllerTester();
+ InMemoryFlagSource flagSource = (InMemoryFlagSource) tester.controller().flagSource();
+ CuratorArchiveBucketDb bucketDb = new CuratorArchiveBucketDb(tester.controller());
+
+ tester.curator().writeArchiveBuckets(ZoneId.defaultId(),
+ Set.of(new ArchiveBucket("existingBucket", "keyArn").withTenant(TenantName.defaultName())));
+
+ // Nothing when feature flag is not set.
+ assertEquals(Optional.empty(), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.defaultName()));
+
+ // Returns hardcoded name from feature flag
+ flagSource.withStringFlag(Flags.SYNC_HOST_LOGS_TO_S3_BUCKET.id(), "hardcoded");
+ assertEquals(Optional.of(URI.create("s3://hardcoded/default/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.defaultName()));
+
+ // Finds existing bucket in db when set to "auto"
+ flagSource.withStringFlag(Flags.SYNC_HOST_LOGS_TO_S3_BUCKET.id(), "auto");
+ assertEquals(Optional.of(URI.create("s3://existingBucket/default/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.defaultName()));
+
+ // Assigns to existing bucket while there is space
+ IntStream.range(0, 29).forEach(i ->
+ assertEquals(
+ Optional.of(URI.create("s3://existingBucket/tenant" + i + "/")), bucketDb
+ .archiveUriFor(ZoneId.defaultId(), TenantName.from("tenant" + i))));
+
+ // Creates new bucket when existing buckets are full
+ assertEquals(Optional.of(URI.create("s3://bucketName/lastDrop/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.from("lastDrop")));
+
+ // Creates new bucket when there are no existing buckets in zone
+ assertEquals(Optional.of(URI.create("s3://bucketName/firstInZone/")), bucketDb.archiveUriFor(ZoneId.from("prod.us-east-3"), TenantName.from("firstInZone")));
+
+ // Lists all buckets by zone
+ Set<TenantName> existingBucketTenants = Streams.concat(Stream.of(TenantName.defaultName()), IntStream.range(0, 29).mapToObj(i -> TenantName.from("tenant" + i))).collect(Collectors.toUnmodifiableSet());
+ assertEquals(
+ Set.of(
+ new ArchiveBucket("existingBucket", "keyArn").withTenants(existingBucketTenants),
+ new ArchiveBucket("bucketName", "keyArn").withTenant(TenantName.from("lastDrop"))),
+ bucketDb.buckets(ZoneId.defaultId()));
+ assertEquals(
+ Set.of(new ArchiveBucket("bucketName", "keyArn").withTenant(TenantName.from("firstInZone"))),
+ bucketDb.buckets(ZoneId.from("prod.us-east-3")));
+ }
+} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java
new file mode 100644
index 00000000000..89072519c7d
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java
@@ -0,0 +1,60 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.maintenance;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.zone.ZoneId;
+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.LockedTenant;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.MockArchiveService;
+import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
+import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest;
+import com.yahoo.vespa.hosted.controller.tenant.Tenant;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author andreer
+ */
+public class ArchiveAccessMaintainerTest extends ControllerContainerCloudTest {
+
+ @Test
+ public void grantsRoleAccess() {
+ var containerTester = new ContainerTester(container, "");
+ ((InMemoryFlagSource) containerTester.controller().flagSource())
+ .withBooleanFlag(PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.id(), true)
+ .withStringFlag(Flags.SYNC_HOST_LOGS_TO_S3_BUCKET.id(), "auto");
+ var tester = new ControllerTester(containerTester);
+
+ String tenant1role = "arn:aws:iam::123456789012:role/my-role";
+ String tenant2role = "arn:aws:iam::210987654321:role/my-role";
+ var tenant1 = createTenantWithAccessRole(tester, "tenant1", tenant1role);
+ createTenantWithAccessRole(tester, "tenant2", tenant2role);
+
+ tester.controller().archiveBucketDb().archiveUriFor(ZoneId.from("prod.us-east-3"), tenant1);
+ var testBucket = new ArchiveBucket("bucketName", "keyArn").withTenant(tenant1);
+
+ MockArchiveService archiveService = (MockArchiveService) tester.controller().serviceRegistry().archiveService();
+ assertNull(archiveService.authorizedIamRoles.get(testBucket));
+ new ArchiveAccessMaintainer(containerTester.controller(), Duration.ofMinutes(10)).maintain();
+ assertEquals(Map.of(tenant1, tenant1role), archiveService.authorizedIamRoles.get(testBucket));
+ }
+
+ private TenantName createTenantWithAccessRole(ControllerTester tester, String tenantName, String role) {
+ var tenant = tester.createTenant(tenantName, Tenant.Type.cloud);
+ tester.controller().tenants().lockOrThrow(tenant, LockedTenant.Cloud.class, lockedTenant -> {
+ lockedTenant = lockedTenant.withArchiveAccessRole(Optional.of(role));
+ tester.controller().tenants().store(lockedTenant);
+ });
+ return tenant;
+ }
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java
index 379331265e5..505536558ab 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java
@@ -5,7 +5,9 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.api.integration.archive.MockArchiveService;
+import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.flags.InMemoryFlagSource;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
@@ -16,7 +18,9 @@ import org.junit.Test;
import java.net.URI;
import java.time.Duration;
+import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
@@ -32,6 +36,9 @@ public class ArchiveUriUpdaterTest {
public void archive_uri_test() {
var updater = new ArchiveUriUpdater(tester.controller(), Duration.ofDays(1));
+ ((InMemoryFlagSource) tester.controller().flagSource())
+ .withStringFlag(Flags.SYNC_HOST_LOGS_TO_S3_BUCKET.id(), "auto");
+
var tenant1 = TenantName.from("tenant1");
var tenant2 = TenantName.from("tenant2");
var tenantInfra = SystemApplication.TENANT;
@@ -43,19 +50,19 @@ public class ArchiveUriUpdaterTest {
assertArchiveUris(Map.of(), zone);
// Archive service now has URI for tenant1, but tenant1 is not deployed in zone
- setArchiveUriInService(Map.of(tenant1, "uri-1"), zone);
- setArchiveUriInService(Map.of(tenantInfra, "uri-3"), zone);
+ setBucketNameInService(Map.of(tenant1, "uri-1"), zone);
+ setBucketNameInService(Map.of(tenantInfra, "uri-3"), zone);
updater.maintain();
assertArchiveUris(Map.of(), zone);
deploy(application, zone);
updater.maintain();
- assertArchiveUris(Map.of(tenant1, "uri-1", tenantInfra, "uri-3"), zone);
+ assertArchiveUris(Map.of(tenant1, "s3://uri-1/tenant1/", tenantInfra, "s3://uri-3/hosted-vespa/"), zone);
// URI for tenant1 should be updated and removed for tenant2
setArchiveUriInNodeRepo(Map.of(tenant1, "wrong-uri", tenant2, "uri-2"), zone);
updater.maintain();
- assertArchiveUris(Map.of(tenant1, "uri-1", tenantInfra, "uri-3"), zone);
+ assertArchiveUris(Map.of(tenant1, "s3://uri-1/tenant1/", tenantInfra, "s3://uri-3/hosted-vespa/"), zone);
}
private void assertArchiveUris(Map<TenantName, String> expectedUris, ZoneId zone) {
@@ -65,9 +72,11 @@ public class ArchiveUriUpdaterTest {
assertEquals(expectedUris, actualUris);
}
- private void setArchiveUriInService(Map<TenantName, String> archiveUris, ZoneId zone) {
- MockArchiveService archiveService = (MockArchiveService) tester.controller().serviceRegistry().archiveService();
- archiveUris.forEach((tenant, uri) -> archiveService.setArchiveUri(zone, tenant, URI.create(uri)));
+ private void setBucketNameInService(Map<TenantName, String> bucketNames, ZoneId zone) {
+ var archiveBuckets = new LinkedHashSet<>(tester.controller().curator().readArchiveBuckets(zone));
+ bucketNames.forEach((tenantName, bucketName) ->
+ archiveBuckets.add(new ArchiveBucket(bucketName, "keyArn").withTenant(tenantName)));
+ tester.controller().curator().writeArchiveBuckets(zone, archiveBuckets);
}
private void setArchiveUriInNodeRepo(Map<TenantName, String> archiveUris, ZoneId zone) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java
new file mode 100644
index 00000000000..17814b12a09
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java
@@ -0,0 +1,28 @@
+package com.yahoo.vespa.hosted.controller.persistence;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
+import org.junit.Test;
+
+import java.util.LinkedHashSet;
+
+import static org.junit.Assert.assertEquals;
+
+public class ArchiveBucketsSerializerTest {
+
+ @Test
+ public void serdes() {
+ var testTenants = new LinkedHashSet<TenantName>();
+ testTenants.add(TenantName.from("tenant1"));
+ testTenants.add(TenantName.from("tenant2"));
+
+ var testBuckets = new LinkedHashSet<ArchiveBucket>();
+ testBuckets.add(new ArchiveBucket("bucket1Name", "key1Arn").withTenants(testTenants));
+ testBuckets.add(new ArchiveBucket("bucket2Name", "key2Arn"));
+
+ String zkData = "{\"buckets\":[{\"bucketName\":\"bucket1Name\",\"keyArn\":\"key1Arn\",\"tenantIds\":[\"tenant1\",\"tenant2\"]},{\"bucketName\":\"bucket2Name\",\"keyArn\":\"key2Arn\",\"tenantIds\":[]}]}";
+
+ assertEquals(testBuckets, ArchiveBucketsSerializer.fromJsonString(zkData));
+ assertEquals(testBuckets, ArchiveBucketsSerializer.fromJsonString(ArchiveBucketsSerializer.toSlime(testBuckets).toString()));
+ }
+}
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 4fb91639daa..7074d0d7354 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
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.LockedTenant;
@@ -107,7 +106,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
@Test
public void trial_tenant_limit_reached() {
- ((InMemoryFlagSource) tester.controller().flagSource()).withIntFlag(Flags.MAX_TRIAL_TENANTS.id(), 1);
+ ((InMemoryFlagSource) tester.controller().flagSource()).withIntFlag(PermanentFlags.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
@@ -190,10 +189,10 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
lockedTenant = lockedTenant.withSecretStore(new TenantSecretStore("secret-foo", "123", "some-role"));
tester.controller().tenants().store(lockedTenant);
});
- var tenant = (CloudTenant) tester.controller().tenants().require(tenantName);
+ var tenant = tester.controller().tenants().require(tenantName, CloudTenant.class);
assertEquals(1, tenant.tenantSecretStores().size());
tester.assertResponse(deleteRequest, "{\"secretStores\":[]}", 200);
- tenant = (CloudTenant) tester.controller().tenants().require(tenantName);
+ tenant = tester.controller().tenants().require(tenantName, CloudTenant.class);
assertEquals(0, tenant.tenantSecretStores().size());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
index 08741e7f38a..17c93c070fb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
@@ -7,6 +7,9 @@
"name": "ApplicationOwnershipConfirmer"
},
{
+ "name": "ArchiveAccessMaintainer"
+ },
+ {
"name": "ArchiveUriUpdater"
},
{
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 1ad705be0b7..03f1d75a50b 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
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller.restapi.user;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
-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;
@@ -255,7 +254,7 @@ public class UserApiTest extends ControllerContainerCloudTest {
public void maxTrialTenants() {
ContainerTester tester = new ContainerTester(container, responseFiles);
((InMemoryFlagSource) tester.controller().flagSource())
- .withIntFlag(Flags.MAX_TRIAL_TENANTS.id(), 1)
+ .withIntFlag(PermanentFlags.MAX_TRIAL_TENANTS.id(), 1)
.withBooleanFlag(PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.id(), true);
ControllerTester controller = new ControllerTester(tester);
Set<Role> operator = Set.of(Role.hostedOperator(), Role.hostedSupporter(), Role.hostedAccountant());