aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorValerij Fredriksen <freva@users.noreply.github.com>2023-03-02 16:51:01 +0100
committerGitHub <noreply@github.com>2023-03-02 16:51:01 +0100
commitfa987c7e8dfaa7293fc99459fd942d001fdef11f (patch)
tree1dc41dad02166dc624baf9af70f0fd33cd0d97d9
parent8a0918e280bf7ebd8f92ec84f76a5d32aa573470 (diff)
parentdb840edf14445c07f991cf4a591aeb552f457962 (diff)
Merge pull request #26279 from vespa-engine/freva/propagate-account-archive-uri
Propagate account archive URI to node-repo
-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.java12
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java45
-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.java101
-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/maintenance/ArchiveUriUpdater.java46
-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.java55
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java2
-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.java57
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java25
16 files changed, 340 insertions, 159 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 46e7fb48553..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
@@ -1,12 +1,14 @@
// 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 com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import java.net.URI;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
/**
@@ -17,11 +19,13 @@ 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);
- URI bucketURI(ZoneId zoneId, String bucketName, TenantName tenantName);
+ Optional<String> findEnclaveArchiveBucket(ZoneId zoneId, CloudAccount cloudAccount);
+
+ URI bucketURI(ZoneId zoneId, String bucketName);
}
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 a2847439ce7..7461d3aa47e 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
@@ -1,16 +1,18 @@
// 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 com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import java.net.URI;
+import java.time.Clock;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
-import java.util.TreeMap;
/**
* @author freva
@@ -18,29 +20,54 @@ import java.util.TreeMap;
*/
public class MockArchiveService implements ArchiveService {
-
- public Set<ArchiveBucket> archiveBuckets = new HashSet<>();
+ private final Map<ZoneId, Set<TenantManagedArchiveBucket>> tenantArchiveBucketsByZone = new HashMap<>();
+ public Set<VespaManagedArchiveBucket> archiveBuckets = new HashSet<>();
public Map<TenantName, ArchiveAccess> authorizeAccessByTenantName = new HashMap<>();
+ private final Clock clock;
+
+ public MockArchiveService(Clock clock) {
+ this.clock = clock;
+ }
@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;
}
@Override
- public URI bucketURI(ZoneId zoneId, String bucketName, TenantName tenantName) {
- return URI.create(String.format("s3://%s/%s/", bucketName, tenantName.value()));
+ public Optional<String> findEnclaveArchiveBucket(ZoneId zoneId, CloudAccount cloudAccount) {
+ return tenantArchiveBucketsByZone.getOrDefault(zoneId, Set.of()).stream()
+ .filter(bucket -> bucket.cloudAccount().equals(cloudAccount))
+ .findFirst()
+ .map(TenantManagedArchiveBucket::bucketName);
+ }
+
+ @Override
+ public URI bucketURI(ZoneId zoneId, String bucketName) {
+ return URI.create(String.format("s3://%s/", bucketName));
+ }
+
+
+ public void setEnclaveArchiveBucket(ZoneId zoneId, CloudAccount cloudAccount, String bucketName) {
+ removeEnclaveArchiveBucket(zoneId, cloudAccount);
+ tenantArchiveBucketsByZone.computeIfAbsent(zoneId, z -> new HashSet<>())
+ .add(new TenantManagedArchiveBucket(bucketName, cloudAccount, clock.instant()));
+ }
+
+ public void removeEnclaveArchiveBucket(ZoneId zoneId, CloudAccount cloudAccount) {
+ Optional.ofNullable(tenantArchiveBucketsByZone.get(zoneId))
+ .ifPresent(set -> set.removeIf(bucket -> bucket.cloudAccount().equals(cloudAccount)));
}
}
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 ac32fe5799d..962bd144a21 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
@@ -1,18 +1,22 @@
// Copyright Yahoo. 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.yahoo.config.provision.CloudAccount;
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.TenantManagedArchiveBucket;
+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.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
import java.util.Map;
import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -23,81 +27,100 @@ import java.util.stream.Collectors;
*/
public class CuratorArchiveBucketDb {
+ private static final Duration ENCLAVE_BUCKET_CACHE_LIFETIME = Duration.ofMinutes(60);
+
/**
* Archive URIs are often requested because they are returned in /application/v4 API. Since they
* never change, it's safe to cache them and only update on misses
*/
private final Map<ZoneId, Map<TenantName, String>> archiveUriCache = new ConcurrentHashMap<>();
+ private final Map<ZoneId, Map<CloudAccount, TenantManagedArchiveBucket>> tenantArchiveCache = new ConcurrentHashMap<>();
private final ArchiveService archiveService;
private final CuratorDb curatorDb;
+ private final Clock clock;
public CuratorArchiveBucketDb(Controller controller) {
this.archiveService = controller.serviceRegistry().archiveService();
this.curatorDb = controller.curator();
+ this.clock = controller.clock();
}
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, tenant));
+ .map(bucketName -> archiveService.bucketURI(zoneId, bucketName));
+ }
+
+ public Optional<URI> archiveUriFor(ZoneId zoneId, CloudAccount account, boolean searchIfMissing) {
+ Instant updatedAfter = searchIfMissing ? clock.instant().minus(ENCLAVE_BUCKET_CACHE_LIFETIME) : Instant.MIN;
+ return getBucketNameFromCache(zoneId, account, updatedAfter)
+ .or(() -> {
+ if (!searchIfMissing) return Optional.empty();
+ try (var lock = curatorDb.lockArchiveBuckets(zoneId)) {
+ ArchiveBuckets archiveBuckets = buckets(zoneId);
+ updateArchiveUriCache(zoneId, archiveBuckets);
+
+ return getBucketNameFromCache(zoneId, account, updatedAfter)
+ .or(() -> archiveService.findEnclaveArchiveBucket(zoneId, account)
+ .map(bucketName -> {
+ var bucket = new TenantManagedArchiveBucket(bucketName, account, clock.instant());
+ ArchiveBuckets updated = archiveBuckets.with(bucket);
+ curatorDb.writeArchiveBuckets(zoneId, updated);
+ updateArchiveUriCache(zoneId, updated);
+ return bucket;
+ }));
+ }
+ })
+ .map(TenantManagedArchiveBucket::bucketName)
+ .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();
-
- // 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);
+ .findAny()
+ // Or create a new one
+ .orElseGet(() -> archiveService.createArchiveBucketFor(zoneId));
- return unfilled.bucketName();
- }
+ ArchiveBuckets updated = archiveBuckets.with(bucketToAssignTo.withTenant(tenant));
+ curatorDb.writeArchiveBuckets(zoneId, updated);
+ updateArchiveUriCache(zoneId, updated);
- // 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()
- .flatMap(bucket -> bucket.tenants().stream()
- .map(tenant -> Map.entry(tenant, bucket.bucketName())))
+ private Optional<TenantManagedArchiveBucket> getBucketNameFromCache(ZoneId zoneId, CloudAccount cloudAccount, Instant updatedAfter) {
+ return Optional.ofNullable(tenantArchiveCache.get(zoneId))
+ .map(map -> map.get(cloudAccount))
+ .filter(bucket -> bucket.updatedAt().isAfter(updatedAfter));
+ }
+
+ 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));
archiveUriCache.put(zoneId, bucketNameByTenant);
+
+ Map<CloudAccount, TenantManagedArchiveBucket> bucketByAccount = archiveBuckets.tenantManaged().stream()
+ .collect(Collectors.toUnmodifiableMap(TenantManagedArchiveBucket::cloudAccount, bucket -> bucket));
+ tenantArchiveCache.put(zoneId, bucketByAccount);
}
}
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/maintenance/ArchiveUriUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
index ddb1365f2de..1083e545b33 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
@@ -1,10 +1,12 @@
// 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.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.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveUriUpdate;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ArchiveUris;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
@@ -18,6 +20,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
+import java.util.stream.Stream;
/**
* Updates archive URIs for tenants in all zones.
@@ -42,14 +45,19 @@ public class ArchiveUriUpdater extends ControllerMaintainer {
@Override
protected double maintain() {
Map<ZoneId, Set<TenantName>> tenantsByZone = new HashMap<>();
+ Map<ZoneId, Set<CloudAccount>> accountsByZone = new HashMap<>();
- controller().zoneRegistry().zonesIncludingSystem().reachable().zones().forEach(
- z -> tenantsByZone.put(z.getVirtualId(), new HashSet<>(INFRASTRUCTURE_TENANTS)));
+ controller().zoneRegistry().zonesIncludingSystem().reachable().zones().forEach(zone -> {
+ tenantsByZone.put(zone.getVirtualId(), new HashSet<>(INFRASTRUCTURE_TENANTS));
+ accountsByZone.put(zone.getVirtualId(), new HashSet<>());
+ });
for (var application : applications.asList()) {
for (var instance : application.instances().values()) {
for (var deployment : instance.deployments().values()) {
tenantsByZone.get(deployment.zone()).add(instance.id().tenant());
+ applications.decideCloudAccountOf(new DeploymentId(instance.id(), deployment.zone()), application.deploymentSpec())
+ .ifPresent(account -> accountsByZone.get(deployment.zone()).add(account));
}
}
}
@@ -59,17 +67,29 @@ public class ArchiveUriUpdater extends ControllerMaintainer {
try {
ArchiveUris zoneArchiveUris = nodeRepository.getArchiveUris(zone);
- for (TenantName tenant : tenantsByZone.get(zone)) {
- archiveBucketDb.archiveUriFor(zone, tenant, true)
- .filter(uri -> !uri.equals(zoneArchiveUris.tenantArchiveUris().get(tenant)))
- .ifPresent(uri -> nodeRepository.updateArchiveUri(zone, ArchiveUriUpdate.setArchiveUriFor(tenant, uri)));
- }
-
- zoneArchiveUris.tenantArchiveUris().keySet().stream()
- .filter(tenant -> !tenantsByZone.get(zone).contains(tenant))
- .forEach(tenant -> nodeRepository.updateArchiveUri(zone, ArchiveUriUpdate.deleteArchiveUriFor(tenant)));
-
- // TODO (freva): Update account archive URIs
+ Stream.of(
+ // Tenant URIs that need to be added or updated
+ tenantsByZone.get(zone).stream()
+ .flatMap(tenant -> archiveBucketDb.archiveUriFor(zone, tenant, true)
+ .filter(uri -> !uri.equals(zoneArchiveUris.tenantArchiveUris().get(tenant)))
+ .map(uri -> ArchiveUriUpdate.setArchiveUriFor(tenant, uri))
+ .stream()),
+ // Account URIs that need to be added or updated
+ accountsByZone.get(zone).stream()
+ .flatMap(account -> archiveBucketDb.archiveUriFor(zone, account, true)
+ .filter(uri -> !uri.equals(zoneArchiveUris.accountArchiveUris().get(account)))
+ .map(uri -> ArchiveUriUpdate.setArchiveUriFor(account, uri))
+ .stream()),
+ // Tenant URIs that need to be deleted
+ zoneArchiveUris.tenantArchiveUris().keySet().stream()
+ .filter(tenant -> !tenantsByZone.get(zone).contains(tenant))
+ .map(ArchiveUriUpdate::deleteArchiveUriFor),
+ // Account URIs that need to be deleted
+ zoneArchiveUris.accountArchiveUris().keySet().stream()
+ .filter(account -> !accountsByZone.get(zone).contains(account))
+ .map(ArchiveUriUpdate::deleteArchiveUriFor))
+ .flatMap(s -> s)
+ .forEach(update -> nodeRepository.updateArchiveUri(zone, update));
} catch (Exception e) {
log.log(Level.WARNING, "Failed to update archive URI in " + zone + ". Retrying in " + interval() + ". Error: " +
Exceptions.toMessageString(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 081056e5184..e5571c0e0ca 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
@@ -1,15 +1,21 @@
// Copyright Yahoo. 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.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.test.ManualClock;
+import com.yahoo.vespa.hosted.controller.Controller;
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.MockArchiveService;
+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;
import java.net.URI;
+import java.time.Duration;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -21,27 +27,27 @@ 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/default/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.defaultName(), true));
+ assertEquals(Optional.of(URI.create("s3://existingBucket/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.defaultName(), true));
// Assigns to existing bucket while there is space
IntStream.range(0, 4).forEach(i ->
assertEquals(
- Optional.of(URI.create("s3://existingBucket/tenant" + i + "/")), bucketDb
+ Optional.of(URI.create("s3://existingBucket/")), bucketDb
.archiveUriFor(ZoneId.defaultId(), TenantName.from("tenant" + i), true)));
// Creates new bucket when existing buckets are full
- assertEquals(Optional.of(URI.create("s3://bucketName/lastDrop/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.from("lastDrop"), true));
+ assertEquals(Optional.of(URI.create("s3://bucketName/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.from("lastDrop"), true));
// 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"), true));
+ assertEquals(Optional.of(URI.create("s3://bucketName/")), bucketDb.archiveUriFor(ZoneId.from("prod.us-east-3"), TenantName.from("firstInZone"), true));
// Does not create bucket if not required
assertEquals(Optional.empty(), bucketDb.archiveUriFor(ZoneId.from("prod.us-east-3"), TenantName.from("newTenant"), false));
@@ -50,11 +56,36 @@ 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());
+ }
+
+ @Test
+ void archiveUriForAccount() {
+ Controller controller = new ControllerTester(SystemName.Public).controller();
+ CuratorArchiveBucketDb bucketDb = new CuratorArchiveBucketDb(controller);
+ MockArchiveService service = (MockArchiveService) controller.serviceRegistry().archiveService();
+ ManualClock clock = (ManualClock) controller.clock();
+
+ CloudAccount acc1 = CloudAccount.from("001122334455");
+ ZoneId z1 = ZoneId.from("prod.us-east-3");
+
+ assertEquals(Optional.empty(), bucketDb.archiveUriFor(z1, acc1, true)); // Initially not set
+ service.setEnclaveArchiveBucket(z1, acc1, "bucket-1");
+ assertEquals(Optional.empty(), bucketDb.archiveUriFor(z1, acc1, false));
+ assertEquals(Optional.of(URI.create("s3://bucket-1/")), bucketDb.archiveUriFor(z1, acc1, true));
+ assertEquals(Optional.of(URI.create("s3://bucket-1/")), bucketDb.archiveUriFor(z1, acc1, false));
+
+ service.setEnclaveArchiveBucket(z1, acc1, "bucket-2");
+ assertEquals(Optional.of(URI.create("s3://bucket-1/")), bucketDb.archiveUriFor(z1, acc1, true)); // Returns old value even with search
+
+ clock.advance(Duration.ofMinutes(61)); // After expiry the cache is expired, new search is performed
+ assertEquals(Optional.of(URI.create("s3://bucket-1/")), bucketDb.archiveUriFor(z1, acc1, false)); // When requesting without search, return previous value even if expired
+ assertEquals(Optional.of(URI.create("s3://bucket-2/")), bucketDb.archiveUriFor(z1, acc1, true));
+ assertEquals(Optional.of(URI.create("s3://bucket-2/")), bucketDb.archiveUriFor(z1, acc1, false));
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
index be257daa211..998b371bbf1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
@@ -37,11 +37,11 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueH
import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClient;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClientMock;
+import com.yahoo.vespa.hosted.controller.api.integration.secrets.EndpointSecretManager;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.GcpSecretStore;
-import com.yahoo.vespa.hosted.controller.api.integration.secrets.NoopGcpSecretStore;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.NoopEndpointSecretManager;
+import com.yahoo.vespa.hosted.controller.api.integration.secrets.NoopGcpSecretStore;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.NoopTenantSecretService;
-import com.yahoo.vespa.hosted.controller.api.integration.secrets.EndpointSecretManager;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.DummySystemMonitor;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues;
@@ -89,7 +89,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
private final ArtifactRegistryMock containerRegistry = new ArtifactRegistryMock();
private final NoopTenantSecretService tenantSecretService = new NoopTenantSecretService();
private final NoopEndpointSecretManager secretManager = new NoopEndpointSecretManager();
- private final ArchiveService archiveService = new MockArchiveService();
+ private final ArchiveService archiveService = new MockArchiveService(clock);
private final MockChangeRequestClient changeRequestClient = new MockChangeRequestClient();
private final AccessControlService accessControlService = new MockAccessControlService();
private final HorizonClient horizonClient = new MockHorizonClient();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
index 38ff9967ef6..de186109784 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
@@ -266,7 +266,7 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
@Override
public boolean hasZone(ZoneId zoneId, CloudAccount cloudAccount) {
- return hasZone(zoneId) && cloudAccountZones.getOrDefault(cloudAccount, Set.of()).contains(zoneId);
+ return hasZone(zoneId) && (system.isPublic() || cloudAccountZones.getOrDefault(cloudAccount, Set.of()).contains(zoneId));
}
@Override
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 de62a2fc48c..d6b59ef860f 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,23 +1,28 @@
// 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.flags.InMemoryFlagSource;
+import com.yahoo.vespa.flags.PermanentFlags;
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.MockArchiveService;
+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;
-import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import org.junit.jupiter.api.Test;
import java.net.URI;
import java.time.Duration;
-import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -36,42 +41,49 @@ public class ArchiveUriUpdaterTest {
var tenant1 = TenantName.from("tenant1");
var tenant2 = TenantName.from("tenant2");
+ var account1 = CloudAccount.from("001122334455");
var tenantInfra = SystemApplication.TENANT;
var application = tester.newDeploymentContext(tenant1.value(), "app1", "instance1");
ZoneId zone = ZoneId.from("prod", "aws-us-east-1c");
// Initially we should only is the bucket for hosted-vespa tenant
updater.maintain();
- assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/hosted-vespa/"), zone);
- assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/hosted-vespa/"), 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);
+ setAccountBucketNameInService(zone, account1, "bkt-1");
updater.maintain();
- assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/hosted-vespa/"), zone);
+ assertArchiveUris(zone, Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/"), Map.of());
- deploy(application, zone);
+ ((InMemoryFlagSource) tester.controller().flagSource())
+ .withListFlag(PermanentFlags.CLOUD_ACCOUNTS.id(), List.of(account1.value()), String.class);
+ deploy(application, zone, account1);
updater.maintain();
- assertArchiveUris(Map.of(tenant1, "s3://uri-1/tenant1/", tenantInfra, "s3://bucketName/hosted-vespa/"), zone);
+ assertArchiveUris(zone, Map.of(tenant1, "s3://uri-1/", tenantInfra, "s3://bucketName/"), Map.of(account1, "s3://bkt-1/"));
// 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/tenant1/", tenantInfra, "s3://bucketName/hosted-vespa/"), zone);
+ assertArchiveUris(zone, Map.of(tenant1, "s3://uri-1/", tenantInfra, "s3://bucketName/"), Map.of(account1, "s3://bkt-1/"));
}
- 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 setAccountBucketNameInService(ZoneId zone, CloudAccount cloudAccount, String bucketName) {
+ ((MockArchiveService) tester.controller().serviceRegistry().archiveService()).setEnclaveArchiveBucket(zone, cloudAccount, bucketName);
}
private void setArchiveUriInNodeRepo(Map<TenantName, String> archiveUris, ZoneId zone) {
@@ -79,8 +91,11 @@ public class ArchiveUriUpdaterTest {
archiveUris.forEach((tenant, uri) -> nodeRepository.updateArchiveUri(zone, ArchiveUriUpdate.setArchiveUriFor(tenant, URI.create(uri))));
}
- private void deploy(DeploymentContext application, ZoneId zone) {
- application.runJob(JobType.deploymentTo(zone), new ApplicationPackage(new byte[0]));
+ private void deploy(DeploymentContext application, ZoneId zone, CloudAccount cloudAccount) {
+ application.submit(new ApplicationPackageBuilder()
+ .cloudAccount(cloudAccount.value())
+ .region(zone.region().value())
+ .build()).deploy();
}
}
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)));
}
}