aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorValerij Fredriksen <valerijf@yahooinc.com>2023-03-02 14:15:28 +0100
committerValerij Fredriksen <valerijf@yahooinc.com>2023-03-02 15:57:32 +0100
commit51dd527f03628a0fe37babdb9a105111a7c7d40a (patch)
tree44a2c47003761422627fbe8297b539ef1cfe21e7
parentd548d85092b289702c484ffe6ccb001c00232545 (diff)
Store tenant managed archive buckets in ZK
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveBuckets.java34
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java12
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/TenantManagedArchiveBucket.java15
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/VespaManagedArchiveBucket.java (renamed from controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveBucket.java)17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java52
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java61
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java17
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java33
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java25
13 files changed, 168 insertions, 127 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveBuckets.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveBuckets.java
new file mode 100644
index 00000000000..62e341c674c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveBuckets.java
@@ -0,0 +1,34 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.archive;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author freva
+ */
+public record ArchiveBuckets(Set<VespaManagedArchiveBucket> vespaManaged,
+ Set<TenantManagedArchiveBucket> tenantManaged) {
+ public static final ArchiveBuckets EMPTY = new ArchiveBuckets(Set.of(), Set.of());
+
+ public ArchiveBuckets(Set<VespaManagedArchiveBucket> vespaManaged, Set<TenantManagedArchiveBucket> tenantManaged) {
+ this.vespaManaged = Set.copyOf(vespaManaged);
+ this.tenantManaged = Set.copyOf(tenantManaged);
+ }
+
+ /** Adds or replaces a VespaManagedArchive bucket with the given archive bucket */
+ public ArchiveBuckets with(VespaManagedArchiveBucket vespaManagedArchiveBucket) {
+ Set<VespaManagedArchiveBucket> updated = new HashSet<>(vespaManaged);
+ updated.removeIf(bucket -> bucket.bucketName().equals(vespaManagedArchiveBucket.bucketName()));
+ updated.add(vespaManagedArchiveBucket);
+ return new ArchiveBuckets(updated, tenantManaged);
+ }
+
+ /** Adds or replaces a TenantManagedArchive bucket with the given archive bucket */
+ public ArchiveBuckets with(TenantManagedArchiveBucket tenantManagedArchiveBucket) {
+ Set<TenantManagedArchiveBucket> updated = new HashSet<>(tenantManaged);
+ updated.removeIf(bucket -> bucket.cloudAccount().equals(tenantManagedArchiveBucket.cloudAccount()));
+ updated.add(tenantManagedArchiveBucket);
+ return new ArchiveBuckets(vespaManaged, updated);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java
index 71425ca4b79..ed965f4331e 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java
@@ -19,11 +19,11 @@ import java.util.Set;
*/
public interface ArchiveService {
- ArchiveBucket createArchiveBucketFor(ZoneId zoneId);
+ VespaManagedArchiveBucket createArchiveBucketFor(ZoneId zoneId);
- void updatePolicies(ZoneId zoneId, Set<ArchiveBucket> buckets, Map<TenantName,ArchiveAccess> authorizeAccessByTenantName);
+ void updatePolicies(ZoneId zoneId, Set<VespaManagedArchiveBucket> buckets, Map<TenantName,ArchiveAccess> authorizeAccessByTenantName);
- boolean canAddTenantToBucket(ZoneId zoneId, ArchiveBucket bucket);
+ boolean canAddTenantToBucket(ZoneId zoneId, VespaManagedArchiveBucket bucket);
Optional<String> findEnclaveArchiveBucket(ZoneId zoneId, CloudAccount cloudAccount);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java
index 17f90f13988..4bed7c5177f 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java
@@ -19,24 +19,22 @@ import java.util.Set;
*/
public class MockArchiveService implements ArchiveService {
-
- public Set<ArchiveBucket> archiveBuckets = new HashSet<>();
+ public Set<VespaManagedArchiveBucket> archiveBuckets = new HashSet<>();
public Map<TenantName, ArchiveAccess> authorizeAccessByTenantName = new HashMap<>();
-
@Override
- public ArchiveBucket createArchiveBucketFor(ZoneId zoneId) {
- return new ArchiveBucket("bucketName", "keyArn");
+ public VespaManagedArchiveBucket createArchiveBucketFor(ZoneId zoneId) {
+ return new VespaManagedArchiveBucket("bucketName", "keyArn");
}
@Override
- public void updatePolicies(ZoneId zoneId, Set<ArchiveBucket> buckets, Map<TenantName, ArchiveAccess> authorizeAccessByTenantName) {
+ public void updatePolicies(ZoneId zoneId, Set<VespaManagedArchiveBucket> buckets, Map<TenantName, ArchiveAccess> authorizeAccessByTenantName) {
this.archiveBuckets = new HashSet<>(buckets);
this.authorizeAccessByTenantName = new HashMap<>(authorizeAccessByTenantName);
}
@Override
- public boolean canAddTenantToBucket(ZoneId zoneId, ArchiveBucket bucket) {
+ public boolean canAddTenantToBucket(ZoneId zoneId, VespaManagedArchiveBucket bucket) {
return bucket.tenants().size() < 5;
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/TenantManagedArchiveBucket.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/TenantManagedArchiveBucket.java
new file mode 100644
index 00000000000..80e9762f84b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/TenantManagedArchiveBucket.java
@@ -0,0 +1,15 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.archive;
+
+import com.yahoo.config.provision.CloudAccount;
+
+import java.time.Instant;
+
+/**
+ * Represents a cloud storage bucket (e.g. AWS S3 or Google Storage) used to store archive data - logs, heap/core dumps, etc.
+ * that is managed by the tenant directly.
+ *
+ * @author freva
+ */
+public record TenantManagedArchiveBucket(String bucketName, CloudAccount cloudAccount, Instant updatedAt) {
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveBucket.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/VespaManagedArchiveBucket.java
index be3b87ddc5c..c80e9b3780d 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveBucket.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/VespaManagedArchiveBucket.java
@@ -8,20 +8,21 @@ import java.util.Objects;
import java.util.Set;
/**
- * Represents an S3 bucket used to store archive data - logs, heap/core dumps, etc.
+ * Represents a cloud storage bucket (e.g. AWS S3 or Google Storage) used to store archive data - logs, heap/core dumps, etc.
+ * that is managed by the Vespa controller.
*
* @author andreer
*/
-public class ArchiveBucket {
+public class VespaManagedArchiveBucket {
private final String bucketName;
private final String keyArn;
private final Set<TenantName> tenants;
- public ArchiveBucket(String bucketName, String keyArn) {
+ public VespaManagedArchiveBucket(String bucketName, String keyArn) {
this(bucketName, keyArn, Set.of());
}
- private ArchiveBucket(String bucketName, String keyArn, Set<TenantName> tenants) {
+ private VespaManagedArchiveBucket(String bucketName, String keyArn, Set<TenantName> tenants) {
this.bucketName = bucketName;
this.keyArn = keyArn;
this.tenants = Set.copyOf(tenants);
@@ -39,19 +40,19 @@ public class ArchiveBucket {
return tenants;
}
- public ArchiveBucket withTenant(TenantName tenant) {
+ public VespaManagedArchiveBucket withTenant(TenantName tenant) {
return withTenants(Set.of(tenant));
}
- public ArchiveBucket withTenants(Set<TenantName> tenants) {
- return new ArchiveBucket(bucketName, keyArn, Sets.union(this.tenants, tenants));
+ public VespaManagedArchiveBucket withTenants(Set<TenantName> tenants) {
+ return new VespaManagedArchiveBucket(bucketName, keyArn, Sets.union(this.tenants, tenants));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- ArchiveBucket that = (ArchiveBucket) o;
+ VespaManagedArchiveBucket that = (VespaManagedArchiveBucket) o;
return bucketName.equals(that.bucketName) && keyArn.equals(that.keyArn) && tenants.equals(that.tenants);
}
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
index 1c930b04989..e9734359647 100644
--- 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
@@ -4,15 +4,14 @@ package com.yahoo.vespa.hosted.controller.archive;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
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.ArchiveBuckets;
import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.VespaManagedArchiveBucket;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import java.net.URI;
-import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -39,62 +38,43 @@ public class CuratorArchiveBucketDb {
public Optional<URI> archiveUriFor(ZoneId zoneId, TenantName tenant, boolean createIfMissing) {
return getBucketNameFromCache(zoneId, tenant)
- .or(() -> findAndUpdateArchiveUriCache(zoneId, tenant, buckets(zoneId)))
.or(() -> createIfMissing ? Optional.of(assignToBucket(zoneId, tenant)) : Optional.empty())
.map(bucketName -> archiveService.bucketURI(zoneId, bucketName));
}
private String assignToBucket(ZoneId zoneId, TenantName tenant) {
try (var lock = curatorDb.lockArchiveBuckets(zoneId)) {
- Set<ArchiveBucket> zoneBuckets = new HashSet<>(buckets(zoneId));
+ ArchiveBuckets archiveBuckets = buckets(zoneId);
+ updateArchiveUriCache(zoneId, archiveBuckets);
- return findAndUpdateArchiveUriCache(zoneId, tenant, zoneBuckets) // Some other thread might have assigned it before we grabbed the lock
+ return getBucketNameFromCache(zoneId, tenant) // 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()
+ VespaManagedArchiveBucket bucketToAssignTo = archiveBuckets.vespaManaged().stream()
.filter(bucket -> archiveService.canAddTenantToBucket(zoneId, bucket))
- .findAny();
+ .findAny()
+ // Or create a new one
+ .orElseGet(() -> archiveService.createArchiveBucketFor(zoneId));
- // And place the tenant in that bucket.
- if (unfilledBucket.isPresent()) {
- var unfilled = unfilledBucket.get();
+ ArchiveBuckets updated = archiveBuckets.with(bucketToAssignTo.withTenant(tenant));
+ curatorDb.writeArchiveBuckets(zoneId, updated);
+ updateArchiveUriCache(zoneId, updated);
- 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);
- updateArchiveUriCache(zoneId, zoneBuckets);
- return newBucket.bucketName();
+ return bucketToAssignTo.bucketName();
});
}
}
- public Set<ArchiveBucket> buckets(ZoneId zoneId) {
+ public ArchiveBuckets buckets(ZoneId zoneId) {
return curatorDb.readArchiveBuckets(zoneId);
}
- private Optional<String> findAndUpdateArchiveUriCache(ZoneId zoneId, TenantName tenant, Set<ArchiveBucket> zoneBuckets) {
- Optional<String> bucketName = zoneBuckets.stream()
- .filter(bucket -> bucket.tenants().contains(tenant))
- .findAny()
- .map(ArchiveBucket::bucketName);
- if (bucketName.isPresent()) updateArchiveUriCache(zoneId, zoneBuckets);
- return bucketName;
- }
-
private Optional<String> getBucketNameFromCache(ZoneId zoneId, TenantName tenantName) {
return Optional.ofNullable(archiveUriCache.get(zoneId)).map(map -> map.get(tenantName));
}
- private void updateArchiveUriCache(ZoneId zoneId, Set<ArchiveBucket> zoneBuckets) {
- Map<TenantName, String> bucketNameByTenant = zoneBuckets.stream()
+ private void updateArchiveUriCache(ZoneId zoneId, ArchiveBuckets archiveBuckets) {
+ Map<TenantName, String> bucketNameByTenant = archiveBuckets.vespaManaged().stream()
.flatMap(bucket -> bucket.tenants().stream()
.map(tenant -> Map.entry(tenant, bucket.bucketName())))
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
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
index eed4fd0245d..b2ed0941c8e 100644
--- 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
@@ -1,12 +1,10 @@
// Copyright Yahoo. 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.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.jdisc.Metric;
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.ArchiveService;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import com.yahoo.vespa.hosted.controller.archive.CuratorArchiveBucketDb;
@@ -17,11 +15,8 @@ import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.time.Duration;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.stream.Collectors;
-import static java.util.stream.Collectors.groupingBy;
-
/**
* Update archive access permissions with roles from tenants
*
@@ -48,7 +43,7 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer {
protected double maintain() {
// Count buckets - so we can alert if we get close to the AWS account limit of 1000
zoneRegistry.zonesIncludingSystem().all().zones().forEach(z ->
- metric.set(bucketCountMetricName, archiveBucketDb.buckets(z.getVirtualId()).size(),
+ metric.set(bucketCountMetricName, archiveBucketDb.buckets(z.getVirtualId()).vespaManaged().size(),
metric.createContext(Map.of(
"zone", z.getVirtualId().value(),
"cloud", z.getCloudName().value()))));
@@ -57,7 +52,7 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer {
ZoneId zoneId = z.getVirtualId();
try {
var tenantArchiveAccessRoles = cloudTenantArchiveExternalAccessRoles();
- var buckets = archiveBucketDb.buckets(zoneId);
+ var buckets = archiveBucketDb.buckets(zoneId).vespaManaged();
archiveService.updatePolicies(zoneId, buckets, tenantArchiveAccessRoles);
} catch (Exception e) {
throw new RuntimeException("Failed to maintain archive access in " + zoneId.value(), e);
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
index a4c7c50085c..f40193510ce 100644
--- 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
@@ -1,12 +1,15 @@
// Copyright Yahoo. 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.CloudAccount;
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 com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBuckets;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.TenantManagedArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.VespaManagedArchiveBucket;
import java.util.Set;
import java.util.stream.Collectors;
@@ -25,46 +28,64 @@ public class ArchiveBucketsSerializer {
// - 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 vespaManagedBucketsFieldName = "buckets";
+ private final static String tenantManagedBucketsFieldName = "tenantManagedBuckets";
private final static String bucketNameFieldName = "bucketName";
private final static String keyArnFieldName = "keyArn";
private final static String tenantsFieldName = "tenantIds";
+ private final static String accountFieldName = "account";
+ private final static String updatedAtFieldName = "updatedAt";
- public static Slime toSlime(Set<ArchiveBucket> archiveBuckets) {
+ public static Slime toSlime(ArchiveBuckets 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()));
- }
- );
+ Cursor vespaBucketsArray = rootObject.setArray(vespaManagedBucketsFieldName);
+ archiveBuckets.vespaManaged().forEach(bucket -> {
+ Cursor cursor = vespaBucketsArray.addObject();
+ cursor.setString(bucketNameFieldName, bucket.bucketName());
+ cursor.setString(keyArnFieldName, bucket.keyArn());
+ Cursor tenants = cursor.setArray(tenantsFieldName);
+ bucket.tenants().forEach(tenantName -> tenants.addString(tenantName.value()));
+ });
+
+ Cursor tenantBucketsArray = rootObject.setArray(tenantManagedBucketsFieldName);
+ archiveBuckets.tenantManaged().forEach(bucket -> {
+ Cursor cursor = tenantBucketsArray.addObject();
+ cursor.setString(bucketNameFieldName, bucket.bucketName());
+ cursor.setString(accountFieldName, bucket.cloudAccount().value());
+ cursor.setLong(updatedAtFieldName, bucket.updatedAt().toEpochMilli());
+ });
return slime;
}
- public static Set<ArchiveBucket> fromSlime(Inspector inspector) {
- return SlimeUtils.entriesStream(inspector.field(bucketsFieldName))
- .map(ArchiveBucketsSerializer::fromInspector)
- .collect(Collectors.toUnmodifiableSet());
+ public static ArchiveBuckets fromSlime(Slime slime) {
+ Inspector inspector = slime.get();
+ return new ArchiveBuckets(
+ SlimeUtils.entriesStream(inspector.field(vespaManagedBucketsFieldName))
+ .map(ArchiveBucketsSerializer::vespaManagedArchiveBucketFromInspector)
+ .collect(Collectors.toUnmodifiableSet()),
+ SlimeUtils.entriesStream(inspector.field(tenantManagedBucketsFieldName))
+ .map(ArchiveBucketsSerializer::tenantManagedArchiveBucketFromInspector)
+ .collect(Collectors.toUnmodifiableSet()));
}
- private static ArchiveBucket fromInspector(Inspector inspector) {
+ private static VespaManagedArchiveBucket vespaManagedArchiveBucketFromInspector(Inspector inspector) {
Set<TenantName> tenants = SlimeUtils.entriesStream(inspector.field(tenantsFieldName))
.map(i -> TenantName.from(i.asString()))
.collect(Collectors.toUnmodifiableSet());
- return new ArchiveBucket(
+ return new VespaManagedArchiveBucket(
inspector.field(bucketNameFieldName).asString(),
inspector.field(keyArnFieldName).asString())
.withTenants(tenants);
}
- public static Set<ArchiveBucket> fromJsonString(String zkData) {
- return fromSlime(SlimeUtils.jsonToSlime(zkData).get());
+ private static TenantManagedArchiveBucket tenantManagedArchiveBucketFromInspector(Inspector inspector) {
+ return new TenantManagedArchiveBucket(
+ inspector.field(bucketNameFieldName).asString(),
+ CloudAccount.from(inspector.field(accountFieldName).asString()),
+ SlimeUtils.instant(inspector.field(updatedAtFieldName)));
}
}
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 f4980073d6c..d4e6d7af4b4 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
@@ -19,7 +19,7 @@ import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.identifiers.ClusterId;
import com.yahoo.vespa.hosted.controller.api.identifiers.ControllerVersion;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBuckets;
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;
@@ -632,12 +632,12 @@ public class CuratorDb {
// -------------- Archive buckets -----------------------------------------
- public Set<ArchiveBucket> readArchiveBuckets(ZoneId zoneId) {
- return curator.getData(archiveBucketsPath(zoneId)).map(String::new).map(ArchiveBucketsSerializer::fromJsonString)
- .orElseGet(Set::of);
+ public ArchiveBuckets readArchiveBuckets(ZoneId zoneId) {
+ return readSlime(archiveBucketsPath(zoneId)).map(ArchiveBucketsSerializer::fromSlime)
+ .orElse(ArchiveBuckets.EMPTY);
}
- public void writeArchiveBuckets(ZoneId zoneid, Set<ArchiveBucket> archiveBuckets) {
+ public void writeArchiveBuckets(ZoneId zoneid, ArchiveBuckets archiveBuckets) {
curator.set(archiveBucketsPath(zoneid), asJson(ArchiveBucketsSerializer.toSlime(archiveBuckets)));
}
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
index 8bad8efc322..9770133dcfb 100644
--- 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
@@ -5,7 +5,8 @@ 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.ControllerTester;
-import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBuckets;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.VespaManagedArchiveBucket;
import org.apache.curator.shaded.com.google.common.collect.Streams;
import org.junit.jupiter.api.Test;
@@ -21,12 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class CuratorArchiveBucketDbTest {
@Test
- void archiveUriFor() {
+ void archiveUriForTenant() {
ControllerTester tester = new ControllerTester(SystemName.Public);
CuratorArchiveBucketDb bucketDb = new CuratorArchiveBucketDb(tester.controller());
tester.curator().writeArchiveBuckets(ZoneId.defaultId(),
- Set.of(new ArchiveBucket("existingBucket", "keyArn").withTenant(TenantName.defaultName())));
+ ArchiveBuckets.EMPTY.with(new VespaManagedArchiveBucket("existingBucket", "keyArn").withTenant(TenantName.defaultName())));
// Finds existing bucket in db
assertEquals(Optional.of(URI.create("s3://existingBucket/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.defaultName(), true));
@@ -50,11 +51,11 @@ public class CuratorArchiveBucketDbTest {
Set<TenantName> existingBucketTenants = Streams.concat(Stream.of(TenantName.defaultName()), IntStream.range(0, 4).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()));
+ new VespaManagedArchiveBucket("existingBucket", "keyArn").withTenants(existingBucketTenants),
+ new VespaManagedArchiveBucket("bucketName", "keyArn").withTenant(TenantName.from("lastDrop"))),
+ bucketDb.buckets(ZoneId.defaultId()).vespaManaged());
assertEquals(
- Set.of(new ArchiveBucket("bucketName", "keyArn").withTenant(TenantName.from("firstInZone"))),
- bucketDb.buckets(ZoneId.from("prod.us-east-3")));
+ Set.of(new VespaManagedArchiveBucket("bucketName", "keyArn").withTenant(TenantName.from("firstInZone"))),
+ bucketDb.buckets(ZoneId.from("prod.us-east-3")).vespaManaged());
}
}
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
index c10b77d853a..0490a9bdcc5 100644
--- 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
@@ -7,7 +7,6 @@ import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.jdisc.test.MockMetric;
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.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
@@ -15,8 +14,6 @@ import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -37,7 +34,6 @@ public class ArchiveAccessMaintainerTest {
ZoneId testZone = ZoneId.from("prod.aws-us-east-1c");
tester.controller().archiveBucketDb().archiveUriFor(testZone, tenant1, true);
- var testBucket = new ArchiveBucket("bucketName", "keyArn").withTenant(tenant1);
MockArchiveService archiveService = (MockArchiveService) tester.controller().serviceRegistry().archiveService();
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 7c487e7249b..ad37c4a985d 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
@@ -1,12 +1,15 @@
// Copyright Yahoo. 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.CloudAccount;
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.ControllerTester;
-import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBuckets;
import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveUriUpdate;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.VespaManagedArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ArchiveUris;
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.SystemApplication;
@@ -17,7 +20,6 @@ import org.junit.jupiter.api.Test;
import java.net.URI;
import java.time.Duration;
-import java.util.LinkedHashSet;
import java.util.Map;
import java.util.stream.Collectors;
@@ -42,36 +44,35 @@ public class ArchiveUriUpdaterTest {
// Initially we should only is the bucket for hosted-vespa tenant
updater.maintain();
- assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/"), zone);
- assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/"), ZoneId.from("prod", "controller"));
+ assertArchiveUris(zone, Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/"), Map.of());
+ assertArchiveUris(ZoneId.from("prod", "controller"), Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/"), Map.of());
// Archive service now has URI for tenant1, but tenant1 is not deployed in zone
setBucketNameInService(Map.of(tenant1, "uri-1"), zone);
updater.maintain();
- assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/"), zone);
+ assertArchiveUris(zone, Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/"), Map.of());
deploy(application, zone);
updater.maintain();
- assertArchiveUris(Map.of(tenant1, "s3://uri-1/", tenantInfra, "s3://bucketName/"), zone);
+ assertArchiveUris(zone, Map.of(tenant1, "s3://uri-1/", tenantInfra, "s3://bucketName/"), Map.of());
// 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, "s3://uri-1/", tenantInfra, "s3://bucketName/"), zone);
+ assertArchiveUris(zone, Map.of(tenant1, "s3://uri-1/", tenantInfra, "s3://bucketName/"), Map.of());
}
- private void assertArchiveUris(Map<TenantName, String> expectedUris, ZoneId zone) {
- Map<TenantName, String> actualUris = tester.controller().serviceRegistry().configServer().nodeRepository()
- .getArchiveUris(zone).tenantArchiveUris().entrySet().stream()
- .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString()));
- assertEquals(expectedUris, actualUris);
+ private void assertArchiveUris(ZoneId zone, Map<TenantName, String> expectedTenantUris, Map<CloudAccount, String> expectedAccountUris) {
+ ArchiveUris archiveUris = tester.controller().serviceRegistry().configServer().nodeRepository().getArchiveUris(zone);
+ assertEquals(expectedTenantUris, archiveUris.tenantArchiveUris().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString())));
+ assertEquals(expectedAccountUris, archiveUris.accountArchiveUris().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString())));
}
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);
+ ArchiveBuckets buckets = tester.controller().curator().readArchiveBuckets(zone);
+ for (var entry : bucketNames.entrySet())
+ buckets = buckets.with(new VespaManagedArchiveBucket(entry.getValue(), "keyArn").withTenant(entry.getKey()));
+ tester.controller().curator().writeArchiveBuckets(zone, buckets);
}
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
index 82c5a6fc0c1..1d1b1124d22 100644
--- 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
@@ -1,11 +1,15 @@
// Copyright Yahoo. 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.CloudAccount;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBuckets;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.TenantManagedArchiveBucket;
+import com.yahoo.vespa.hosted.controller.api.integration.archive.VespaManagedArchiveBucket;
import org.junit.jupiter.api.Test;
-import java.util.LinkedHashSet;
+import java.time.Instant;
+import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -13,17 +17,12 @@ public class ArchiveBucketsSerializerTest {
@Test
void serdes() {
- var testTenants = new LinkedHashSet<TenantName>();
- testTenants.add(TenantName.from("tenant1"));
- testTenants.add(TenantName.from("tenant2"));
+ ArchiveBuckets archiveBuckets = new ArchiveBuckets(
+ Set.of(new VespaManagedArchiveBucket("bucket1Name", "key1Arn").withTenants(Set.of(TenantName.from("t1"), TenantName.from("t2"))),
+ new VespaManagedArchiveBucket("bucket2Name", "key2Arn").withTenant(TenantName.from("t3"))),
+ Set.of(new TenantManagedArchiveBucket("bucket3Name", CloudAccount.from("acct-1"), Instant.ofEpochMilli(1234)),
+ new TenantManagedArchiveBucket("bucket4Name", CloudAccount.from("acct-2"), Instant.ofEpochMilli(5678))));
- 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()));
+ assertEquals(archiveBuckets, ArchiveBucketsSerializer.fromSlime(ArchiveBucketsSerializer.toSlime(archiveBuckets)));
}
}