aboutsummaryrefslogtreecommitdiffstats
path: root/controller-api
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@yahooinc.com>2022-06-14 15:36:45 +0200
committerØyvind Grønnesby <oyving@yahooinc.com>2022-06-14 15:36:45 +0200
commit82cc6b485145067f281a2cf68a0e823b8cde6e13 (patch)
treec37f10c8fade516a37dc721ea01b36bfcac9661d /controller-api
parente5486a7ad29d64215c593b0062d9f622be9ce394 (diff)
parentaaafe503173878c081aadaa91665cef162726a24 (diff)
Merge remote-tracking branch 'origin/master' into ogronnesby/persist-scaling-events
Conflicts: controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
Diffstat (limited to 'controller-api')
-rw-r--r--controller-api/pom.xml11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/ArchiveService.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/archive/MockArchiveService.java23
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/CloudEvent.java36
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/CloudEventFetcher.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/MockCloudEventFetcher.java25
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java28
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/secrets/GcpSecretStore.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/secrets/NoopGcpSecretStore.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/ArchiveAccess.java105
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java32
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java2
18 files changed, 230 insertions, 110 deletions
diff --git a/controller-api/pom.xml b/controller-api/pom.xml
index 42e96e0331a..f7057c93561 100644
--- a/controller-api/pom.xml
+++ b/controller-api/pom.xml
@@ -8,12 +8,12 @@
<parent>
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
- <version>7-SNAPSHOT</version>
+ <version>8-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>controller-api</artifactId>
<packaging>container-plugin</packaging>
- <version>7-SNAPSHOT</version>
+ <version>8-SNAPSHOT</version>
<dependencies>
@@ -67,6 +67,13 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>security-utils</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
<!-- compile -->
<dependency>
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java
index 33a87e82ec9..bf16913d05a 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java
@@ -7,7 +7,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.ControllerVersion;
import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService;
import com.yahoo.vespa.hosted.controller.api.integration.artifact.ArtifactRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AccessControlService;
-import com.yahoo.vespa.hosted.controller.api.integration.aws.CloudEventFetcher;
import com.yahoo.vespa.hosted.controller.api.integration.aws.ResourceTagger;
import com.yahoo.vespa.hosted.controller.api.integration.aws.RoleService;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
@@ -30,6 +29,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipI
import com.yahoo.vespa.hosted.controller.api.integration.organization.SystemMonitor;
import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumer;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClient;
+import com.yahoo.vespa.hosted.controller.api.integration.secrets.GcpSecretStore;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretService;
import com.yahoo.vespa.hosted.controller.api.integration.user.RoleMaintainer;
import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequestClient;
@@ -74,8 +74,6 @@ public interface ServiceRegistry {
CostReportConsumer costReportConsumer();
- CloudEventFetcher eventFetcherService();
-
ArtifactRepository artifactRepository();
TesterCloud testerCloud();
@@ -113,4 +111,6 @@ public interface ServiceRegistry {
PlanRegistry planRegistry();
RoleMaintainer roleMaintainer();
+
+ GcpSecretStore gcpSecretStore();
}
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 69eda662692..46e7fb48553 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
@@ -3,7 +3,9 @@ package com.yahoo.vespa.hosted.controller.api.integration.archive;
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.Set;
@@ -17,7 +19,9 @@ public interface ArchiveService {
ArchiveBucket createArchiveBucketFor(ZoneId zoneId);
- void updateBucketPolicy(ZoneId zoneId, ArchiveBucket bucket, Map<TenantName, String> authorizeIamRoleByTenantName);
+ void updatePolicies(ZoneId zoneId, Set<ArchiveBucket> buckets, Map<TenantName,ArchiveAccess> authorizeAccessByTenantName);
- void updateKeyPolicy(ZoneId zoneId, String keyArn, Set<String> tenantAuthorizedIamRoles);
+ boolean canAddTenantToBucket(ZoneId zoneId, ArchiveBucket bucket);
+
+ URI bucketURI(ZoneId zoneId, String bucketName, TenantName tenantName);
}
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 ce7b56ad1f6..a2847439ce7 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
@@ -3,8 +3,11 @@ package com.yahoo.vespa.hosted.controller.api.integration.archive;
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.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -15,8 +18,10 @@ import java.util.TreeMap;
*/
public class MockArchiveService implements ArchiveService {
- public Map<ArchiveBucket, Map<TenantName, String>> authorizedIamRolesForBucket = new HashMap<>();
- public Map<String, Set<String>> authorizedIamRolesForKey = new TreeMap<>();
+
+ public Set<ArchiveBucket> archiveBuckets = new HashSet<>();
+ public Map<TenantName, ArchiveAccess> authorizeAccessByTenantName = new HashMap<>();
+
@Override
public ArchiveBucket createArchiveBucketFor(ZoneId zoneId) {
@@ -24,12 +29,18 @@ public class MockArchiveService implements ArchiveService {
}
@Override
- public void updateBucketPolicy(ZoneId zoneId, ArchiveBucket bucket, Map<TenantName, String> authorizeIamRoleByTenantName) {
- authorizedIamRolesForBucket.put(bucket, authorizeIamRoleByTenantName);
+ public void updatePolicies(ZoneId zoneId, Set<ArchiveBucket> buckets, Map<TenantName, ArchiveAccess> authorizeAccessByTenantName) {
+ this.archiveBuckets = new HashSet<>(buckets);
+ this.authorizeAccessByTenantName = new HashMap<>(authorizeAccessByTenantName);
+ }
+
+ @Override
+ public boolean canAddTenantToBucket(ZoneId zoneId, ArchiveBucket bucket) {
+ return bucket.tenants().size() < 5;
}
@Override
- public void updateKeyPolicy(ZoneId zoneId, String keyArn, Set<String> tenantAuthorizedIamRoles) {
- authorizedIamRolesForKey.put(keyArn, tenantAuthorizedIamRoles);
+ public URI bucketURI(ZoneId zoneId, String bucketName, TenantName tenantName) {
+ return URI.create(String.format("s3://%s/%s/", bucketName, tenantName.value()));
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/CloudEvent.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/CloudEvent.java
deleted file mode 100644
index b5445791bf0..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/CloudEvent.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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.aws;
-
-import java.util.Date;
-import java.util.Optional;
-import java.util.Set;
-
-/**
- * A maintenance event in a cloud service.
- *
- * @author freva
- */
-public class CloudEvent {
-
- public final String instanceEventId;
- public final String code;
- public final String description;
- public final Optional<Date> notBefore;
- public final Optional<Date> notBeforeDeadline;
- public final Optional<Date> notAfter;
- public final String awsRegionName;
- public final Set<String> affectedInstances;
-
- public CloudEvent(String instanceEventId, String code, String description, Date notAfter, Date notBefore,
- Date notBeforeDeadline, String awsRegionName, Set<String> affectedInstances) {
- this.instanceEventId = instanceEventId;
- this.code = code;
- this.description = description;
- this.notBefore = Optional.ofNullable(notBefore);
- this.notBeforeDeadline = Optional.ofNullable(notBeforeDeadline);
- this.notAfter = Optional.ofNullable(notAfter);
- this.awsRegionName = awsRegionName;
- this.affectedInstances = Set.copyOf(affectedInstances);
- }
-
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/CloudEventFetcher.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/CloudEventFetcher.java
deleted file mode 100644
index 0d08a5a8cb6..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/CloudEventFetcher.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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.aws;
-
-import java.util.List;
-
-/**
- * @author freva
- */
-public interface CloudEventFetcher {
-
- List<CloudEvent> getEvents(String regionName);
-
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/MockCloudEventFetcher.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/MockCloudEventFetcher.java
deleted file mode 100644
index 3300d8879ce..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/MockCloudEventFetcher.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.aws;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author freva
- */
-public class MockCloudEventFetcher implements CloudEventFetcher {
-
- private final Map<String, List<CloudEvent>> mockedEvents = new HashMap<>();
-
- @Override
- public List<CloudEvent> getEvents(String regionName) {
- return mockedEvents.getOrDefault(regionName, new ArrayList<>());
- }
-
- public void addEvent(String regionName, CloudEvent cloudEvent) {
- mockedEvents.computeIfAbsent(regionName, i -> new ArrayList<>()).add(cloudEvent);
- }
-
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java
index 23fb1f20fbc..6b267e538e7 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java
@@ -1,6 +1,7 @@
// 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.billing;
+import java.util.List;
import java.util.Optional;
/**
@@ -16,6 +17,9 @@ public interface PlanRegistry {
/** Get a plan given a plan ID */
Optional<Plan> plan(PlanId planId);
+ /** Get a set of all plans */
+ List<Plan> all();
+
/** Get a plan give a plan ID */
default Optional<Plan> plan(String planId) {
if (planId == null || planId.isBlank())
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
index 4757fa76224..723ffa383eb 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
@@ -6,6 +6,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceUsage;
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@@ -27,6 +28,11 @@ public class PlanRegistryMock implements PlanRegistry {
.findAny();
}
+ @Override
+ public List<Plan> all() {
+ return List.of(freeTrial, paidPlan, nonePlan);
+ }
+
private static class MockPlan implements Plan {
private final PlanId planId;
private final String description;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java
index 1b36a573bf1..b423fcb83f8 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java
@@ -84,6 +84,34 @@ public class EndpointCertificateMetadata {
return lastRefreshed;
}
+ public EndpointCertificateMetadata withKeyName(String keyName) {
+ return new EndpointCertificateMetadata(
+ keyName,
+ this.certName,
+ this.version,
+ this.lastRequested,
+ this.rootRequestId,
+ this.leafRequestId,
+ this.requestedDnsSans,
+ this.issuer,
+ this.expiry,
+ this.lastRefreshed);
+ }
+
+ public EndpointCertificateMetadata withCertName(String certName) {
+ return new EndpointCertificateMetadata(
+ this.keyName,
+ certName,
+ this.version,
+ this.lastRequested,
+ this.rootRequestId,
+ this.leafRequestId,
+ this.requestedDnsSans,
+ this.issuer,
+ this.expiry,
+ this.lastRefreshed);
+ }
+
public EndpointCertificateMetadata withVersion(int version) {
return new EndpointCertificateMetadata(
this.keyName,
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java
index 9e14c4ae8dd..77879699ab9 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java
@@ -134,7 +134,7 @@ public final class JobType implements Comparable<JobType> {
}
public static List<JobType> allIn(ZoneRegistry zones) {
- return zones.zones().controllerUpgraded().zones().stream()
+ return zones.zones().reachable().zones().stream()
.flatMap(zone -> zone.getEnvironment().isProduction() ? Stream.of(deploymentTo(zone.getId()), productionTestOf(zone.getId()))
: Stream.of(deploymentTo(zone.getId())))
.sorted(naturalOrder())
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/secrets/GcpSecretStore.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/secrets/GcpSecretStore.java
new file mode 100644
index 00000000000..312bec1fd98
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/secrets/GcpSecretStore.java
@@ -0,0 +1,8 @@
+package com.yahoo.vespa.hosted.controller.api.integration.secrets;
+
+public interface GcpSecretStore {
+
+ void createSecret(String secretName, String secret);
+
+ String getSecret(String secretName, int version);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/secrets/NoopGcpSecretStore.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/secrets/NoopGcpSecretStore.java
new file mode 100644
index 00000000000..9335a814f6c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/secrets/NoopGcpSecretStore.java
@@ -0,0 +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.secrets;
+
+/**
+ * @author olaa
+ */
+public class NoopGcpSecretStore implements GcpSecretStore {
+
+ @Override
+ public void createSecret(String secretName, String secret) {
+
+ }
+
+ @Override
+ public String getSecret(String secretName, int version) {
+ return "";
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
index b5f03756777..a3c1a0315ec 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
@@ -55,6 +55,9 @@ enum PathGroup {
tenantInfo(Matcher.tenant,
"/application/v4/tenant/{tenant}/application/",
"/application/v4/tenant/{tenant}/info/",
+ "/application/v4/tenant/{tenant}/info/profile",
+ "/application/v4/tenant/{tenant}/info/billing",
+ "/application/v4/tenant/{tenant}/info/contacts",
"/application/v4/tenant/{tenant}/notifications",
"/routing/v1/status/tenant/{tenant}/{*}"),
@@ -62,7 +65,10 @@ enum PathGroup {
"/application/v4/tenant/{tenant}/key/"),
tenantArchiveAccess(Matcher.tenant,
- "/application/v4/tenant/{tenant}/archive-access"),
+ "/application/v4/tenant/{tenant}/archive-access",
+ "/application/v4/tenant/{tenant}/archive-access/aws",
+ "/application/v4/tenant/{tenant}/archive-access/gcp"),
+
billingToken(Matcher.tenant,
"/billing/v1/tenant/{tenant}/token"),
@@ -228,7 +234,8 @@ enum PathGroup {
/** Paths used for invoice management */
hostedAccountant("/billing/v1/invoice/{*}",
- "/billing/v1/billing"),
+ "/billing/v1/billing",
+ "/billing/v1/plans"),
/** Path used for listing endpoint certificate request and re-requesting endpoint certificates */
endpointCertificates("/endpointcertificates/"),
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java
index a739a8e2b01..60950341a42 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java
@@ -177,7 +177,7 @@ public class SystemFlagsDataArchive {
if (!filenamesForSystem.isEmpty() && !filenamesForSystem.contains(filename)) {
if (systemDefinition != null && filename.startsWith(systemDefinition.system().value() + '.')) {
throw new IllegalArgumentException(String.format(
- "Environment or zone in filename '%s' is does not exist", filename));
+ "Environment or zone in filename '%s' does not exist", filename));
}
return; // Ignore files irrelevant for system
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/ArchiveAccess.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/ArchiveAccess.java
new file mode 100644
index 00000000000..fba361f9223
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/ArchiveAccess.java
@@ -0,0 +1,105 @@
+package com.yahoo.vespa.hosted.controller.tenant;
+
+import com.yahoo.text.Text;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class ArchiveAccess {
+
+ private static final Pattern VALID_AWS_ARCHIVE_ACCESS_ROLE_PATTERN = Pattern.compile("arn:aws:iam::\\d{12}:.+");
+ private static final Pattern VALID_GCP_ARCHIVE_ACCESS_MEMBER_PATTERN = Pattern.compile("(?<prefix>[a-zA-Z]+):.+");
+
+ private static final Set<String> gcpMemberPrefixes = Set.of("user", "serviceAccount", "group", "domain");
+
+ // AWS IAM Role
+ private final Optional<String> awsRole;
+ // GCP Member
+ private final Optional<String> gcpMember;
+
+ public ArchiveAccess() {
+ this(Optional.empty(), Optional.empty());
+ }
+
+ private ArchiveAccess(Optional<String> awsRole, Optional<String> gcpMember) {
+ this.awsRole = awsRole;
+ this.gcpMember = gcpMember;
+
+ awsRole.ifPresent(role -> validateAWSIAMRole(role));
+ gcpMember.ifPresent(member -> validateGCPMember(member));
+ }
+
+ public ArchiveAccess withAWSRole(String role) {
+ return new ArchiveAccess(Optional.of(role), gcpMember());
+ }
+
+ public ArchiveAccess withGCPMember(String member) {
+ return new ArchiveAccess(awsRole(), Optional.of(member));
+ }
+
+ public ArchiveAccess withAWSRole(Optional<String> role) {
+ return new ArchiveAccess(role, gcpMember());
+ }
+
+ public ArchiveAccess withGCPMember(Optional<String> member) {
+ return new ArchiveAccess(awsRole(), member);
+ }
+
+ public ArchiveAccess removeAWSRole() {
+ return new ArchiveAccess(Optional.empty(), gcpMember());
+ }
+
+ public ArchiveAccess removeGCPMember() {
+ return new ArchiveAccess(awsRole(), Optional.empty());
+ }
+
+ private void validateAWSIAMRole(String role) {
+ if (!VALID_AWS_ARCHIVE_ACCESS_ROLE_PATTERN.matcher(role).matches()) {
+ throw new IllegalArgumentException(Text.format("Invalid archive access role '%s': Must match expected pattern: '%s'",
+ awsRole.get(), VALID_AWS_ARCHIVE_ACCESS_ROLE_PATTERN.pattern()));
+ }
+ if (role.length() > 100) {
+ throw new IllegalArgumentException("Invalid archive access role too long, must be 100 or less characters");
+ }
+ }
+
+ private void validateGCPMember(String member) {
+ var matcher = VALID_GCP_ARCHIVE_ACCESS_MEMBER_PATTERN.matcher(member);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(Text.format("Invalid GCP archive access member '%s': Must match expected pattern: '%s'",
+ gcpMember.get(), VALID_GCP_ARCHIVE_ACCESS_MEMBER_PATTERN.pattern()));
+ }
+ var prefix = matcher.group("prefix");
+ if (!gcpMemberPrefixes.contains(prefix)) {
+ throw new IllegalArgumentException(Text.format("Invalid GCP member prefix '%s', must be one of '%s'",
+ prefix, gcpMemberPrefixes.stream().collect(Collectors.joining(", "))));
+ }
+ if (!"domain".equals(prefix) && !member.contains("@")) {
+ throw new IllegalArgumentException(Text.format("Invalid GCP member '%s', prefix '%s' must be followed by an email id", member, prefix));
+ }
+ }
+
+ public Optional<String> awsRole() {
+ return awsRole;
+ }
+
+ public Optional<String> gcpMember() {
+ return gcpMember;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ArchiveAccess that = (ArchiveAccess) o;
+ return awsRole.equals(that.awsRole) && gcpMember.equals(that.gcpMember);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(awsRole, gcpMember);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
index 9a4e04ebb3a..54924b9c456 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller.tenant;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.text.Text;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import java.security.Principal;
@@ -13,7 +12,6 @@ import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.regex.Pattern;
/**
* A paying tenant in a Vespa cloud service.
@@ -22,29 +20,22 @@ import java.util.regex.Pattern;
*/
public class CloudTenant extends Tenant {
- private static final Pattern VALID_ARCHIVE_ACCESS_ROLE_PATTERN = Pattern.compile("arn:aws:iam::\\d{12}:.+");
-
private final Optional<Principal> creator;
private final BiMap<PublicKey, Principal> developerKeys;
private final TenantInfo info;
private final List<TenantSecretStore> tenantSecretStores;
- private final Optional<String> archiveAccessRole;
+ private final ArchiveAccess archiveAccess;
/** Public for the serialization layer — do not use! */
public CloudTenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator,
BiMap<PublicKey, Principal> developerKeys, TenantInfo info,
- List<TenantSecretStore> tenantSecretStores, Optional<String> archiveAccessRole) {
+ List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess) {
super(name, createdAt, lastLoginInfo, Optional.empty());
this.creator = creator;
this.developerKeys = developerKeys;
this.info = Objects.requireNonNull(info);
this.tenantSecretStores = tenantSecretStores;
- this.archiveAccessRole = archiveAccessRole;
- if (!archiveAccessRole.map(role -> VALID_ARCHIVE_ACCESS_ROLE_PATTERN.matcher(role).matches()).orElse(true))
- throw new IllegalArgumentException(Text.format("Invalid archive access role '%s': Must match expected pattern: '%s'",
- archiveAccessRole.get(), VALID_ARCHIVE_ACCESS_ROLE_PATTERN.pattern()));
- if (archiveAccessRole.map(role -> role.length() > 100).orElse(false))
- throw new IllegalArgumentException("Invalid archive access role too long, must be 100 or less characters");
+ this.archiveAccess = Objects.requireNonNull(archiveAccess);
}
/** Creates a tenant with the given name, provided it passes validation. */
@@ -53,7 +44,7 @@ public class CloudTenant extends Tenant {
createdAt,
LastLoginInfo.EMPTY,
Optional.ofNullable(creator),
- ImmutableBiMap.of(), TenantInfo.empty(), List.of(), Optional.empty());
+ ImmutableBiMap.of(), TenantInfo.empty(), List.of(), new ArchiveAccess());
}
/** The user that created the tenant */
@@ -66,11 +57,6 @@ public class CloudTenant extends Tenant {
return info;
}
- /** An iam role which is allowed to access the S3 (log, dump) archive) */
- public Optional<String> archiveAccessRole() {
- return archiveAccessRole;
- }
-
/** Returns the set of developer keys and their corresponding developers for this tenant. */
public BiMap<PublicKey, Principal> developerKeys() { return developerKeys; }
@@ -79,6 +65,16 @@ public class CloudTenant extends Tenant {
return tenantSecretStores;
}
+ /**
+ * Role or member that is allowed to access archive bucket (log, dump)
+ *
+ * For AWS is this the IAM role
+ * For GCP it is a GCP member
+ */
+ public ArchiveAccess archiveAccess() {
+ return archiveAccess;
+ }
+
@Override
public Type type() {
return Type.cloud;
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java
index e4c46cfed1a..ce2746f92e0 100644
--- a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java
@@ -135,7 +135,7 @@ public class SystemFlagsDataArchiveTest {
Path directory = Paths.get("src/test/resources/system-flags-with-unknown-file-name/");
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
- "Environment or zone in filename 'main.prod.unknown-region.json' is does not exist");
+ "Environment or zone in filename 'main.prod.unknown-region.json' does not exist");
SystemFlagsDataArchive.fromDirectoryAndSystem(directory, createZoneRegistryMock());
}