summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorEirik Nygaard <eirik.nygaard@yahooinc.com>2022-05-25 14:20:53 +0200
committerEirik Nygaard <eirik.nygaard@yahooinc.com>2022-05-25 15:22:17 +0200
commit03b9ea8cc73793c427d2358e4f7b96cd8e3800cf (patch)
treec728b673e07d53e4bd20849ef011d3a3b8835eb9 /controller-server
parent9bd5c77ab39308e3fe99ab540c1113d974d4b3a0 (diff)
Expose GCP archive member setting in API
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java64
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java35
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json1
8 files changed, 100 insertions, 18 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
index f00e179cfcf..e7cf0c34511 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
@@ -210,7 +210,7 @@ public class TenantSerializer {
// TODO(enygaard, 2022-05-24): Remove when all tenants have been rewritten to use ArchiveAccess object
Optional<String> archiveAccessRole = SlimeUtils.optionalString(tenantObject.field(archiveAccessRoleField));
if (archiveAccessRole.isPresent()) {
- return new ArchiveAccess(archiveAccessRole, Optional.empty());
+ return new ArchiveAccess().withAWSRole(archiveAccessRole.get());
}
Inspector object = tenantObject.field(archiveAccessField);
if (!object.valid()) {
@@ -218,7 +218,9 @@ public class TenantSerializer {
}
Optional<String> awsArchiveAccessRole = SlimeUtils.optionalString(object.field(awsArchiveAccessRoleField));
Optional<String> gcpArchiveAccessMember = SlimeUtils.optionalString(object.field(gcpArchiveAccessMemberField));
- return new ArchiveAccess(awsArchiveAccessRole, gcpArchiveAccessMember);
+ return new ArchiveAccess()
+ .withAWSRole(awsArchiveAccessRole)
+ .withGCPMember(gcpArchiveAccessMember);
}
TenantInfo tenantInfoFromSlime(Inspector infoObject) {
if (!infoObject.valid()) return TenantInfo.empty();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 18391815499..b60c402895b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -297,7 +297,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/access/approve/operator")) return approveAccessRequest(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/access/managed/operator")) return addManagedAccess(path.get("tenant"));
if (path.matches("/application/v4/tenant/{tenant}/info")) return updateTenantInfo(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return allowArchiveAccess(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return allowAwsArchiveAccess(path.get("tenant"), request); // TODO(enygaard, 2022-05-25) Remove when no longer used by console
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access/aws")) return allowAwsArchiveAccess(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access/gcp")) return allowGcpArchiveAccess(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/secret-store/{name}")) return addSecretStore(path.get("tenant"), path.get("name"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
@@ -345,7 +347,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/access/managed/operator")) return removeManagedAccess(path.get("tenant"));
if (path.matches("/application/v4/tenant/{tenant}/key")) return removeDeveloperKey(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return removeArchiveAccess(path.get("tenant"));
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return removeAwsArchiveAccess(path.get("tenant")); // TODO(enygaard, 2022-05-25) Remove when no longer used by console
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access/aws")) return removeAwsArchiveAccess(path.get("tenant"));
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access/gcp")) return removeGcpArchiveAccess(path.get("tenant"));
if (path.matches("/application/v4/tenant/{tenant}/secret-store/{name}")) return deleteSecretStore(path.get("tenant"), path.get("name"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deployment")) return removeAllProdDeployments(path.get("tenant"), path.get("application"));
@@ -1028,7 +1032,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
- private HttpResponse allowArchiveAccess(String tenantName, HttpRequest request) {
+ private HttpResponse allowAwsArchiveAccess(String tenantName, HttpRequest request) {
if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
@@ -1036,27 +1040,62 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
var role = mandatory("role", data).asString();
if (role.isBlank()) {
- return ErrorResponse.badRequest("Archive access role can't be whitespace only");
+ return ErrorResponse.badRequest("AWS archive access role can't be whitespace only");
}
controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccess(new ArchiveAccess(Optional.of(role), Optional.empty()));
+ var access = lockedTenant.get().archiveAccess();
+ lockedTenant = lockedTenant.withArchiveAccess(access.withAWSRole(role));
controller.tenants().store(lockedTenant);
});
- return new MessageResponse("Archive access role set to '" + role + "' for tenant " + tenantName + ".");
+ return new MessageResponse("AWS archive access role set to '" + role + "' for tenant " + tenantName + ".");
}
- private HttpResponse removeArchiveAccess(String tenantName) {
+ private HttpResponse removeAwsArchiveAccess(String tenantName) {
if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccess(new ArchiveAccess());
+ var access = lockedTenant.get().archiveAccess();
+ lockedTenant = lockedTenant.withArchiveAccess(access.removeAWSRole());
controller.tenants().store(lockedTenant);
});
- return new MessageResponse("Archive access role removed for tenant " + tenantName + ".");
+ return new MessageResponse("AWS archive access role removed for tenant " + tenantName + ".");
+ }
+
+ private HttpResponse allowGcpArchiveAccess(String tenantName, HttpRequest request) {
+ if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
+ throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
+
+ var data = toSlime(request.getData()).get();
+ var member = mandatory("member", data).asString();
+
+ if (member.isBlank()) {
+ return ErrorResponse.badRequest("GCP archive access role can't be whitespace only");
+ }
+
+ controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
+ var access = lockedTenant.get().archiveAccess();
+ lockedTenant = lockedTenant.withArchiveAccess(access.withGCPMember(member));
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("GCP archive access member set to '" + member + "' for tenant " + tenantName + ".");
+ }
+
+ private HttpResponse removeGcpArchiveAccess(String tenantName) {
+ if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
+ throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
+
+ controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
+ var access = lockedTenant.get().archiveAccess();
+ lockedTenant = lockedTenant.withArchiveAccess(access.removeGCPMember());
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("GCP archive access member removed for tenant " + tenantName + ".");
}
private HttpResponse patchApplication(String tenantName, String applicationName, HttpRequest request) {
@@ -2354,7 +2393,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
log.warning(String.format("Failed to get quota for tenant %s: %s", tenant.name(), Exceptions.toMessageString(e)));
}
+ // TODO(enygaard, 2022-05-25) Remove when console is using new archive access structure
cloudTenant.archiveAccess().awsRole().ifPresent(role -> object.setString("archiveAccessRole", role));
+ toSlime(cloudTenant.archiveAccess(), object.setObject("archiveAccess"));
break;
}
@@ -2385,6 +2426,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
tenantMetaDataToSlime(tenant, applications, object.setObject("metaData"));
}
+ private void toSlime(ArchiveAccess archiveAccess, Cursor object) {
+ archiveAccess.awsRole().ifPresent(role -> object.setString("awsRole", role));
+ archiveAccess.gcpMember().ifPresent(member -> object.setString("gcpMember", member));
+ }
+
private void toSlime(Quota quota, QuotaUsage usage, Cursor object) {
quota.budget().ifPresentOrElse(
budget -> object.setDouble("budget", budget.doubleValue()),
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 b2451161f34..3535417c586 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
@@ -45,8 +45,8 @@ public class ArchiveAccessMaintainerTest {
assertEquals(0, archiveService.authorizeAccessByTenantName.size());
MockMetric metric = new MockMetric();
new ArchiveAccessMaintainer(tester.controller(), metric, Duration.ofMinutes(10)).maintain();
- assertEquals(new ArchiveAccess(Optional.of(tenant1role), Optional.empty()), archiveService.authorizeAccessByTenantName.get(tenant1));
- assertEquals(new ArchiveAccess(Optional.of(tenant2role), Optional.empty()), archiveService.authorizeAccessByTenantName.get(tenant2));
+ assertEquals(new ArchiveAccess().withAWSRole(tenant1role), archiveService.authorizeAccessByTenantName.get(tenant1));
+ assertEquals(new ArchiveAccess().withAWSRole(tenant2role), archiveService.authorizeAccessByTenantName.get(tenant2));
var expected = Map.of("archive.bucketCount",
tester.controller().zoneRegistry().zonesIncludingSystem().all().ids().stream()
@@ -60,7 +60,7 @@ public class ArchiveAccessMaintainerTest {
private TenantName createTenantWithAccessRole(ControllerTester tester, String tenantName, String role) {
var tenant = tester.createTenant(tenantName, Tenant.Type.cloud);
tester.controller().tenants().lockOrThrow(tenant, LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccess(new ArchiveAccess(Optional.of(role), Optional.empty()));
+ lockedTenant = lockedTenant.withArchiveAccess(new ArchiveAccess().withAWSRole(role));
tester.controller().tenants().store(lockedTenant);
});
return tenant;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
index 431cfbbcbad..a9e633a78d6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
@@ -125,7 +125,7 @@ public class TenantSerializerTest {
new TenantSecretStore("ss1", "123", "role1"),
new TenantSecretStore("ss2", "124", "role2")
),
- new ArchiveAccess(Optional.of("arn:aws:iam::123456789012:role/my-role"), Optional.empty())
+ new ArchiveAccess().withAWSRole("arn:aws:iam::123456789012:role/my-role")
);
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.info(), serialized.info());
@@ -174,7 +174,7 @@ public class TenantSerializerTest {
otherPublicKey, new SimplePrincipal("jane")),
TenantInfo.empty(),
List.of(),
- new ArchiveAccess(Optional.of("arn:aws:iam::123456789012:role/my-role"), Optional.of("user:foo@example.com"))
+ new ArchiveAccess().withAWSRole("arn:aws:iam::123456789012:role/my-role").withGCPMember("user:foo@example.com")
);
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(serialized.archiveAccess().awsRole().get(), "arn:aws:iam::123456789012:role/my-role");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
index 5627a1dd2d3..1df87ffa7ad 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
@@ -315,9 +315,40 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
.data("{\"role\":\"dummy\"}").roles(Role.administrator(tenantName)),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Invalid archive access role 'dummy': Must match expected pattern: 'arn:aws:iam::\\\\d{12}:.+'\"}", 400);
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/aws", PUT)
+ .data("{\"role\":\"arn:aws:iam::123456789012:role/my-role\"}").roles(Role.administrator(tenantName)),
+ "{\"message\":\"AWS archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertTrue(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")),
+ 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/aws", DELETE).roles(Role.administrator(tenantName)),
+ "{\"message\":\"AWS archive access role removed for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertFalse(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")),
+ 200);
+
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/gcp", PUT)
+ .data("{\"member\":\"user:test@example.com\"}").roles(Role.administrator(tenantName)),
+ "{\"message\":\"GCP archive access member set to 'user:test@example.com' for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertTrue(response.getBodyAsString().contains("\"gcpMember\":\"user:test@example.com\"")),
+ 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/gcp", DELETE).roles(Role.administrator(tenantName)),
+ "{\"message\":\"GCP archive access member removed for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertFalse(response.getBodyAsString().contains("\"gcpMember\":\"user:test@example.com\"")),
+ 200);
+
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", PUT)
+ .data("{\"role\":\"arn:aws:iam::123456789012:role/my-role\"}").roles(Role.administrator(tenantName)),
+ "{\"message\":\"AWS archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertTrue(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")),
+ 200);
+
tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", PUT)
.data("{\"role\":\"arn:aws:iam::123456789012:role/my-role\"}").roles(Role.administrator(tenantName)),
- "{\"message\":\"Archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200);
+ "{\"message\":\"AWS archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200);
tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
(response) -> assertTrue(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")),
200);
@@ -327,7 +358,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
new File("deployment-cloud.json"));
tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", DELETE).roles(Role.administrator(tenantName)),
- "{\"message\":\"Archive access role removed for tenant scoober.\"}", 200);
+ "{\"message\":\"AWS archive access role removed for tenant scoober.\"}", 200);
tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
(response) -> assertFalse(response.getBodyAsString().contains("archiveAccessRole")),
200);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json
index 3237e99783d..f980f9231f3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json
@@ -24,6 +24,7 @@
"budgetUsed": 0.0,
"clusterSize": 5
},
+ "archiveAccess": { },
"applications": [
{
"tenant": "my-tenant",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json
index 0cc8ba2cd9e..1152033791b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json
@@ -32,6 +32,7 @@
"budgetUsed": 0.0,
"clusterSize": 5
},
+ "archiveAccess": { },
"applications": [
{
"tenant": "my-tenant",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json
index 3153c6e218a..631346181a1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json
@@ -15,6 +15,7 @@
"budgetUsed": 0.0,
"clusterSize": 5
},
+ "archiveAccess": { },
"applications": [ ],
"metaData": {
"createdAtMillis": 1600000000000