summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Marius Venstad <jonmv@users.noreply.github.com>2019-04-01 11:04:29 +0200
committerGitHub <noreply@github.com>2019-04-01 11:04:29 +0200
commitae909b3a59e2f947b94a243ea62d34a4392ad2d0 (patch)
tree49f6f323b0334f2dce7e04987244efc761f040e6
parentb012478bd6aed5cf4d2f62854b6d800b8c020b0e (diff)
parent65c0ced3e0c6ed15c563636dc20fc6ec9957b2b2 (diff)
Merge pull request #8944 from vespa-engine/jvenstad/more-general-roles
Jvenstad/more general roles
-rw-r--r--container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java121
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java120
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java66
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java27
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json130
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-details.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json32
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolverTest.java22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/PathGroupTest.java63
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java14
13 files changed, 475 insertions, 141 deletions
diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
index f1eb03d88a9..8858465e846 100644
--- a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
+++ b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
@@ -23,7 +23,11 @@ import org.json.JSONObject;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.io.PrintStream;
import java.io.ByteArrayOutputStream;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java
index 8a5e70af6c0..21c9875bb8b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java
@@ -93,7 +93,7 @@ public class AthenzRoleResolver implements RoleMembership.Resolver {
memberships.add(Role.hostedOperator);
}
if (tenant.isPresent() && isTenantAdmin(identity, tenant.get())) {
- memberships.add(Role.tenantAdmin).limitedTo(tenant.get().name());
+ memberships.add(Role.athenzTenantAdmin).limitedTo(tenant.get().name());
}
AthenzDomain principalDomain = identity.getDomain();
if (principalDomain.equals(SCREWDRIVER_DOMAIN)) {
@@ -102,11 +102,11 @@ public class AthenzRoleResolver implements RoleMembership.Resolver {
if (tenant.get() instanceof AthenzTenant) {
AthenzDomain tenantDomain = ((AthenzTenant) tenant.get()).domain();
if (hasDeployerAccess(identity, tenantDomain, application.get())) {
- memberships.add(Role.tenantPipelineOperator).limitedTo(tenant.get().name(), application.get());
+ memberships.add(Role.tenantPipeline).limitedTo(tenant.get().name(), application.get());
}
}
else {
- memberships.add(Role.tenantPipelineOperator).limitedTo(tenant.get().name(), application.get());
+ memberships.add(Role.tenantPipeline).limitedTo(tenant.get().name(), application.get());
}
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java
index 3776b1d2a13..e3d81e04591 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java
@@ -17,10 +17,8 @@ import java.util.Set;
*/
public enum PathGroup {
- /** Paths used for system management by operators */
+ /** Paths used for system management by operators. */
operator("/controller/v1/{*}",
- "/cost/v1/{*}",
- "/deployment/v1/{*}",
"/flags/v1/{*}",
"/nodes/v2/{*}",
"/orchestrator/v1/{*}",
@@ -28,57 +26,84 @@ public enum PathGroup {
"/provision/v2/{*}",
"/zone/v2/{*}"),
- /** Paths used for creating user tenants */
- onboardingUser("/application/v4/user"),
-
- /** Paths used for creating tenants with access control */
- onboarding("/application/v4/tenant/{ignored}"),
-
- /** Read-only paths used when onboarding tenants */
- onboardingInfo("/athenz/v1/",
- "/athenz/v1/domains",
- "/athenz/v1/properties"),
-
- /** Paths used for user management */
+ /** Paths used for user management. */
userManagement("/user/v1/{*}"), // TODO probably add tenant and application levels.
- /** Paths used by tenant administrators */
- tenantInfo("/application/v4/", // TODO move
- "/application/v4/tenant/"), // TODO move
+ /** Paths used for creating user tenants. */
+ user("/application/v4/user"),
- /** Paths used by tenant administrators */
+ /** Paths used for creating tenants with proper access control. */
tenant(Matcher.tenant,
- "/application/v4/tenant/{tenant}",
- "/application/v4/tenant/{tenant}/application/"),
+ "/application/v4/tenant/{tenant}"),
- /** Paths used by application administrators */
+ /** Paths used by tenant administrators. */
+ tenantInfo(Matcher.tenant,
+ "/application/v4/tenant/{tenant}/application/"),
+
+ /** Path for the base application resource. */
application(Matcher.tenant,
Matcher.application,
- "/application/v4/tenant/{tenant}/application/{application}",
- "/application/v4/tenant/{tenant}/application/{application}/deploying/{*}",
- "/application/v4/tenant/{tenant}/application/{application}/instance/{*}",
- "/application/v4/tenant/{tenant}/application/{application}/environment/dev/{*}",
- "/application/v4/tenant/{tenant}/application/{application}/environment/perf/{*}",
- "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}/global-rotation/override"),
-
- /** Paths used for deployments by build service(s) */
+ "/application/v4/tenant/{tenant}/application/{application}"),
+
+ /** Paths used by application administrators. */
+ applicationInfo(Matcher.tenant,
+ Matcher.application,
+ "/application/v4/tenant/{tenant}/application/{application}/deploying/{*}",
+ "/application/v4/tenant/{tenant}/application/{application}/instance/{*}",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}/logs",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}/suspended",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}/service/{*}",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}/global-rotation/{*}"),
+
+ /** Path used to restart application nodes. */ // TODO move to the above when everyone is on new pipeline.
+ applicationRestart(Matcher.tenant,
+ Matcher.application,
+ "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{ignored}/restart"),
+
+ /** Paths used for development deployments. */
+ developmentDeployment(Matcher.tenant,
+ Matcher.application,
+ "/application/v4/tenant/{tenant}/application/{application}/environment/dev/region/{region}/instance/{instance}",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/dev/region/{region}/instance/{instance}/deploy",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/perf/region/{region}/instance/{instance}",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/perf/region/{region}/instance/{instance}/deploy"),
+
+ /** Paths used for production deployments. */
+ productionDeployment(Matcher.tenant,
+ Matcher.application,
+ "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}/deploy",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/test/region/{region}/instance/{instance}",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/test/region/{region}/instance/{instance}/deploy",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/staging/region/{region}/instance/{instance}",
+ "/application/v4/tenant/{tenant}/application/{application}/environment/staging/region/{region}/instance/{instance}/deploy"),
+
+ /** Paths used for continuous deployment to production. */
+ submission(Matcher.tenant,
+ Matcher.application,
+ "/application/v4/tenant/{tenant}/application/{application}/submit"),
+
+ /** Paths used for other tasks by build services. */ // TODO: This will vanish.
buildService(Matcher.tenant,
Matcher.application,
"/application/v4/tenant/{tenant}/application/{application}/jobreport",
- "/application/v4/tenant/{tenant}/application/{application}/submit",
"/application/v4/tenant/{tenant}/application/{application}/promote",
- "/application/v4/tenant/{tenant}/application/{application}/environment/prod/{*}",
- "/application/v4/tenant/{tenant}/application/{application}/environment/test/{*}",
- "/application/v4/tenant/{tenant}/application/{application}/environment/staging/{*}"),
-
- /** Read-only paths providing information related to deployments */
- deploymentStatus("/badge/v1/{*}",
- "/zone/v1/{*}"),
-
- /** Paths used by some dashboard */
- dashboard("/",
- "/d/{*}",
- "/statuspage/v1/{*}");
+ "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/promote"),
+
+ /** Paths which contain (not very strictly) classified information about, e.g., customers. */
+ classifiedInfo("/athenz/v1/{*}",
+ "/cost/v1/{*}",
+ "/deployment/v1/{*}",
+ "/application/v4/",
+ "/application/v4/tenant/",
+ "/",
+ "/d/{*}",
+ "/statuspage/v1/{*}"
+ ),
+
+ /** Paths providing public information. */
+ publicInfo("/badge/v1/{*}",
+ "/zone/v1/{*}");
final List<String> pathSpecs;
final List<Matcher> matchers;
@@ -118,11 +143,11 @@ public enum PathGroup {
public boolean matches(String path, Context context) {
return get(path).map(p -> {
boolean match = true;
- String tenant = p.get("tenant");
+ String tenant = p.get(Matcher.tenant.name);
if (tenant != null && context.tenant().isPresent()) {
match = context.tenant().get().value().equals(tenant);
}
- String application = p.get("application");
+ String application = p.get(Matcher.application.name);
if (application != null && context.application().isPresent()) {
match &= context.application().get().value().equals(application);
}
@@ -138,8 +163,12 @@ public enum PathGroup {
application("{application}");
final String pattern;
+ final String name;
- Matcher(String pattern) { this.pattern = pattern; }
+ Matcher(String pattern) {
+ this.pattern = pattern;
+ this.name = pattern.substring(1, pattern.length() - 1);
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java
index 9c09cb193da..85702ac1b89 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java
@@ -1,51 +1,111 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.role;
+import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
import java.util.Set;
/**
* Policies for REST APIs in the controller. A policy is only considered when defined in a {@link Role}.
+ * A policy describes a set of {@link Privilege}s, which are valid for a set of {@link SystemName}s.
+ * A policy is evaluated with a {@link Context}, which provides the {@link SystemName} the policy is
+ * evaluated in, and any limitations to a specific {@link TenantName} or {@link ApplicationName}.
*
* @author mpolden
*/
public enum Policy {
- /** Operator policy allows access to everything in all systems */
+ /** Full access to everything. */
operator(Privilege.grant(Action.all())
.on(PathGroup.all())
.in(SystemName.all())),
- /**
- * Tenant policy allows tenants to access their own tenant, in all systems, and allows global read access in
- * selected systems
- */
- tenant(Privilege.grant(Action.all())
- .on(PathGroup.tenantInfo, PathGroup.tenant, PathGroup.application)
- .in(SystemName.all())),
-
- /** Build service policy only allows access relevant for build service(s) */
- buildService(Privilege.grant(Action.all())
- .on(PathGroup.buildService)
- .in(SystemName.all())),
-
- /** Unauthorized policy allows creation of tenants and read of everything in selected systems */
- unauthorized(Privilege.grant(Action.update)
- .on(PathGroup.onboardingUser)
- .in(SystemName.main, SystemName.cd, SystemName.dev),
- Privilege.grant(Action.create)
- .on(PathGroup.onboarding)
- .in(SystemName.main, SystemName.cd, SystemName.dev), // TODO System.all()
- Privilege.grant(Action.read)
- .on(PathGroup.onboardingInfo)
- .in(SystemName.main, SystemName.cd, SystemName.dev),
- Privilege.grant(Action.read)
- .on(PathGroup.all())
- .in(SystemName.main, SystemName.cd, SystemName.dev),
- Privilege.grant(Action.read)
- .on(PathGroup.deploymentStatus)
- .in(SystemName.all()));
+ /** Full access to user management in select systems. */
+ manager(Privilege.grant(Action.all())
+ .on(PathGroup.userManagement)
+ .in(SystemName.Public)),
+
+ /** Access to create a user tenant in select systems. */
+ userCreate(Privilege.grant(Action.update)
+ .on(PathGroup.user)
+ .in(SystemName.main, SystemName.cd, SystemName.dev)),
+
+ /** Access to create a tenant in select systems. */
+ tenantCreate(Privilege.grant(Action.create)
+ .on(PathGroup.tenant)
+ .in(SystemName.main, SystemName.cd, SystemName.dev)), // TODO SystemName.all()
+
+ /** Full access to tenant information and settings. */
+ tenantWrite(Privilege.grant(Action.write())
+ .on(PathGroup.tenant)
+ .in(SystemName.all())),
+
+ /** Read access to tenant information and settings. */
+ tenantRead(Privilege.grant(Action.read)
+ .on(PathGroup.tenant, PathGroup.tenantInfo)
+ .in(SystemName.all())),
+
+ /** Access to create application under a certain tenant. */
+ applicationCreate(Privilege.grant(Action.create)
+ .on(PathGroup.application)
+ .in(SystemName.all())),
+
+ /** Read access to application information and settings. */
+ applicationRead(Privilege.grant(Action.read)
+ .on(PathGroup.application, PathGroup.applicationInfo)
+ .in(SystemName.all())),
+
+ /** Read access to application information and settings. */
+ applicationUpdate(Privilege.grant(Action.update)
+ .on(PathGroup.application, PathGroup.applicationInfo)
+ .in(SystemName.all())),
+
+ /** Access to delete a certain application. */
+ applicationDelete(Privilege.grant(Action.delete)
+ .on(PathGroup.application)
+ .in(SystemName.all())),
+
+ /** Full access to application information and settings. */
+ applicationOperations(Privilege.grant(Action.write())
+ .on(PathGroup.applicationInfo, PathGroup.applicationRestart)
+ .in(SystemName.all())),
+
+ /** Full access to application development deployments. */
+ developmentDeployment(Privilege.grant(Action.all())
+ .on(PathGroup.developmentDeployment)
+ .in(SystemName.all())),
+
+ /** Full access to application production deployments. */
+ productionDeployment(Privilege.grant(Action.all())
+ .on(PathGroup.productionDeployment)
+ .in(SystemName.all())),
+
+ /** Read access to all application deployments. */
+ deploymentRead(Privilege.grant(Action.read)
+ .on(PathGroup.developmentDeployment, PathGroup.productionDeployment)
+ .in(SystemName.all())),
+
+ /** Full access to submissions for continuous deployment. */
+ submission(Privilege.grant(Action.all())
+ .on(PathGroup.submission)
+ .in(SystemName.all())),
+
+ /** Full access to the additional tasks needed for continuous deployment. */
+ deploymentPipeline(Privilege.grant(Action.all()) // TODO remove when everyone is on new pipeline.
+ .on(PathGroup.buildService, PathGroup.applicationRestart)
+ .in(SystemName.all())),
+
+ /** Read access to all information in select systems. */
+ classifiedRead(Privilege.grant(Action.read)
+ .on(PathGroup.all())
+ .in(SystemName.main, SystemName.cd, SystemName.dev)),
+
+ /** Read access to public info. */
+ publicRead(Privilege.grant(Action.read)
+ .on(PathGroup.publicInfo)
+ .in(SystemName.all()));
private final Set<Privilege> privileges;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java
index 01d1c7e0eb8..cae143a92a2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java
@@ -5,17 +5,70 @@ import java.util.EnumSet;
import java.util.Set;
/**
- * This declares all tenant roles known to the controller. A role contains one or more {@link Policy}'s which decide
+ * This declares all tenant roles known to the controller. A role contains one or more {@link Policy}s which decide
* what actions a member of a role can perform.
*
+ * Optionally, some role definition also inherit all policies from a "lower ranking" role. Read the list of roles
+ * from {@code everyone} to {@code tenantAdmin}, in order, to see what policies these roles.
+ *
* @author mpolden
+ * @author jonmv
*/
public enum Role {
+ /** Deus ex machina. */
hostedOperator(Policy.operator),
- tenantAdmin(Policy.tenant),
- tenantPipelineOperator(Policy.buildService),
- everyone(Policy.unauthorized);
+
+ /** Build service which may submit new applications for continuous deployment. */
+ buildService(Policy.submission,
+ Policy.applicationRead),
+
+ /** Base role which every user is part of. */
+ everyone(Policy.classifiedRead,
+ Policy.publicRead,
+ Policy.userCreate,
+ Policy.tenantCreate),
+
+ /** Application reader which can see all information about an application, its tenant and deployments. */
+ applicationReader(everyone,
+ Policy.tenantRead,
+ Policy.applicationRead,
+ Policy.deploymentRead),
+
+ /** Application developer with access to deploy to development zones. */
+ applicationDeveloper(applicationReader,
+ Policy.developmentDeployment),
+
+ /** Application operator with access to normal, operational tasks of an application. */
+ applicationOperator(applicationDeveloper,
+ Policy.applicationOperations),
+
+ /** Application administrator with full access to an already existing application, including emergency operations. */
+ applicationAdmin(applicationOperator,
+ Policy.applicationUpdate,
+ Policy.productionDeployment,
+ Policy.submission),
+
+ /** Tenant admin with full access to all tenant resources, including the ability to create new applications. */
+ tenantAdmin(applicationAdmin,
+ Policy.applicationCreate,
+ Policy.applicationDelete,
+ Policy.manager,
+ Policy.tenantWrite),
+
+ /** Build and continuous delivery service. */ // TODO replace with buildService, when everyone is on new pipeline.
+ tenantPipeline(Policy.submission,
+ Policy.deploymentPipeline,
+ Policy.productionDeployment),
+
+ /** Tenant administrator with full access to all child resources. */
+ athenzTenantAdmin(Policy.tenantWrite,
+ Policy.tenantRead,
+ Policy.applicationCreate,
+ Policy.applicationUpdate,
+ Policy.applicationDelete,
+ Policy.applicationOperations,
+ Policy.developmentDeployment); // TODO remove, as it is covered by applicationAdmin.
private final Set<Policy> policies;
@@ -23,6 +76,11 @@ public enum Role {
this.policies = EnumSet.copyOf(Set.of(policies));
}
+ Role(Role inherited, Policy... policies) {
+ this.policies = EnumSet.copyOf(Set.of(policies));
+ this.policies.addAll(inherited.policies);
+ }
+
/**
* Returns whether this role is allowed to perform action in given role context. Action is allowed if at least one
* policy evaluates to true.
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
index ecf849fc68c..ca8a2e34fdf 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
@@ -89,10 +89,7 @@ public class InternalDeploymentTester {
* Submits a new application, and returns the version of the new submission.
*/
public ApplicationVersion newSubmission() {
- ApplicationVersion version = jobs.submit(appId, BuildJob.defaultSourceRevision, "a@b", 2, applicationPackage, new byte[0]);
- tester.applicationStore().put(appId, version, applicationPackage.zippedContent());
- tester.applicationStore().put(testerId, version, new byte[0]);
- return version;
+ return jobs.submit(appId, BuildJob.defaultSourceRevision, "a@b", 2, applicationPackage, new byte[0]);
}
/**
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 26cf9c3127e..e8d5c8b8ca4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -453,6 +453,11 @@ public class ApplicationApiTest extends ControllerContainerTest {
// POST a 'restart application' command
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/restart", POST)
+ .userIdentity(USER_ID),
+ "Requested restart of tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default");
+
+ // POST a 'restart application' command
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/restart", POST)
.screwdriverIdentity(SCREWDRIVER_ID),
"Requested restart of tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default");
@@ -539,10 +544,28 @@ public class ApplicationApiTest extends ControllerContainerTest {
"to the old pipeline, please file a ticket at yo/vespa-support and request this.\"}",
400);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/production-us-west-1", DELETE)
+ // GET deployment job overview, after triggering system and staging test jobs.
+ assertEquals(2, tester.controller().applications().deploymentTrigger().triggerReadyJobs());
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job", GET)
+ .userIdentity(USER_ID),
+ new File("jobs.json"));
+
+ // GET system test job overview.
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test", GET)
+ .userIdentity(USER_ID),
+ new File("system-test-job.json"));
+
+ // GET system test run 1 details.
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test/run/1", GET)
+ .userIdentity(USER_ID),
+ new File("system-test-details.json"));
+
+ // DELETE a running job to have it aborted.
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/staging-test", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Nothing to abort.\"}");
+ "{\"message\":\"Aborting run 1 of stagingTest for tenant1.application1\"}");
+ // DELETE submission to unsubscribe from continuous deployment.
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", DELETE)
.userIdentity(HOSTED_VESPA_OPERATOR),
"{\"message\":\"Unregistered 'tenant1.application1' from internal deployment pipeline.\"}");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json
new file mode 100644
index 00000000000..1c8da68e138
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json
@@ -0,0 +1,130 @@
+{
+ "lastVersions": {
+ "platform": {
+ "platform": "6.1",
+ "at": 0,
+ "pending": "Waiting for current deployment to complete"
+ },
+ "application": {
+ "application": {
+ "hash": "1.0.44-d00d",
+ "build": 44,
+ "source": {
+ "gitRepository": "repo",
+ "gitBranch": "master",
+ "gitCommit": "d00d"
+ }
+ },
+ "at": (ignore),
+ "deploying": "0 of 1 complete"
+ }
+ },
+ "deploying": {
+ "application": {
+ "hash": "1.0.44-d00d",
+ "build": 44,
+ "source": {
+ "gitRepository": "repo",
+ "gitBranch": "master",
+ "gitCommit": "d00d"
+ }
+ }
+ },
+ "deployments": [
+ {}
+ ],
+ "jobs": {
+ "system-test": {
+ "runs": [
+ {
+ "id": 1,
+ "status": "running",
+ "start": (ignore),
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.44-d00d",
+ "build": 44,
+ "source": {
+ "gitRepository": "repo",
+ "gitBranch": "master",
+ "gitCommit": "d00d"
+ }
+ },
+ "steps": {
+ "deployReal": "unfinished",
+ "installReal": "unfinished",
+ "deployTester": "unfinished",
+ "installTester": "unfinished",
+ "startTests": "unfinished",
+ "endTests": "unfinished",
+ "deactivateReal": "unfinished",
+ "deactivateTester": "unfinished",
+ "report": "unfinished"
+ },
+ "tasks": {
+ "deploy": "running"
+ },
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test/run/1"
+ }
+ ],
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test"
+ },
+ "staging-test": {
+ "runs": [
+ {
+ "id": 1,
+ "status": "running",
+ "start": (ignore),
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.44-d00d",
+ "build": 44,
+ "source": {
+ "gitRepository": "repo",
+ "gitBranch": "master",
+ "gitCommit": "d00d"
+ }
+ },
+ "steps": {
+ "deployInitialReal": "unfinished",
+ "installInitialReal": "unfinished",
+ "deployReal": "unfinished",
+ "installReal": "unfinished",
+ "deployTester": "unfinished",
+ "installTester": "unfinished",
+ "startTests": "unfinished",
+ "endTests": "unfinished",
+ "deactivateReal": "unfinished",
+ "deactivateTester": "unfinished",
+ "report": "unfinished"
+ },
+ "tasks": {},
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/staging-test/run/1"
+ }
+ ],
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/staging-test"
+ },
+ "us-west-1": {
+ "runs": [
+ {
+ "status": "pending",
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.44-d00d",
+ "build": 44,
+ "source": {
+ "gitRepository": "repo",
+ "gitBranch": "master",
+ "gitCommit": "d00d"
+ }
+ },
+ "tasks": {
+ "system-test": "running",
+ "staging-test": "running"
+ }
+ }
+ ],
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/production-us-west-1"
+ }
+ }
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-details.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-details.json
new file mode 100644
index 00000000000..98d4e2db612
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-details.json
@@ -0,0 +1,4 @@
+{
+ "active": true,
+ "log": {}
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json
new file mode 100644
index 00000000000..71f647ead11
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json
@@ -0,0 +1,32 @@
+{
+ "1": {
+ "id": 1,
+ "status": "running",
+ "start": (ignore),
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.44-d00d",
+ "build": 44,
+ "source": {
+ "gitRepository": "repo",
+ "gitBranch": "master",
+ "gitCommit": "d00d"
+ }
+ },
+ "steps": {
+ "deployReal": "unfinished",
+ "installReal": "unfinished",
+ "deployTester": "unfinished",
+ "installTester": "unfinished",
+ "startTests": "unfinished",
+ "endTests": "unfinished",
+ "deactivateReal": "unfinished",
+ "deactivateTester": "unfinished",
+ "report": "unfinished"
+ },
+ "tasks": {
+ "deploy": "running"
+ },
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test/run/1"
+ }
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolverTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolverTest.java
index 5c86301d037..4628b95ad3c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolverTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolverTest.java
@@ -92,29 +92,29 @@ public class AthenzRoleResolverTest {
// Operators and tenant admins are tenant admins of their tenants.
assertEquals(Set.of(Context.limitedTo(TENANT, tester.controller().system())),
- resolver.membership(HOSTED_OPERATOR, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantAdmin));
+ resolver.membership(HOSTED_OPERATOR, APPLICATION_CONTEXT_PATH).contextsFor(Role.athenzTenantAdmin));
assertEquals(emptySet(), // TODO this is wrong, but we can't do better until we ask ZMS for roles.
- resolver.membership(TENANT_ADMIN, NO_CONTEXT_PATH).contextsFor(Role.tenantAdmin));
+ resolver.membership(TENANT_ADMIN, NO_CONTEXT_PATH).contextsFor(Role.athenzTenantAdmin));
assertEquals(Set.of(Context.limitedTo(TENANT, tester.controller().system())),
- resolver.membership(TENANT_ADMIN, TENANT_CONTEXT_PATH).contextsFor(Role.tenantAdmin));
+ resolver.membership(TENANT_ADMIN, TENANT_CONTEXT_PATH).contextsFor(Role.athenzTenantAdmin));
assertEquals(emptySet(),
- resolver.membership(TENANT_ADMIN, TENANT2_CONTEXT_PATH).contextsFor(Role.tenantAdmin));
+ resolver.membership(TENANT_ADMIN, TENANT2_CONTEXT_PATH).contextsFor(Role.athenzTenantAdmin));
assertEquals(emptySet(),
- resolver.membership(TENANT_PIPELINE, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantAdmin));
+ resolver.membership(TENANT_PIPELINE, APPLICATION_CONTEXT_PATH).contextsFor(Role.athenzTenantAdmin));
assertEquals(emptySet(),
- resolver.membership(USER, TENANT_CONTEXT_PATH).contextsFor(Role.tenantAdmin));
+ resolver.membership(USER, TENANT_CONTEXT_PATH).contextsFor(Role.athenzTenantAdmin));
// Only build services are pipeline operators of their applications.
assertEquals(emptySet(),
- resolver.membership(HOSTED_OPERATOR, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantPipelineOperator));
+ resolver.membership(HOSTED_OPERATOR, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantPipeline));
assertEquals(emptySet(),
- resolver.membership(TENANT_ADMIN, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantPipelineOperator));
+ resolver.membership(TENANT_ADMIN, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantPipeline));
assertEquals(Set.of(Context.limitedTo(TENANT, APPLICATION, tester.controller().system())),
- resolver.membership(TENANT_PIPELINE, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantPipelineOperator));
+ resolver.membership(TENANT_PIPELINE, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantPipeline));
assertEquals(emptySet(),
- resolver.membership(TENANT_PIPELINE, APPLICATION2_CONTEXT_PATH).contextsFor(Role.tenantPipelineOperator));
+ resolver.membership(TENANT_PIPELINE, APPLICATION2_CONTEXT_PATH).contextsFor(Role.tenantPipeline));
assertEquals(emptySet(),
- resolver.membership(USER, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantPipelineOperator));
+ resolver.membership(USER, APPLICATION_CONTEXT_PATH).contextsFor(Role.tenantPipeline));
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/PathGroupTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/PathGroupTest.java
index 4cf46933dcb..b4a3e674594 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/PathGroupTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/PathGroupTest.java
@@ -2,10 +2,12 @@ package com.yahoo.vespa.hosted.controller.role;
import org.junit.Test;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Pattern;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
@@ -15,45 +17,40 @@ import static org.junit.Assert.fail;
public class PathGroupTest {
@Test
- public void nonOverlappingGroups() {
- for (PathGroup pg : PathGroup.all()) {
- for (PathGroup pg2 : PathGroup.all()) {
- if (pg == pg2) continue;
- Set<String> overlapping = new LinkedHashSet<>(pg.pathSpecs);
- overlapping.retainAll(pg2.pathSpecs);
- if ( ! overlapping.isEmpty()) {
- fail("The following path specs overlap in " + pg + " and " + pg2 + ": " + overlapping);
- }
- }
- }
- }
-
- @Test
public void uniqueMatches() {
// Ensure that each path group contains at most one match for any given path, to avoid undefined context extraction.
- for (PathGroup group : PathGroup.values())
- for (String path1 : group.pathSpecs)
- for (String path2 : group.pathSpecs) {
- if (path1 == path2) continue;
+ Set<String> checkedAgainstSelf = new HashSet<>();
+ for (PathGroup group1 : PathGroup.values())
+ for (PathGroup group2 : PathGroup.values())
+ for (String path1 : group1.pathSpecs)
+ for (String path2 : group2.pathSpecs) {
+ if (path1.equals(path2)) {
+ if (checkedAgainstSelf.add(path1)) continue;
+ fail("Path '" + path1 + "' appears in both '" + group1 + "' and '" + group2 + "'.");
+ }
- String[] parts1 = path1.split("/");
- String[] parts2 = path2.split("/");
+ String[] parts1 = path1.split("/");
+ String[] parts2 = path2.split("/");
- int end = Math.min(parts1.length, parts2.length);
- // If one path has more parts than the other ...
- // and the other doesn't end with a wildcard matcher ...
- // and the longest one isn't just one wildcard longer ...
- if (end < parts1.length && (end == 0 || ! parts2[end - 1].equals("{*}")) && ! parts1[end].equals("{*}")) continue;
- if (end < parts2.length && (end == 0 || ! parts1[end - 1].equals("{*}")) && ! parts2[end].equals("{*}")) continue;
+ int end = Math.min(parts1.length, parts2.length);
+ // If one path has more parts than the other ...
+ // and the other doesn't end with a wildcard matcher ...
+ // and the longest one isn't just one wildcard longer ...
+ // then one is strictly longer than the other, and it's not a match.
+ if (end < parts1.length && (end == 0 || ! parts2[end - 1].equals("{*}")) && ! parts1[end].equals("{*}")) continue;
+ if (end < parts2.length && (end == 0 || ! parts1[end - 1].equals("{*}")) && ! parts2[end].equals("{*}")) continue;
- int i;
- for (i = 0; i < end; i++)
- if ( ! parts1[i].equals(parts2[i])
- && ! (parts1[i].startsWith("{") && parts1[i].endsWith("}"))
- && ! (parts2[i].startsWith("{") && parts2[i].endsWith("}"))) break;
+ int i;
+ for (i = 0; i < end; i++)
+ if ( ! parts1[i].equals(parts2[i])
+ && ! (parts1[i].startsWith("{") && parts1[i].endsWith("}"))
+ && ! (parts2[i].startsWith("{") && parts2[i].endsWith("}"))) break;
- if (i == end) fail("Paths '" + path1 + "' and '" + path2 + "' overlap.");
- }
+ if (i == end) fail("Paths '" + path1 + "' and '" + path2 + "' overlap.");
+ }
+
+ assertEquals(PathGroup.all().stream().mapToInt(group -> group.pathSpecs.size()).sum(),
+ checkedAgainstSelf.size());
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java
index 2638e3f74e6..972ddefb1a5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java
@@ -31,7 +31,7 @@ public class RoleMembershipTest {
@Test
public void tenant_membership() {
RoleMembership roles = RoleMembership.in(SystemName.main)
- .add(Role.tenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
+ .add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
.build();
assertFalse(roles.allows(Action.create, "/not/explicitly/defined"));
assertFalse("Deny access to operator API", roles.allows(Action.create, "/controller/v1/foo"));
@@ -40,15 +40,15 @@ public class RoleMembershipTest {
assertTrue(roles.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
RoleMembership multiContext = RoleMembership.in(SystemName.main)
- .add(Role.tenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
- .add(Role.tenantAdmin).limitedTo(TenantName.from("t2"), ApplicationName.from("a2"))
+ .add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
+ .add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t2"), ApplicationName.from("a2"))
.build();
assertFalse("Deny access to other tenant and app", multiContext.allows(Action.update, "/application/v4/tenant/t3/application/a3"));
assertTrue(multiContext.allows(Action.update, "/application/v4/tenant/t2/application/a2"));
assertTrue(multiContext.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
RoleMembership publicSystem = RoleMembership.in(SystemName.vaas)
- .add(Role.tenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
+ .add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
.build();
assertFalse(publicSystem.allows(Action.read, "/controller/v1/foo"));
assertTrue(multiContext.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
@@ -57,7 +57,7 @@ public class RoleMembershipTest {
@Test
public void build_service_membership() {
RoleMembership roles = RoleMembership.in(SystemName.main)
- .add(Role.tenantPipelineOperator).build();
+ .add(Role.tenantPipeline).build();
assertFalse(roles.allows(Action.create, "/not/explicitly/defined"));
assertFalse(roles.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
assertTrue(roles.allows(Action.create, "/application/v4/tenant/t1/application/a1/jobreport"));
@@ -67,8 +67,8 @@ public class RoleMembershipTest {
@Test
public void multi_role_membership() {
RoleMembership roles = RoleMembership.in(SystemName.main)
- .add(Role.tenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
- .add(Role.tenantPipelineOperator)
+ .add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
+ .add(Role.tenantPipeline)
.add(Role.everyone)
.build();
assertFalse(roles.allows(Action.create, "/not/explicitly/defined"));