aboutsummaryrefslogtreecommitdiffstats
path: root/controller-api/src
diff options
context:
space:
mode:
authorMartin Polden <martin.polden@gmail.com>2017-08-22 13:33:30 +0200
committerMartin Polden <martin.polden@gmail.com>2017-08-24 12:45:09 +0200
commit56b9aae782b782066d7e9603fce9095aa5cafd30 (patch)
tree8f4d1a590e3745737bf60b0b47ead54d186e5821 /controller-api/src
parent08c2a0490261a4666b65882616756ecfbe1c8c9b (diff)
Import controller
Diffstat (limited to 'controller-api/src')
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ApplicationApi.java42
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ApplicationResource.java49
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/EnvironmentResource.java98
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ServiceViewResource.java32
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/TenantResource.java47
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/UserResource.java27
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ApplicationReference.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/AthensDomainsResponse.java17
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeployOptions.java42
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeployResult.java43
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java58
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/GitRevision.java55
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceInformation.java34
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceReference.java32
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstancesReply.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JobStatus.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JobStatusList.java14
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JsonResponse.java20
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/LogEntry.java35
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ScrewdriverBuildJob.java47
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantCreateOptions.java48
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantInfo.java31
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantMetaData.java36
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantMigrateOptions.java22
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantPipelinesInfo.java21
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantType.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantUpdateOptions.java58
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantWithApplications.java39
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/UserInfo.java17
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/ConfigChangeActions.java32
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/RefeedAction.java48
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/RestartAction.java44
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/ServiceInfo.java38
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/BcpStatus.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/BrooklynStatusResource.java23
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/Environment.java38
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/Region.java39
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/CostJsonModel.java73
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/CostResource.java41
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ApplicationId.java29
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/AthensDomain.java29
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java69
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/EnvironmentId.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitBranch.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitCommit.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitRepository.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Hostname.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Identifier.java103
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/InstanceId.java19
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/NonDefaultIdentifier.java21
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Property.java15
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/PropertyId.java29
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RegionId.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RevisionId.java15
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ScrewdriverId.java30
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/SerializedIdentifier.java22
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java34
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserGroup.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java17
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java33
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Contacts.java136
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Issues.java182
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java66
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Properties.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ApplicationAction.java17
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/Athens.java24
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPrincipal.java59
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPublicKey.java48
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensService.java51
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/InvalidTokenException.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NToken.java21
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NTokenValidator.java12
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/UnauthorizedZmsClient.java23
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClient.java35
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClientFactory.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsException.java23
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsKeystore.java19
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensDbMock.java73
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensMock.java95
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/NTokenMock.java68
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientFactoryMock.java55
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientMock.java131
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/package-info.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/AttributeMapping.java35
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/Chef.java42
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/ChefMock.java112
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefEnvironment.java110
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefNode.java118
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefResource.java74
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/Client.java25
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/CookBook.java32
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/NodeResult.java20
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/PartialNode.java40
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/PartialNodeResult.java20
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerClient.java69
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java41
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Log.java15
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NoInstanceException.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/PrepareResponse.java22
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/ApplicationCost.java105
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Backend.java21
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/ClusterCost.java182
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Cost.java53
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostJsonModelAdapter.java93
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java31
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java24
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java74
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java27
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/EntityService.java28
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/MemoryEntityService.java37
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitHub.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitHubMock.java48
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitSha.java56
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraComment.java20
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraCreateIssue.java86
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraIssue.java45
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraIssues.java22
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraMock.java50
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/GlobalRoutingService.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/MemoryGlobalRoutingService.java20
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RotationStatus.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RoutingEndpoint.java30
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RoutingGenerator.java19
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/security/KeyService.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/security/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/ContactsMock.java31
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingIssues.java87
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/PropertiesMock.java26
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/package-info.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java31
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/nonpublic/HeaderFields.java14
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java39
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/statuspage/StatusPageResource.java24
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/statuspage/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/ZoneApi.java35
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/ZoneReference.java64
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java110
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneReference.java27
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneReferences.java31
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/package-info.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/ContextAttributes.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/NotFoundCheckedException.java23
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/package-info.java5
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeployOptionsTest.java31
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java153
169 files changed, 5993 insertions, 0 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ApplicationApi.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ApplicationApi.java
new file mode 100644
index 00000000000..4233f6308d5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ApplicationApi.java
@@ -0,0 +1,42 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4;
+
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.AthensDomainsResponse;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantInfo;
+import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantPipelinesInfo;
+
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author gv
+ */
+@Path("/v4/")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public interface ApplicationApi {
+
+ @GET
+ @Path(TenantResource.API_PATH)
+ List<TenantInfo> listTenants();
+
+ @Path(TenantResource.API_PATH + "/{tenantId}")
+ TenantResource tenant(@PathParam("tenantId")TenantId tenantId);
+
+ @GET
+ @Path("athensDomain")
+ AthensDomainsResponse listAthensDomains(@DefaultValue("") @QueryParam("prefix") String prefix);
+
+ @GET
+ @Path("tenant-pipeline")
+ TenantPipelinesInfo listTenantPipelines();
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ApplicationResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ApplicationResource.java
new file mode 100644
index 00000000000..e5833682c90
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ApplicationResource.java
@@ -0,0 +1,49 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4;
+
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.JobStatusList;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.ApplicationReference;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.InstancesReply;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author gv
+ */
+@Path("") //Ensures that the produces annotation is inherited
+@Produces(MediaType.APPLICATION_JSON)
+public interface ApplicationResource {
+
+ String API_PATH = "application";
+
+ @GET
+ List<ApplicationReference> listApplications();
+
+ @Path("{applicationId}")
+ @POST
+ ApplicationReference createApplication(@PathParam("applicationId") ApplicationId applicationId);
+
+ @Path("{applicationId}")
+ @DELETE
+ void deleteApplication(@PathParam("applicationId") ApplicationId applicationId);
+
+ @Path("{applicationId}/environment")
+ EnvironmentResource environment();
+
+ @Path("{applicationId}")
+ @GET
+ InstancesReply listInstances(@PathParam("applicationId") ApplicationId applicationId);
+
+ @Path("{applicationId}/deployment")
+ @GET
+ JobStatusList deployment(@PathParam("applicationId") ApplicationId applicationId);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/EnvironmentResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/EnvironmentResource.java
new file mode 100644
index 00000000000..4f1583dd905
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/EnvironmentResource.java
@@ -0,0 +1,98 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployResult;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.InstanceInformation;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.EnvironmentId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname;
+import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+import org.glassfish.jersey.media.multipart.FormDataBodyPart;
+import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
+import org.glassfish.jersey.media.multipart.FormDataParam;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.io.InputStream;
+
+/**
+ * @author Tony Vaagenes
+ * @author gv
+ */
+@Path("") //Ensures that the produces annotation is inherited
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface EnvironmentResource {
+
+ String API_PATH = "environment";
+
+ String APPLICATION_ZIP = "applicationZip";
+ String DEPLOY_OPTIONS = "deployOptions";
+
+ @POST
+ @Path("{environmentId}/region/{regionId}/instance/{instanceId}/deploy")
+ @Consumes({MediaType.MULTIPART_FORM_DATA})
+ DeployResult deploy(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("instanceId") InstanceId instanceId,
+ @FormDataParam(APPLICATION_ZIP) InputStream applicationZipFile,
+ @FormDataParam(APPLICATION_ZIP) FormDataContentDisposition fileMetaData,
+ @FormDataParam(DEPLOY_OPTIONS) FormDataBodyPart deployOptions);
+
+ @DELETE
+ @Path("{environmentId}/region/{regionId}/instance/{instanceId}")
+ String deactivate(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("instanceId") InstanceId instanceId);
+
+ @POST
+ @Path("{environmentId}/region/{regionId}/instance/{instanceId}/restart")
+ String restart(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("instanceId") InstanceId instanceId,
+ @QueryParam("hostname") Hostname hostname);
+
+ @GET
+ @Path("{environmentId}/region/{regionId}/instance/{instanceId}")
+ InstanceInformation instanceInfo(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("instanceId") InstanceId instanceId);
+
+ @GET
+ @Path("{environmentId}/region/{regionId}/instance/{instanceId}/converge")
+ JsonNode waitForConfigConverge(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("instanceId") InstanceId instanceId,
+ @QueryParam("timeout") long timeoutInSeconds);
+
+ @POST
+ @Path("{environmentId}/region/{regionId}/instance/{instanceId}/log")
+ JsonNode grabLog(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("instanceId") InstanceId instanceId);
+
+ @Path("{environmentId}/region/{regionId}/instance/{instanceId}/service")
+ ServiceViewResource service();
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ServiceViewResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ServiceViewResource.java
new file mode 100644
index 00000000000..c058a72341a
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/ServiceViewResource.java
@@ -0,0 +1,32 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4;
+
+import com.yahoo.vespa.serviceview.bindings.ApplicationView;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.HashMap;
+
+/**
+ * @author Stian Kristoffersen
+ */
+@Path("")
+@Produces(MediaType.APPLICATION_JSON)
+public interface ServiceViewResource {
+
+ @GET
+ @Path("")
+ @Produces(MediaType.APPLICATION_JSON)
+ ApplicationView getUserInfo();
+
+ @GET
+ @Path("{serviceIdentifier}/{apiParams: .*}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("rawtypes")
+ HashMap singleService(@PathParam("serviceIdentifier") String identifier,
+ @PathParam("apiParams") String apiParams);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/TenantResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/TenantResource.java
new file mode 100644
index 00000000000..8db6f982ef6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/TenantResource.java
@@ -0,0 +1,47 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4;
+
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantCreateOptions;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantInfo;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantMigrateOptions;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantUpdateOptions;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantWithApplications;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author Tony Vaagenes
+ */
+@Path("") //Ensures that the produces annotation is inherited
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface TenantResource {
+
+ String API_PATH = "tenant";
+
+ @GET
+ TenantWithApplications metaData();
+
+ @DELETE
+ TenantInfo deleteTenant();
+
+ @POST
+ TenantInfo createTenant(TenantCreateOptions tenantOptions);
+
+ @PUT
+ TenantInfo updateTenant(TenantUpdateOptions tenantOptions);
+
+ @Path(ApplicationResource.API_PATH)
+ ApplicationResource application();
+
+ @PUT
+ @Path("migrateTenantToAthens")
+ TenantInfo migrateTenantToAthens(TenantMigrateOptions tenantOptions);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/UserResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/UserResource.java
new file mode 100644
index 00000000000..a290323a245
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/UserResource.java
@@ -0,0 +1,27 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.UserInfo;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author gv
+ */
+@Path("/v4/user")
+@Produces(MediaType.APPLICATION_JSON)
+public interface UserResource {
+ @GET
+ @JsonInclude(value = JsonInclude.Include.NON_NULL)
+ UserInfo whoAmI(@QueryParam("userOverride") UserId userOverride);
+
+ @PUT
+ void createUserTenant();
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ApplicationReference.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ApplicationReference.java
new file mode 100644
index 00000000000..c542987e78f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ApplicationReference.java
@@ -0,0 +1,16 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+
+import java.net.URI;
+
+/**
+ * @author Stian Kristoffersen
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ApplicationReference {
+ public ApplicationId application;
+ public URI url;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/AthensDomainsResponse.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/AthensDomainsResponse.java
new file mode 100644
index 00000000000..400b973a4e1
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/AthensDomainsResponse.java
@@ -0,0 +1,17 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+
+import java.util.List;
+
+/**
+ * @author gv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AthensDomainsResponse extends JsonResponse<List<AthensDomain>> {
+ public AthensDomainsResponse(List<AthensDomain> athensDomainList) {
+ super(athensDomainList);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeployOptions.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeployOptions.java
new file mode 100644
index 00000000000..d8551898f7c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeployOptions.java
@@ -0,0 +1,42 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.component.Version;
+
+import java.util.Optional;
+
+/**
+ * @author gjoranv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DeployOptions {
+
+ public final Optional<ScrewdriverBuildJob> screwdriverBuildJob;
+ public final Optional<String> vespaVersion;
+ public final boolean ignoreValidationErrors;
+ public final boolean deployCurrentVersion;
+
+ @JsonCreator
+ public DeployOptions(@JsonProperty("screwdriverBuildJob") Optional<ScrewdriverBuildJob> screwdriverBuildJob,
+ @JsonProperty("vespaVersion") Optional<Version> vespaVersion,
+ @JsonProperty("ignoreValidationErrors") boolean ignoreValidationErrors,
+ @JsonProperty("deployCurrentVersion") boolean deployCurrentVersion) {
+ this.screwdriverBuildJob = screwdriverBuildJob;
+ this.vespaVersion = vespaVersion.map(Version::toString);
+ this.ignoreValidationErrors = ignoreValidationErrors;
+ this.deployCurrentVersion = deployCurrentVersion;
+ }
+
+ @Override
+ public String toString() {
+ return "DeployData{" +
+ "screwdriverBuildJob=" + screwdriverBuildJob.map(ScrewdriverBuildJob::toString).orElse("None") +
+ ", vespaVersion=" + vespaVersion.orElse("None") +
+ ", ignoreValidationErrors=" + ignoreValidationErrors +
+ ", deployCurrentVersion=" + deployCurrentVersion +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeployResult.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeployResult.java
new file mode 100644
index 00000000000..3a98926805f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeployResult.java
@@ -0,0 +1,43 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions;
+import com.yahoo.vespa.hosted.controller.api.identifiers.RevisionId;
+
+import java.util.List;
+
+/**
+ * @author gjoranv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DeployResult {
+
+ public final RevisionId revisionId;
+ public final Long applicationZipSize;
+ public final List<LogEntry> prepareMessages;
+ public final ConfigChangeActions configChangeActions;
+
+ @JsonCreator
+ public DeployResult(@JsonProperty("revisionId") RevisionId revisionId,
+ @JsonProperty("applicationZipSize") Long applicationZipSize,
+ @JsonProperty("prepareMessages") List<LogEntry> prepareMessages,
+ @JsonProperty("configChangeActions") ConfigChangeActions configChangeActions) {
+ this.revisionId = revisionId;
+ this.applicationZipSize = applicationZipSize;
+ this.prepareMessages = prepareMessages;
+ this.configChangeActions = configChangeActions;
+ }
+
+ @Override
+ public String toString() {
+ return "DeployResult{" +
+ "revisionId=" + revisionId.id() +
+ ", applicationZipSize=" + applicationZipSize +
+ ", prepareMessages=" + prepareMessages +
+ ", configChangeActions=" + configChangeActions +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java
new file mode 100644
index 00000000000..d014a82bf62
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/EndpointStatus.java
@@ -0,0 +1,58 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+/**
+ * Represent the operational status of a service endpoint (where the endpoint itself
+ * is identified by the container cluster id).
+ *
+ * The status of an endpoint may be assigned from the controller.
+ *
+ * @author smorgrav
+ */
+public class EndpointStatus {
+ private final String agent;
+ private final String reason;
+ private final Status status;
+ private final long epoch;
+
+ public enum Status {
+ in,
+ out,
+ unknown;
+ }
+
+ public EndpointStatus(Status status, String reason, String agent, long epoch) {
+ this.status = status;
+ this.reason = reason;
+ this.agent = agent;
+ this.epoch = epoch;
+ }
+
+ /**
+ * @return The agent responsible setting this status
+ */
+ public String getAgent() {
+ return agent;
+ }
+
+ /**
+ * @return The reason for this status (e.g. 'incident INCXXX')
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * @return The current status
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ /**
+ * @return The epoch for when this status became active
+ */
+ public long getEpoch() {
+ return epoch;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/GitRevision.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/GitRevision.java
new file mode 100644
index 00000000000..317da739103
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/GitRevision.java
@@ -0,0 +1,55 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.hosted.controller.api.identifiers.GitBranch;
+import com.yahoo.vespa.hosted.controller.api.identifiers.GitCommit;
+import com.yahoo.vespa.hosted.controller.api.identifiers.GitRepository;
+
+import java.util.Objects;
+
+/**
+ * @author gv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class GitRevision {
+
+ public final GitRepository repository;
+ public final GitBranch branch;
+ public final GitCommit commit;
+
+ @JsonCreator
+ public GitRevision(@JsonProperty("repository") GitRepository repository,
+ @JsonProperty("branch") GitBranch branch,
+ @JsonProperty("commit") GitCommit commit) {
+ this.repository = repository;
+ this.branch = branch;
+ this.commit = commit;
+ }
+
+ @Override
+ public String toString() {
+ return "GitRevision{" +
+ "repository=" + repository +
+ ", branch=" + branch +
+ ", commit=" + commit +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ GitRevision that = (GitRevision) o;
+ return Objects.equals(repository, that.repository) &&
+ Objects.equals(branch, that.branch) &&
+ Objects.equals(commit, that.commit);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(repository, branch, commit);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceInformation.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceInformation.java
new file mode 100644
index 00000000000..e862bd744dc
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceInformation.java
@@ -0,0 +1,34 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.yahoo.vespa.hosted.controller.api.cost.CostJsonModel;
+import com.yahoo.vespa.hosted.controller.api.identifiers.GitBranch;
+import com.yahoo.vespa.hosted.controller.api.identifiers.GitCommit;
+import com.yahoo.vespa.hosted.controller.api.identifiers.GitRepository;
+import com.yahoo.vespa.hosted.controller.api.identifiers.RevisionId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class InstanceInformation {
+ public List<URI> serviceUrls;
+ public URI nodes;
+ public URI elkUrl;
+ public URI yamasUrl;
+ public RevisionId revision;
+ public Long deployTimeEpochMs;
+ public Long expiryTimeEpochMs;
+
+ public ScrewdriverId screwdriverId;
+ public GitRepository gitRepository;
+ public GitBranch gitBranch;
+ public GitCommit gitCommit;
+
+ public CostJsonModel.Application cost;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceReference.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceReference.java
new file mode 100644
index 00000000000..6ac27d0bad9
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceReference.java
@@ -0,0 +1,32 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.yahoo.vespa.hosted.controller.api.bcp.BcpStatus;
+import com.yahoo.vespa.hosted.controller.api.identifiers.EnvironmentId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId;
+
+import java.net.URI;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class InstanceReference {
+ public EnvironmentId environment;
+ public RegionId region;
+ public InstanceId instance;
+ public BcpStatus bcpStatus;
+
+ public URI url;
+
+ public static InstanceReference createInstanceReference(InstanceId instanceId, RegionId regionId, EnvironmentId environmentId, URI uri) {
+ InstanceReference instanceReference = new InstanceReference();
+ instanceReference.instance = instanceId;
+ instanceReference.region = regionId;
+ instanceReference.environment = environmentId;
+ instanceReference.url = uri;
+ return instanceReference;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstancesReply.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstancesReply.java
new file mode 100644
index 00000000000..ff0c155460e
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstancesReply.java
@@ -0,0 +1,18 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Tony Vaagenes
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class InstancesReply {
+ public Set<URI> globalRotations;
+ public List<InstanceReference> instances;
+ public String compileVersion;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JobStatus.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JobStatus.java
new file mode 100644
index 00000000000..cce8a8c88fc
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JobStatus.java
@@ -0,0 +1,16 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * @author bratseth
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JobStatus {
+
+ public String jobType;
+ public long lastCompleted;
+ public boolean success;
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JobStatusList.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JobStatusList.java
new file mode 100644
index 00000000000..30af3291fbd
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JobStatusList.java
@@ -0,0 +1,14 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.util.List;
+
+/**
+ * @author bratseth
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JobStatusList {
+ public List<JobStatus> jobs;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JsonResponse.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JsonResponse.java
new file mode 100644
index 00000000000..3690644c49b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/JsonResponse.java
@@ -0,0 +1,20 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+/**
+ * @author gv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(Include.NON_NULL)
+public abstract class JsonResponse<DATA> {
+ public DATA data;
+
+ public JsonResponse(DATA data) {
+ this.data = data;
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/LogEntry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/LogEntry.java
new file mode 100644
index 00000000000..d5fc0addd70
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/LogEntry.java
@@ -0,0 +1,35 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author gjoranv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class LogEntry {
+
+ public final long time;
+ public final String level;
+ public final String message;
+
+ @JsonCreator
+ public LogEntry(@JsonProperty("time") long time,
+ @JsonProperty("level") String level,
+ @JsonProperty("message") String message) {
+ this.time = time;
+ this.level = level;
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return "LogEntry{" +
+ "time=" + time +
+ ", level='" + level + '\'' +
+ ", message='" + message + '\'' +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ScrewdriverBuildJob.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ScrewdriverBuildJob.java
new file mode 100644
index 00000000000..032b97c5424
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ScrewdriverBuildJob.java
@@ -0,0 +1,47 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
+
+import java.util.Objects;
+
+/**
+ * @author gjoranv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ScrewdriverBuildJob {
+ public final ScrewdriverId screwdriverId;
+ public final GitRevision gitRevision;
+
+ @JsonCreator
+ public ScrewdriverBuildJob(@JsonProperty("screwdriverId") ScrewdriverId screwdriverId,
+ @JsonProperty("gitRevision") GitRevision gitRevision) {
+ this.screwdriverId = screwdriverId;
+ this.gitRevision = gitRevision;
+ }
+
+ @Override
+ public String toString() {
+ return "ScrewdriverBuildJob{" +
+ "screwdriverId=" + screwdriverId +
+ ", gitRevision=" + gitRevision +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ScrewdriverBuildJob that = (ScrewdriverBuildJob) o;
+ return Objects.equals(screwdriverId, that.screwdriverId) &&
+ Objects.equals(gitRevision, that.gitRevision);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(screwdriverId, gitRevision);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantCreateOptions.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantCreateOptions.java
new file mode 100644
index 00000000000..4032a960b3c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantCreateOptions.java
@@ -0,0 +1,48 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
+import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+public class TenantCreateOptions {
+ public AthensDomain athensDomain;
+ public Property property;
+ public PropertyId propertyId;
+ public UserGroup userGroup;
+
+ public TenantCreateOptions() {}
+
+ public TenantCreateOptions(UserGroup userGroup, Property property, PropertyId propertyId) {
+ this.userGroup = userGroup;
+ this.property = property;
+ this.propertyId = propertyId;
+ }
+
+ public TenantCreateOptions(AthensDomain athensDomain, Property property, PropertyId propertyId) {
+ this.athensDomain = athensDomain;
+ this.property = property;
+ this.propertyId = propertyId;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("options: ");
+ sb.append("athens-domain='").append(this.athensDomain).append("', ");
+ sb.append("property='").append(this.property).append("'");
+ if (this.propertyId != null) {
+ sb.append(", propertyId='").append(this.propertyId).append("'");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantInfo.java
new file mode 100644
index 00000000000..ef1afbc9edf
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantInfo.java
@@ -0,0 +1,31 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+
+import java.net.URI;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
+public class TenantInfo {
+ public TenantId tenant;
+ // TODO: make optional
+ public TenantMetaData metaData;
+ public URI url;
+
+ // Required for Jackson deserialization
+ public TenantInfo() {}
+
+ public TenantInfo(TenantId tenantId, TenantMetaData metaData, URI url) {
+ this.tenant = tenantId;
+ this.metaData = metaData;
+ this.url = url;
+ }
+
+ public TenantInfo(TenantId tenant, URI url) {
+ this.tenant = tenant;
+ this.url = url;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantMetaData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantMetaData.java
new file mode 100644
index 00000000000..5ded1d8030e
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantMetaData.java
@@ -0,0 +1,36 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
+
+import java.util.Optional;
+
+/**
+ * @author gv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(value = Include.NON_EMPTY)
+public class TenantMetaData {
+ public TenantType type;
+ public Optional<AthensDomain> athensDomain;
+ public Optional<Property> property;
+ public Optional<UserGroup> userGroup;
+
+ // Required for Jackson deserialization
+ public TenantMetaData() {}
+
+ public TenantMetaData(TenantType type,
+ Optional<AthensDomain> athensDomain,
+ Optional<Property> property,
+ Optional<UserGroup> userGroup) {
+ this.type = type;
+ this.athensDomain = athensDomain;
+ this.property = property;
+ this.userGroup = userGroup;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantMigrateOptions.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantMigrateOptions.java
new file mode 100644
index 00000000000..9c748eafd38
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantMigrateOptions.java
@@ -0,0 +1,22 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+public class TenantMigrateOptions {
+
+ public AthensDomain athensDomain;
+
+ public TenantMigrateOptions() {}
+
+ public TenantMigrateOptions(AthensDomain athensDomain) {
+ this.athensDomain = athensDomain;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantPipelinesInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantPipelinesInfo.java
new file mode 100644
index 00000000000..a7f1fb408fe
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantPipelinesInfo.java
@@ -0,0 +1,21 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TenantPipelinesInfo {
+ public List<TenantPipelineInfo> tenantPipelines = new ArrayList<>();
+
+ public static class TenantPipelineInfo {
+ public String screwdriverId;
+ public String tenant;
+ public String application;
+ public String instance;
+ }
+
+ public List<TenantPipelineInfo> brokenTenantPipelines = new ArrayList<>();
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantType.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantType.java
new file mode 100644
index 00000000000..2c543af7bf8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantType.java
@@ -0,0 +1,11 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+/**
+ * @author bjorncs
+ */
+public enum TenantType {
+ OPSDB,
+ USER,
+ ATHENS
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantUpdateOptions.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantUpdateOptions.java
new file mode 100644
index 00000000000..9b2f24e2f62
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantUpdateOptions.java
@@ -0,0 +1,58 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author gv
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(value = JsonInclude.Include.NON_ABSENT)
+public class TenantUpdateOptions {
+ public final Property property;
+ public final Optional<UserGroup> userGroup;
+ public final Optional<AthensDomain> athensDomain;
+
+ @JsonCreator
+ public TenantUpdateOptions(@JsonProperty("property") Property property,
+ @JsonProperty("userGroup") Optional<UserGroup> userGroup,
+ @JsonProperty("athensDomain") Optional<AthensDomain> athensDomain) {
+ this.userGroup = userGroup;
+ this.property = property;
+ this.athensDomain = athensDomain;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TenantUpdateOptions that = (TenantUpdateOptions) o;
+ return Objects.equals(property, that.property) &&
+ Objects.equals(userGroup, that.userGroup) &&
+ Objects.equals(athensDomain, that.athensDomain);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(property, userGroup, athensDomain);
+ }
+
+ @Override
+ public String toString() {
+ return "TenantUpdateOptions{" +
+ "property=" + property +
+ ", userGroup=" + userGroup +
+ ", athensDomain=" + athensDomain +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantWithApplications.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantWithApplications.java
new file mode 100644
index 00000000000..de731d5c971
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/TenantWithApplications.java
@@ -0,0 +1,39 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
+
+import java.util.List;
+
+/**
+ * @author Tony Vaagenes
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+public class TenantWithApplications {
+ // TODO: use TenantMetaData instead of individual fields (requires dashboard updates)
+ public TenantType type;
+ public AthensDomain athensDomain;
+ public Property property;
+ public UserGroup userGroup;
+ public List<ApplicationReference> applications;
+
+ public TenantWithApplications() {}
+
+ public TenantWithApplications(
+ TenantType type,
+ AthensDomain athensDomain,
+ Property property,
+ UserGroup userGroup,
+ List<ApplicationReference> applications) {
+ this.type = type;
+ this.athensDomain = athensDomain;
+ this.property = property;
+ this.userGroup = userGroup;
+ this.applications = applications;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/UserInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/UserInfo.java
new file mode 100644
index 00000000000..2b2a089c543
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/UserInfo.java
@@ -0,0 +1,17 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+
+import java.util.List;
+
+/**
+ * @author gv
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class UserInfo {
+ public UserId user;
+ public boolean tenantExists;
+ public List<TenantInfo> tenants;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/ConfigChangeActions.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/ConfigChangeActions.java
new file mode 100644
index 00000000000..397461a829d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/ConfigChangeActions.java
@@ -0,0 +1,32 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ConfigChangeActions {
+ @JsonProperty("restart") public final List<RestartAction> restartActions;
+ @JsonProperty("refeed") public final List<RefeedAction> refeedActions;
+
+ @JsonCreator
+ public ConfigChangeActions(@JsonProperty("restart") List<RestartAction> restartActions,
+ @JsonProperty("refeed") List<RefeedAction> refeedActions) {
+ this.restartActions = restartActions;
+ this.refeedActions = refeedActions;
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigChangeActions{" +
+ "restartActions=" + restartActions +
+ ", refeedActions=" + refeedActions +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/RefeedAction.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/RefeedAction.java
new file mode 100644
index 00000000000..0546a3b5c44
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/RefeedAction.java
@@ -0,0 +1,48 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RefeedAction {
+ public final String name;
+ public final boolean allowed;
+ public final String documentType;
+ public final String clusterName;
+ public final List<ServiceInfo> services;
+ public final List<String> messages;
+
+ @JsonCreator
+ public RefeedAction(@JsonProperty("name") String name,
+ @JsonProperty("allowed") boolean allowed,
+ @JsonProperty("documentType") String documentType,
+ @JsonProperty("clusterName") String clusterName,
+ @JsonProperty("services") List<ServiceInfo> services,
+ @JsonProperty("messages") List<String> messages) {
+ this.name = name;
+ this.allowed = allowed;
+ this.documentType = documentType;
+ this.clusterName = clusterName;
+ this.services = services;
+ this.messages = messages;
+ }
+
+ @Override
+ public String toString() {
+ return "RefeedAction{" +
+ "name='" + name + '\'' +
+ ", allowed=" + allowed +
+ ", documentType='" + documentType + '\'' +
+ ", clusterName='" + clusterName + '\'' +
+ ", services=" + services +
+ ", messages=" + messages +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/RestartAction.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/RestartAction.java
new file mode 100644
index 00000000000..a760a26d47d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/RestartAction.java
@@ -0,0 +1,44 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RestartAction {
+ public final String clusterName;
+ public final String clusterType;
+ public final String serviceType;
+ public final List<ServiceInfo> services;
+ public final List<String> messages;
+
+ @JsonCreator
+ public RestartAction(@JsonProperty("clusterName") String clusterName,
+ @JsonProperty("clusterType") String clusterType,
+ @JsonProperty("serviceType") String serviceType,
+ @JsonProperty("services") List<ServiceInfo> services,
+ @JsonProperty("messages") List<String> messages) {
+ this.clusterName = clusterName;
+ this.clusterType = clusterType;
+ this.serviceType = serviceType;
+ this.services = services;
+ this.messages = messages;
+ }
+
+ @Override
+ public String toString() {
+ return "RestartAction{" +
+ "clusterName='" + clusterName + '\'' +
+ ", clusterType='" + clusterType + '\'' +
+ ", serviceType='" + serviceType + '\'' +
+ ", services=" + services +
+ ", messages=" + messages +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/ServiceInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/ServiceInfo.java
new file mode 100644
index 00000000000..8d03d2da440
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/ServiceInfo.java
@@ -0,0 +1,38 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ServiceInfo {
+ public final String serviceName;
+ public final String serviceType;
+ public final String configId;
+ public final String hostName;
+
+ @JsonCreator
+ public ServiceInfo(@JsonProperty("serviceName") String serviceName,
+ @JsonProperty("serviceType") String serviceType,
+ @JsonProperty("configId") String configId,
+ @JsonProperty("hostName")String hostName) {
+ this.serviceName = serviceName;
+ this.serviceType = serviceType;
+ this.configId = configId;
+ this.hostName = hostName;
+ }
+
+ @Override
+ public String toString() {
+ return "ServiceInfo{" +
+ "serviceName='" + serviceName + '\'' +
+ ", serviceType='" + serviceType + '\'' +
+ ", configId='" + configId + '\'' +
+ ", hostName='" + hostName + '\'' +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/package-info.java
new file mode 100644
index 00000000000..1201f148329
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/configserverbindings/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/package-info.java
new file mode 100644
index 00000000000..1eac6d8c296
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.application.v4.model;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/package-info.java
new file mode 100644
index 00000000000..e7b71b693a3
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.application.v4;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/BcpStatus.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/BcpStatus.java
new file mode 100644
index 00000000000..679d5fc5727
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/BcpStatus.java
@@ -0,0 +1,18 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.bcp;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class BcpStatus {
+ public String rotationStatus;
+ public String reason;
+
+ // For jackson
+ public BcpStatus() {}
+
+ public BcpStatus(String rotationStatus, String reason) {
+ this.rotationStatus = rotationStatus;
+ this.reason = reason;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/BrooklynStatusResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/BrooklynStatusResource.java
new file mode 100644
index 00000000000..c77f9fceef9
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/BrooklynStatusResource.java
@@ -0,0 +1,23 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.bcp;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author andreer
+ */
+@Path("") //Ensures that the produces annotation is inherited
+@Produces(MediaType.APPLICATION_JSON)
+public interface BrooklynStatusResource {
+
+ @GET
+ @Path("{rotation}")
+ @Produces(MediaType.APPLICATION_JSON)
+ JsonNode rotationStatus(@PathParam("rotation") String page);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/package-info.java
new file mode 100644
index 00000000000..2bb442c3db8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/bcp/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.bcp;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/Environment.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/Environment.java
new file mode 100644
index 00000000000..ad28d3ca5b5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/Environment.java
@@ -0,0 +1,38 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.configserver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Environment representation using the same definition as configserver. And allowing
+ * serialization/deserialization to/from JSON.
+ *
+ * @author Ulf Lilleengen
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Environment {
+ private final com.yahoo.config.provision.Environment environment;
+
+ public Environment(com.yahoo.config.provision.Environment environment) {
+ this.environment = environment;
+ }
+
+ @JsonValue
+ public String value() {
+ return environment.value();
+ }
+
+ @Override
+ public String toString() {
+ return value();
+ }
+
+ public com.yahoo.config.provision.Environment getEnvironment() {
+ return environment;
+ }
+
+ public Environment(String environment) {
+ this.environment = com.yahoo.config.provision.Environment.from(environment);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/Region.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/Region.java
new file mode 100644
index 00000000000..b7f1560eb67
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/Region.java
@@ -0,0 +1,39 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.configserver;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.yahoo.config.provision.RegionName;
+
+/**
+ * Region representation using the same definition as configserver. And allowing
+ * serialization/deserialization to/from JSON.
+ *
+ * @author Ulf Lilleengen
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Region {
+ private final RegionName region;
+
+ public Region(RegionName region) {
+ this.region = region;
+ }
+
+ @JsonValue
+ public String value() {
+ return region.value();
+ }
+
+ @Override
+ public String toString() { return value(); }
+
+ public RegionName getRegion() {
+ return region;
+ }
+
+ @JsonCreator
+ public Region(String region) {
+ this.region = com.yahoo.config.provision.RegionName.from(region);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/package-info.java
new file mode 100644
index 00000000000..f035e200661
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/configserver/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.configserver;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/CostJsonModel.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/CostJsonModel.java
new file mode 100644
index 00000000000..bfc451946f6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/CostJsonModel.java
@@ -0,0 +1,73 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.cost;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * JSON datamodel for the cost api.
+ *
+ * @author smorgrav
+ */
+public class CostJsonModel {
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Application {
+
+ @JsonProperty
+ public String zone;
+ @JsonProperty
+ public String tenant;
+ @JsonProperty
+ public String app;
+ @JsonProperty
+ public int tco;
+ @JsonProperty
+ public float utilization;
+ @JsonProperty
+ public float waste;
+ @JsonProperty
+ public Map<String, Cluster> cluster;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Cluster {
+
+ @JsonProperty
+ public int count;
+ @JsonProperty
+ public String resource;
+ @JsonProperty
+ public float utilization;
+ @JsonProperty
+ public int tco;
+ @JsonProperty
+ public String flavor;
+ @JsonProperty
+ public int waste;
+ @JsonProperty
+ public String type;
+ @JsonProperty
+ public HardwareResources util;
+ @JsonProperty
+ public HardwareResources usage;
+ @JsonProperty
+ public List<String> hostnames;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class HardwareResources {
+
+ @JsonProperty
+ public float mem;
+ @JsonProperty
+ public float disk;
+ @JsonProperty
+ public float cpu;
+ @JsonProperty("diskbusy")
+ public float diskBusy;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/CostResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/CostResource.java
new file mode 100644
index 00000000000..3cc6d682f4a
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/CostResource.java
@@ -0,0 +1,41 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.cost;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * Cost and Utilization APi for hosted Vespa.
+ *
+ * Used to give insight to PEG and application owners about
+ * TOC and if the application is reasonable scaled.
+ *
+ * @author smorgrav
+ */
+@Path("v1")
+@Produces(MediaType.APPLICATION_JSON)
+public interface CostResource {
+
+ @GET
+ @Path("/analysis/cpu")
+ List<CostJsonModel.Application> getCPUAnalysis();
+
+ @GET
+ @Produces("text/csv")
+ @Path("/csv")
+ String getCSV();
+
+ @GET
+ @Path("/apps")
+ List<CostJsonModel.Application> getApplicationsCost();
+
+ @GET
+ @Path("/apps/{environment}/{region}/{application}")
+ CostJsonModel.Application getApplicationCost(@PathParam("application") String appName,
+ @PathParam("region") String regionName,
+ @PathParam("environment") String envName);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/package-info.java
new file mode 100644
index 00000000000..8e95bd4f6f1
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/cost/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.cost;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ApplicationId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ApplicationId.java
new file mode 100644
index 00000000000..0a5f2809780
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ApplicationId.java
@@ -0,0 +1,29 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class ApplicationId extends NonDefaultIdentifier {
+
+ public ApplicationId(String id) {
+ super(id);
+ }
+
+ public static boolean isLegal(String id) {
+ return strictPattern.matcher(id).matches();
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ validateNoUpperCase();
+ }
+
+ public static void validate(String id) {
+ if (!isLegal(id)) {
+ throwInvalidId(id, "Must match pattern " + strictPattern, "application");
+ }
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/AthensDomain.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/AthensDomain.java
new file mode 100644
index 00000000000..eb8b5c5256b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/AthensDomain.java
@@ -0,0 +1,29 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class AthensDomain extends Identifier {
+
+ public AthensDomain(String id) {
+ super(id);
+ }
+
+ public boolean isTopLevelDomain() {
+ return !id().contains(".");
+ }
+
+ public AthensDomain getParent() {
+ return new AthensDomain(id().substring(0, lastDot()));
+ }
+
+ public String getName() {
+ return id().substring(lastDot() + 1);
+ }
+
+ private int lastDot() {
+ return id().lastIndexOf('.');
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java
new file mode 100644
index 00000000000..80fe98a4489
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java
@@ -0,0 +1,69 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+import com.yahoo.config.provision.Zone;
+
+/**
+ * Application + zone.
+ *
+ * @author smorgrav
+ * @author bratseth
+ */
+public class DeploymentId {
+
+ private final com.yahoo.config.provision.ApplicationId application;
+ private final Zone zone;
+
+ public DeploymentId(com.yahoo.config.provision.ApplicationId application, Zone zone) {
+ this.application = application;
+ this.zone = zone;
+ }
+
+ public com.yahoo.config.provision.ApplicationId applicationId() {
+ return application;
+ }
+ public Zone zone() { return zone; }
+
+
+ public String dottedString() {
+ return unCapitalize(applicationId().tenant().value()) + "."
+ + unCapitalize(applicationId().application().value()) + "."
+ + unCapitalize(zone.environment().value()) + "."
+ + unCapitalize(zone.region().value()) + "."
+ + unCapitalize(application.instance().value());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DeploymentId other = (DeploymentId) o;
+ if ( ! this.application.equals(other.application)) return false;
+ // TODO: Simplify when Zone implements equals
+ if ( ! this.zone.environment().equals(other.zone.environment())) return false;
+ if ( ! this.zone.region().equals(other.zone.region())) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ // TODO: Simplify when Zone implements hashCode
+ return application.hashCode() +
+ 7 * zone.environment().hashCode() +
+ 31 * zone.region().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return toUserFriendlyString();
+ }
+
+ public String toUserFriendlyString() {
+ return application + " in " + zone;
+ }
+
+ private static String unCapitalize(String str) {
+ return str.toLowerCase().substring(0,1) + str.substring(1);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/EnvironmentId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/EnvironmentId.java
new file mode 100644
index 00000000000..a09e802c251
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/EnvironmentId.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class EnvironmentId extends NonDefaultIdentifier {
+
+ public EnvironmentId(String id) {
+ super(id);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitBranch.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitBranch.java
new file mode 100644
index 00000000000..31402825d3c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitBranch.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class GitBranch extends Identifier {
+
+ public GitBranch(String id) {
+ super(id);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitCommit.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitCommit.java
new file mode 100644
index 00000000000..289b3ec59a0
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitCommit.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class GitCommit extends Identifier {
+
+ public GitCommit(String id) {
+ super(id);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitRepository.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitRepository.java
new file mode 100644
index 00000000000..50bbc0bd9f9
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/GitRepository.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class GitRepository extends Identifier {
+
+ public GitRepository(String id) {
+ super(id);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Hostname.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Hostname.java
new file mode 100644
index 00000000000..3f7437c5d0b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Hostname.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class Hostname extends Identifier {
+
+ public Hostname(String hostname) {
+ super(hostname);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Identifier.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Identifier.java
new file mode 100644
index 00000000000..70ebc8712d5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Identifier.java
@@ -0,0 +1,103 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * @author smorgrav
+ */
+public abstract class Identifier {
+
+ protected static final Pattern strictPattern = Pattern.compile("[a-z0-9][a-z0-9-]{0,26}[a-z0-9]");
+ private static final Pattern serializedIdentifierPattern = Pattern.compile("[a-zA-Z0-9_-]+");
+ private static final Pattern serializedPattern = Pattern.compile("[a-zA-Z0-9_.-]+");
+
+ private final String id;
+
+ @JsonCreator
+ public Identifier(String id) {
+ Objects.requireNonNull(id, "Id string cannot be null");
+ this.id = id;
+ validate();
+ }
+
+ public String toDns() {
+ return id.replace('_', '-');
+ }
+
+ @Override
+ public String toString() {
+ return id;
+ }
+
+ @JsonValue
+ public String id() { return id; }
+
+ public String capitalizedType() {
+ String simpleName = this.getClass().getSimpleName();
+ String suffix = "Id";
+ if (simpleName.endsWith(suffix)) {
+ simpleName = simpleName.substring(0, simpleName.length() - suffix.length());
+ }
+ return simpleName;
+ }
+
+ public void validate() {
+ if (id.equals("api")) {
+ throwInvalidId(id, "'api' not allowed.");
+ }
+ }
+
+ protected void validateSerialized() {
+ if (!serializedPattern.matcher(id).matches()) {
+ throwInvalidId(id, "Must match pattern " + serializedPattern);
+ }
+ }
+
+ protected void validateSerializedIdentifier() {
+ if (!serializedIdentifierPattern.matcher(id).matches()) {
+ throwInvalidId(id, "Must match pattern " + serializedIdentifierPattern);
+ }
+ }
+
+ protected void validateNoDefault() {
+ if (id.equals("default")) {
+ throwInvalidId(id, "'default' not allowed.");
+ }
+ }
+
+ protected void validateNoUpperCase() {
+ if (!id.equals(id.toLowerCase()))
+ throwInvalidId(id, "Uppercase not allowed.");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Identifier identity = (Identifier) o;
+
+ return id.equals(identity.id);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ public static void throwInvalidId(String id, String explanation) {
+ throw new IllegalArgumentException(String.format("Invalid id: %s. %s", id, explanation));
+ }
+
+ public static void throwInvalidId(String id, String explanation, String idName) {
+ throw new IllegalArgumentException(String.format("Invalid %s id: %s. %s", idName, id, explanation));
+ }
+
+}
+
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/InstanceId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/InstanceId.java
new file mode 100644
index 00000000000..6e3087cdcf6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/InstanceId.java
@@ -0,0 +1,19 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class InstanceId extends SerializedIdentifier {
+
+ public InstanceId(String id) {
+ super(id);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ validateNoUpperCase();
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/NonDefaultIdentifier.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/NonDefaultIdentifier.java
new file mode 100644
index 00000000000..96f0a9c43f0
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/NonDefaultIdentifier.java
@@ -0,0 +1,21 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * TODO: Class description
+ *
+ * @author smorgrav
+ */
+public abstract class NonDefaultIdentifier extends SerializedIdentifier {
+
+ public NonDefaultIdentifier(String id) {
+ super(id);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ validateNoDefault();
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Property.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Property.java
new file mode 100644
index 00000000000..7dde9002310
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Property.java
@@ -0,0 +1,15 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * A business property.
+ *
+ * @author smorgrav
+ */
+public class Property extends Identifier {
+
+ public Property(String id) {
+ super(id);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/PropertyId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/PropertyId.java
new file mode 100644
index 00000000000..c84cfb9b512
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/PropertyId.java
@@ -0,0 +1,29 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+import java.util.regex.Pattern;
+
+/**
+ * A business property ID.
+ *
+ * @author frodelu
+ */
+public class PropertyId extends Identifier {
+
+ private static final Pattern PATTERN = Pattern.compile("\\d+");
+
+ public PropertyId(String id) {
+ super(id);
+ }
+
+ /** Returns this id as a long */
+ public long value() { return Long.parseLong(id()); }
+
+ @Override
+ public void validate() {
+ super.validate();
+ if(!PATTERN.matcher(id()).matches()) {
+ throwInvalidId(id(), "Property id must match pattern: " + PATTERN);
+ }
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RegionId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RegionId.java
new file mode 100644
index 00000000000..bb6208ff8e3
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RegionId.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class RegionId extends NonDefaultIdentifier {
+
+ public RegionId(String id) {
+ super(id);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RevisionId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RevisionId.java
new file mode 100644
index 00000000000..11094c69707
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RevisionId.java
@@ -0,0 +1,15 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * An unique identifier of an application package.
+ *
+ * @author smorgrav
+ */
+public class RevisionId extends SerializedIdentifier {
+
+ public RevisionId(String id) {
+ super(id);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java
new file mode 100644
index 00000000000..aab18595d20
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java
@@ -0,0 +1,18 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class RotationId extends Identifier {
+
+ public RotationId(String id) {
+ super(id);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ validateSerialized();
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ScrewdriverId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ScrewdriverId.java
new file mode 100644
index 00000000000..b0fb72662c6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ScrewdriverId.java
@@ -0,0 +1,30 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+import java.util.regex.Pattern;
+
+/**
+ * @author smorgrav
+ * @author bjorncs
+ */
+public class ScrewdriverId extends Identifier {
+
+ // TODO: If only there was a separate type for this ...
+ // This demonstrates why this subclassing scheme is a bad idea
+ private static final Pattern PATTERN = Pattern.compile("\\d+");
+
+ public ScrewdriverId(String id) {
+ super(id);
+ }
+
+ /** Returns this id as a long */
+ public long value() { return Long.parseLong(id()); }
+
+ @Override
+ public void validate() {
+ super.validate();
+ if(!PATTERN.matcher(id()).matches()) {
+ throwInvalidId(id(), "Screwdriver id must match pattern: " + PATTERN);
+ }
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/SerializedIdentifier.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/SerializedIdentifier.java
new file mode 100644
index 00000000000..3660262f9c1
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/SerializedIdentifier.java
@@ -0,0 +1,22 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * TODO: Class description
+ *
+ * @author smorgrav
+ */
+
+public abstract class SerializedIdentifier extends Identifier {
+
+ public SerializedIdentifier(String id) {
+ super(id);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ validateSerializedIdentifier();
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java
new file mode 100644
index 00000000000..82cd6d80ec8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java
@@ -0,0 +1,34 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class TenantId extends NonDefaultIdentifier {
+
+ public TenantId(String id) {
+ super(id);
+ }
+
+ public boolean isUser() {
+ return id().startsWith("by-");
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ validateNoUpperCase();
+ }
+
+ public static void validate(String id) {
+ if (!strictPattern.matcher(id).matches()) {
+ throwInvalidId(id, "Must match pattern " + strictPattern, "tenant");
+ }
+ }
+
+ /** Return true if this is the user tenant of the given user */
+ public boolean isTenantFor(UserId userId) {
+ return id().equals("by-" + userId.id().replace('_', '-'));
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserGroup.java
new file mode 100644
index 00000000000..b6b0379bc90
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserGroup.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class UserGroup extends Identifier {
+
+ public UserGroup(String id) {
+ super(id);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java
new file mode 100644
index 00000000000..d2effc76827
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java
@@ -0,0 +1,17 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class UserId extends NonDefaultIdentifier {
+
+ public UserId(String id) {
+ super(id);
+ }
+
+ public TenantId toTenantId() {
+ return new TenantId("by-" + id().replace('_', '-'));
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java
new file mode 100644
index 00000000000..79210143d19
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+/**
+ * @author smorgrav
+ */
+public class ZoneId extends Identifier {
+
+ public ZoneId(EnvironmentId envId, RegionId regionId) {
+ super(envId.id() + ":" + regionId.id());
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/package-info.java
new file mode 100644
index 00000000000..211a2ab7fc0
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java
new file mode 100644
index 00000000000..bbd15707cde
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java
@@ -0,0 +1,33 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration;
+
+/**
+ * @author jvenstad
+ */
+public interface BuildService {
+
+ /**
+ * Enqueue a job defined by "buildJob in an external build system, and return the outcome of the enqueue request.
+ * This method should return @false only when a retry is in order, and @true otherwise, e.g., on succes, or for invalid jobs.
+ */
+ boolean trigger(BuildJob buildJob);
+
+ class BuildJob {
+
+ private final long projectId;
+ private final String jobName;
+
+ public BuildJob(long projectId, String jobName) {
+ this.projectId = projectId;
+ this.jobName = jobName;
+ }
+
+ public long projectId() { return projectId; }
+ public String jobName() { return jobName; }
+
+ @Override
+ public String toString() { return jobName + "@" + projectId; }
+
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Contacts.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Contacts.java
new file mode 100644
index 00000000000..329483a85c5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Contacts.java
@@ -0,0 +1,136 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Objects;
+
+import static com.yahoo.vespa.hosted.controller.api.integration.Contacts.Category.unknown;
+import static java.util.Comparator.reverseOrder;
+
+/**
+ * @author jvenstad
+ */
+public interface Contacts {
+
+ /**
+ * Returns the most relevant user of lowest non-empty level above that of @assignee, or, if no such user exists,
+ * the @assignee with @Category information.
+ */
+ static UserContact escalationTargetFrom(Collection<UserContact> userContacts, String assignee) {
+ return userContacts.stream()
+ .filter(contact -> ! contact.username().isEmpty()) // don't assign to empty names
+ .sorted(reverseOrder()).distinct() // Pick out the highest category per user.
+ // Keep the assignee, or the last user on the first non-empty level above her.
+ .sorted().reduce(new UserContact(assignee, assignee, unknown), (current, next) ->
+ next.is(assignee) || (current.is(assignee) ^ current.level() == next.level()) ? next : current);
+ }
+
+ /**
+ * Return a list of all contact entries for property with id @propertyId, where username is set.
+ */
+ Collection<UserContact> userContactsFor(long propertyId);
+
+ /** Returns the URL listing contacts for the given property */
+ URI contactsUri(long propertyId);
+
+ /**
+ * Return a target of escalation above @assignee, from the set of @UserContact entries found for @propertyId.
+ */
+ default UserContact escalationTargetFor(long propertyId, String assignee) {
+ return escalationTargetFrom(userContactsFor(propertyId), assignee);
+ }
+
+ /**
+ * A list of contact roles, in the order in which we look for escalation targets.
+ * Categories must be listed in increasing order of relevancy per level, and by increasing level.
+ */
+ enum Category {
+
+ unknown(-1, Level.none, "Unknown"),
+ admin(54, Level.grunt, "Administrator"), // TODO: Find more grunts?
+ businessOwner(567, Level.owner, "Business Owner"),
+ serviceOwner(646, Level.owner, "Service Engineering Owner"),
+ engineeringOwner(566, Level.owner, "Engineering Owner"),
+ vpBusiness(11, Level.VP, "VP Business"),
+ vpService(647, Level.VP, "VP Service Engineering"),
+ vpEngineering(9, Level.VP, "VP Engineering");
+
+ public final long id;
+ public final Level level;
+ public final String name;
+
+ Category(long id, Level level, String name) {
+ this.id = id;
+ this.level = level;
+ this.name = name;
+ }
+
+ /** Find the category for the given id, or unknown if the id is unknown. */
+ public static Category of(Long id) {
+ for (Category category : values())
+ if (category.id == id)
+ return category;
+ return unknown;
+ }
+
+ public enum Level {
+ none,
+ grunt,
+ owner,
+ VP;
+ }
+
+ }
+
+ /** Container class for user contact information; sorts by category and identifies by username. Immutable. */
+ class UserContact implements Comparable<UserContact> {
+
+ private final String username;
+ private final String name;
+ private final Category category;
+
+ public UserContact(String username, String name, Category category) {
+ Objects.requireNonNull(username, "username cannot be null");
+ Objects.requireNonNull(name, "name cannot be null");
+ Objects.requireNonNull(category, "category cannot be null");
+ this.username = username;
+ this.name = name;
+ this.category = category;
+ }
+
+ public String username() { return username; }
+ public String name() { return name; }
+ public Category category() { return category; }
+ public Category.Level level() { return category.level; }
+
+ public boolean is(String username) { return this.username.equals(username); }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ UserContact that = (UserContact) o;
+ return Objects.equals(username, that.username);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(username);
+ }
+
+ @Override
+ public int compareTo(@NotNull UserContact other) {
+ return category().compareTo(other.category());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s, %s, %s", username, name, category.name);
+ }
+
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Issues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Issues.java
new file mode 100644
index 00000000000..6b7464b9ed0
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Issues.java
@@ -0,0 +1,182 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author jvenstad
+ */
+public interface Issues {
+
+ /**
+ * Returns information about an issue.
+ * If this issue does not exist this returns an issue id containing the id and default values.
+ */
+ IssueInfo fetch(String issueId);
+
+ /**
+ * Returns the @Meta of all unresolved issues which have the same summary (and queue, if present) as @issue.
+ */
+ List<IssueInfo> fetchSimilarTo(Issue issue);
+
+ /**
+ * Files the given issue
+ *
+ * @return the id of the created issue
+ */
+ String file(Issue issue);
+
+ /**
+ * Update the description fields of the issue stored with id @issueId to be @description.
+ */
+ void update(String issueId, String description);
+
+ /**
+ * Set the assignee of the issue with id @issueId to the user with usename @assignee.
+ */
+ void reassign(String issueId, String assignee);
+
+ /**
+ * Add the user with username @watcher to the watcher list of the issue with id @issueId.
+ */
+ void addWatcher(String issueId, String watcher);
+
+ /**
+ * Post @comment as a comment to the issue with id @issueId.
+ */
+ void comment(String issueId, String comment);
+
+
+ /** Contains information used to file an issue with the responsible party; only @queue is mandatory. */
+ class Classification {
+
+ private final String queue;
+ private final String component;
+ private final String label;
+
+ public Classification(String queue, String component, String label) {
+ if (queue.isEmpty()) throw new IllegalArgumentException("Queue can not be empty!");
+
+ this.queue = queue;
+ this.component = component;
+ this.label = label;
+ }
+
+ public Classification(String queue) {
+ this(queue, null, null);
+ }
+
+ public Classification withComponent(String component) { return new Classification(queue, component, label); }
+ public Classification withLabel(String label) { return new Classification(queue, component, label); }
+
+ public String queue() { return queue; }
+ public Optional<String> component() { return Optional.ofNullable(component); }
+ public Optional<String> label() { return Optional.ofNullable(label); }
+
+ @Override
+ public String toString() {
+ return
+ "Queue : " + queue() + "\n" +
+ "Component : " + component() + "\n" +
+ "Label : " + label() + "\n";
+ }
+
+ }
+
+
+ /** Information about a stored issue */
+ class IssueInfo {
+
+ private final String id;
+ private final String key;
+ private final Instant updated;
+ private final Optional<String> assignee;
+ private final Status status;
+
+ public IssueInfo(String id, String key, Instant updated, Optional<String> assignee, Status status) {
+ if (assignee == null || assignee.isPresent() && assignee.get().isEmpty()) // TODO: Throw on these things
+ assignee = Optional.empty();
+ this.id = id;
+ this.key = key;
+ this.updated = updated;
+ this.assignee = assignee;
+ this.status = status;
+ }
+
+ public IssueInfo withAssignee(Optional<String> assignee) {
+ return new IssueInfo(id, key, updated, assignee, status);
+ }
+
+ public String id() { return id; }
+ public String key() { return key; }
+ public Instant updated() { return updated; }
+ public Optional<String> assignee() { return assignee; }
+ public Status status() { return status; }
+
+ public enum Status {
+
+ toDo("To Do"),
+ inProgress("In Progress"),
+ done("Done"),
+ noCategory("No Category");
+
+ private final String value;
+
+ Status(String value) { this.value = value; }
+
+ public static Status fromValue(String value) {
+ for (Status status : Status.values())
+ if (status.value.equals(value))
+ return status;
+ throw new IllegalArgumentException(value + " is not a valid status.");
+ }
+
+ }
+
+ }
+
+
+ /**
+ * A representation of an issue with a Vespa application which can be reported and escalated through an external issue service.
+ * This class is immutable.
+ *
+ * @author jvenstad
+ */
+ class Issue {
+
+ private final String summary;
+ private final String description;
+ private final Classification classification;
+
+ public Issue(String summary, String description, Classification classification) {
+ if (summary.isEmpty()) throw new IllegalArgumentException("Summary can not be empty.");
+ if (description.isEmpty()) throw new IllegalArgumentException("Description can not be empty.");
+
+ this.summary = summary;
+ this.description = description;
+ this.classification = classification;
+ }
+
+ public Issue(String summary, String description) {
+ this(summary, description, null);
+ }
+
+ public Issue with(Classification classification) {
+ return new Issue(summary, description, classification);
+ }
+ public Issue withDescription(String description) { return new Issue(summary, description, classification); }
+
+ /** Return a new @Issue with the description of @this, but with @appendage appended. */
+ public Issue append(String appendage) {
+ return new Issue(summary, description + "\n\n" + appendage, classification);
+ }
+
+ public String summary() { return summary; }
+ public String description() { return description; }
+ public Optional<Classification> classification() { return Optional.ofNullable(classification); }
+
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java
new file mode 100644
index 00000000000..2068bc7e92d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java
@@ -0,0 +1,66 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Zone;
+
+/**
+ * A service which returns metric values on request
+ *
+ * @author bratseth
+ */
+public interface MetricsService {
+
+ ApplicationMetrics getApplicationMetrics(ApplicationId application);
+
+ DeploymentMetrics getDeploymentMetrics(ApplicationId application, Zone zone);
+
+ class DeploymentMetrics {
+
+ private final double queriesPerSecond;
+ private final double writesPerSecond;
+ private final long documentCount;
+ private final double queryLatencyMillis;
+ private final double writeLatencyMillis;
+
+ public DeploymentMetrics(double queriesPerSecond, double writesPerSecond,
+ long documentCount,
+ double queryLatencyMillis, double writeLatencyMillis) {
+ this.queriesPerSecond = queriesPerSecond;
+ this.writesPerSecond = writesPerSecond;
+ this.documentCount = documentCount;
+ this.queryLatencyMillis = queryLatencyMillis;
+ this.writeLatencyMillis = writeLatencyMillis;
+ }
+
+ public double queriesPerSecond() { return queriesPerSecond; }
+
+ public double writesPerSecond() { return writesPerSecond; }
+
+ public long documentCount() { return documentCount; }
+
+ public double queryLatencyMillis() { return queryLatencyMillis; }
+
+ public double writeLatencyMillis() { return writeLatencyMillis; }
+
+ }
+
+ class ApplicationMetrics {
+
+ private final double queryServiceQuality;
+ private final double writeServiceQuality;
+
+ public ApplicationMetrics(double queryServiceQuality, double writeServiceQuality) {
+ this.queryServiceQuality = queryServiceQuality;
+ this.writeServiceQuality = writeServiceQuality;
+ }
+
+ /** Returns the quality of service for queries as a number between 1 (perfect) and 0 (none) */
+ public double queryServiceQuality() { return queryServiceQuality; }
+
+ /** Returns the quality of service for writes as a number between 1 (perfect) and 0 (none) */
+ public double writeServiceQuality() { return writeServiceQuality; }
+
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Properties.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Properties.java
new file mode 100644
index 00000000000..652b5495bc5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/Properties.java
@@ -0,0 +1,16 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration;
+
+import java.util.Optional;
+
+/**
+ * @author jvenstad
+ */
+public interface Properties {
+
+ /**
+ * Return the @Issues.Classification listed for the property with id @propertyId.
+ */
+ Optional<Issues.Classification> classificationFor(long propertyId);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ApplicationAction.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ApplicationAction.java
new file mode 100644
index 00000000000..cb5731164c8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ApplicationAction.java
@@ -0,0 +1,17 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+/**
+ * @author mpolden
+ */
+public enum ApplicationAction {
+ deploy("deployer"),
+ read("reader"),
+ write("writer");
+
+ public final String roleName;
+
+ ApplicationAction(String roleName) {
+ this.roleName = roleName;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/Athens.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/Athens.java
new file mode 100644
index 00000000000..c1f72fa4370
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/Athens.java
@@ -0,0 +1,24 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+
+/**
+ * Interface for integrating controller with Athens.
+ *
+ * @author mpolden
+ */
+public interface Athens {
+
+ String principalTokenHeader();
+ AthensPrincipal principalFrom(ScrewdriverId screwdriverId);
+ AthensPrincipal principalFrom(UserId userId);
+ NTokenValidator validator();
+ NToken nTokenFrom(String rawToken);
+ UnauthorizedZmsClient unauthorizedZmsClient();
+ ZmsClientFactory zmsClientFactory();
+ AthensDomain screwdriverDomain();
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPrincipal.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPrincipal.java
new file mode 100644
index 00000000000..58b878870b9
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPrincipal.java
@@ -0,0 +1,59 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+
+import java.security.Principal;
+import java.util.Objects;
+
+/**
+ * @author bjorncs
+ */
+public class AthensPrincipal implements Principal {
+
+ private final AthensDomain domain;
+ private final UserId userId;
+
+ public AthensPrincipal(AthensDomain domain, UserId userId) {
+ this.domain = domain;
+ this.userId = userId;
+ }
+
+ public UserId getUserId() {
+ return userId;
+ }
+
+ public AthensDomain getDomain() {
+ return domain;
+ }
+
+ public String toYRN() {
+ return domain.id() + "." + userId.id();
+ }
+
+ @Override
+ public String toString() {
+ return toYRN();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AthensPrincipal that = (AthensPrincipal) o;
+ return Objects.equals(domain, that.domain) &&
+ Objects.equals(userId, that.userId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(domain, userId);
+ }
+
+ @Override
+ public String getName() {
+ return userId.id();
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPublicKey.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPublicKey.java
new file mode 100644
index 00000000000..9bbb5f28d8f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPublicKey.java
@@ -0,0 +1,48 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import java.security.PublicKey;
+import java.util.Objects;
+
+/**
+ * @author bjorncs
+ */
+public class AthensPublicKey {
+ private final PublicKey publicKey;
+ private final String keyId;
+
+ public AthensPublicKey(PublicKey publicKey, String keyId) {
+ this.publicKey = publicKey;
+ this.keyId = keyId;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AthensPublicKey that = (AthensPublicKey) o;
+ return Objects.equals(publicKey, that.publicKey) &&
+ Objects.equals(keyId, that.keyId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(publicKey, keyId);
+ }
+
+ @Override
+ public String toString() {
+ return "AthensPublicKey{" +
+ "publicKey=" + publicKey +
+ ", keyId='" + keyId + '\'' +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensService.java
new file mode 100644
index 00000000000..42af966be3d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensService.java
@@ -0,0 +1,51 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+
+import java.util.Objects;
+
+/**
+ * @author bjorncs
+ */
+public class AthensService {
+
+ private final AthensDomain domain;
+ private final String serviceName;
+
+ public AthensService(AthensDomain domain, String serviceName) {
+ this.domain = domain;
+ this.serviceName = serviceName;
+ }
+
+ public String toFullServiceName() {
+ return domain.id() + "." + serviceName;
+ }
+
+ public AthensDomain getDomain() {
+ return domain;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AthensService that = (AthensService) o;
+ return Objects.equals(domain, that.domain) &&
+ Objects.equals(serviceName, that.serviceName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(domain, serviceName);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("AthensService(%s)", toFullServiceName());
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/InvalidTokenException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/InvalidTokenException.java
new file mode 100644
index 00000000000..9c21d5814cb
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/InvalidTokenException.java
@@ -0,0 +1,11 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+/**
+ * @author bjorncs
+ */
+public class InvalidTokenException extends Exception {
+ public InvalidTokenException(String message) {
+ super(message);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NToken.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NToken.java
new file mode 100644
index 00000000000..b74872b4c6a
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NToken.java
@@ -0,0 +1,21 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+
+import java.security.PublicKey;
+
+/**
+ * @author mpolden
+ */
+public interface NToken {
+
+ AthensPrincipal getPrincipal();
+ UserId getUser();
+ AthensDomain getDomain();
+ String getToken();
+ String getKeyId();
+ void validateSignatureAndExpiration(PublicKey publicKey) throws InvalidTokenException;
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NTokenValidator.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NTokenValidator.java
new file mode 100644
index 00000000000..905d7d864a3
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NTokenValidator.java
@@ -0,0 +1,12 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+/**
+ * @author mpolden
+ */
+public interface NTokenValidator {
+
+ void preloadPublicKeys();
+ AthensPrincipal validate(NToken nToken) throws InvalidTokenException;
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/UnauthorizedZmsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/UnauthorizedZmsClient.java
new file mode 100644
index 00000000000..d1996bdbd45
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/UnauthorizedZmsClient.java
@@ -0,0 +1,23 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+
+import java.util.List;
+
+/**
+ * @author gv
+ */
+public class UnauthorizedZmsClient {
+
+ private final ZmsClient client;
+
+ public UnauthorizedZmsClient(ZmsClientFactory zmsClientFactory) {
+ client = zmsClientFactory.createClientWithoutPrincipal();
+ }
+
+ public List<AthensDomain> getDomainList(String prefix) {
+ return client.getDomainList(prefix);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClient.java
new file mode 100644
index 00000000000..7ff54957e16
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClient.java
@@ -0,0 +1,35 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+public interface ZmsClient {
+ void createTenant(AthensDomain tenantDomain);
+
+ void deleteTenant(AthensDomain tenantDomain);
+
+ void addApplication(AthensDomain tenantDomain, ApplicationId applicationName);
+
+ void deleteApplication(AthensDomain tenantDomain, ApplicationId applicationName);
+
+ boolean hasApplicationAccess(AthensPrincipal principal, ApplicationAction action, AthensDomain tenantDomain, ApplicationId applicationName);
+
+ boolean hasTenantAdminAccess(AthensPrincipal principal, AthensDomain tenantDomain);
+
+ // Used before vespa tenancy is established for the domain.
+ boolean isDomainAdmin(AthensPrincipal principal, AthensDomain domain);
+
+ List<AthensDomain> getDomainList(String prefix);
+
+ List<AthensDomain> getTenantDomainsForUser(AthensPrincipal principal);
+
+ AthensPublicKey getPublicKey(AthensService service, String keyId);
+
+ List<AthensPublicKey> getPublicKeys(AthensService service);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClientFactory.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClientFactory.java
new file mode 100644
index 00000000000..24a2d67ebf6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClientFactory.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+/**
+ * @author bjorncs
+ */
+public interface ZmsClientFactory {
+ ZmsClient createClientWithServicePrincipal();
+
+ ZmsClient createClientWithAuthorizedServiceToken(NToken authorizedServiceToken);
+
+ ZmsClient createClientWithoutPrincipal();
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsException.java
new file mode 100644
index 00000000000..ed5b2daca86
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsException.java
@@ -0,0 +1,23 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+/**
+ * @author bjorncs
+ */
+public class ZmsException extends RuntimeException {
+
+ private final int code;
+
+ public ZmsException(Throwable t, int code) {
+ super(t.getMessage(), t);
+ this.code = code;
+ }
+
+ public ZmsException(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsKeystore.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsKeystore.java
new file mode 100644
index 00000000000..4f8e5f5ff05
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsKeystore.java
@@ -0,0 +1,19 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import java.security.PublicKey;
+import java.util.Optional;
+
+/**
+ * Interface for a keystore containing public keys for Athens services
+ *
+ * @author bjorncs
+ */
+@FunctionalInterface
+public interface ZmsKeystore {
+ Optional<PublicKey> getPublicKey(AthensService service, String keyId);
+
+ default void preloadKeys(AthensService service) {
+ // Default implementation is noop
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensDbMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensDbMock.java
new file mode 100644
index 00000000000..8a02d0dcff5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensDbMock.java
@@ -0,0 +1,73 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens.mock;
+
+import com.yahoo.vespa.hosted.controller.api.integration.athens.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author bjorncs
+ */
+public class AthensDbMock {
+
+ public final Map<AthensDomain, Domain> domains = new HashMap<>();
+
+ public AthensDbMock addDomain(Domain domain) {
+ domains.put(domain.name, domain);
+ return this;
+ }
+
+ public static class Domain {
+
+ public final AthensDomain name;
+ public final Set<AthensPrincipal> admins = new HashSet<>();
+ public final Set<AthensPrincipal> tenantAdmins = new HashSet<>();
+ public final Map<ApplicationId, Application> applications = new HashMap<>();
+ public boolean isVespaTenant = false;
+
+ public Domain(AthensDomain name) {
+ this.name = name;
+ }
+
+ public Domain admin(AthensPrincipal user) {
+ admins.add(user);
+ return this;
+ }
+
+ public Domain tenantAdmin(AthensPrincipal user) {
+ tenantAdmins.add(user);
+ return this;
+ }
+
+ /**
+ * Simulates establishing Vespa tenancy in Athens.
+ */
+ public void markAsVespaTenant() {
+ isVespaTenant = true;
+ }
+
+ }
+
+ public static class Application {
+
+ public final Map<ApplicationAction, Set<AthensPrincipal>> acl = new HashMap<>();
+
+ public Application() {
+ acl.put(ApplicationAction.deploy, new HashSet<>());
+ acl.put(ApplicationAction.read, new HashSet<>());
+ acl.put(ApplicationAction.write, new HashSet<>());
+ }
+
+ public Application addRoleMember(ApplicationAction action, AthensPrincipal user) {
+ acl.get(action).add(user);
+ return this;
+ }
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensMock.java
new file mode 100644
index 00000000000..a993c6e3da3
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensMock.java
@@ -0,0 +1,95 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens.mock;
+
+import com.google.inject.Inject;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.InvalidTokenException;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.NTokenValidator;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.UnauthorizedZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClientFactory;
+
+/**
+ * @author mpolden
+ */
+public class AthensMock extends AbstractComponent implements Athens {
+
+ private static final AthensDomain userDomain = new AthensDomain("domain1");
+ private static final AthensDomain screwdriverDomain = new AthensDomain("screwdriver-domain");
+
+ private final ZmsClientFactory zmsClientFactory;
+ private final UnauthorizedZmsClient unauthorizedZmsClient;
+ private final NTokenValidator nTokenValidator;
+
+ public AthensMock(AthensDbMock athensDb, NTokenValidator nTokenValidator) {
+ this.zmsClientFactory = new ZmsClientFactoryMock(athensDb);
+ this.unauthorizedZmsClient = new UnauthorizedZmsClient(zmsClientFactory);
+ this.nTokenValidator = nTokenValidator;
+ }
+
+ public AthensMock(AthensDbMock athensDbMock) {
+ this(athensDbMock, mockValidator);
+ }
+
+ @Inject
+ public AthensMock() {
+ this(new AthensDbMock(), mockValidator);
+ }
+
+ @Override
+ public String principalTokenHeader() {
+ return "X-Athens-Token";
+ }
+
+ @Override
+ public AthensPrincipal principalFrom(ScrewdriverId screwdriverId) {
+ return new AthensPrincipal(screwdriverDomain, new UserId("screwdriver-" + screwdriverId.id()));
+ }
+
+ @Override
+ public AthensPrincipal principalFrom(UserId userId) {
+ return new AthensPrincipal(userDomain, userId);
+ }
+
+ @Override
+ public NTokenValidator validator() {
+ return nTokenValidator;
+ }
+
+ @Override
+ public NToken nTokenFrom(String rawToken) {
+ return new NTokenMock(rawToken);
+ }
+
+ @Override
+ public UnauthorizedZmsClient unauthorizedZmsClient() {
+ return unauthorizedZmsClient;
+ }
+
+ @Override
+ public ZmsClientFactory zmsClientFactory() {
+ return zmsClientFactory;
+ }
+
+ @Override
+ public AthensDomain screwdriverDomain() {
+ return screwdriverDomain;
+ }
+
+ private static final NTokenValidator mockValidator = new NTokenValidator() {
+ @Override
+ public void preloadPublicKeys() {
+ }
+
+ @Override
+ public AthensPrincipal validate(NToken nToken) throws InvalidTokenException {
+ return nToken.getPrincipal();
+ }
+ };
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/NTokenMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/NTokenMock.java
new file mode 100644
index 00000000000..ae23a69e409
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/NTokenMock.java
@@ -0,0 +1,68 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens.mock;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.InvalidTokenException;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken;
+
+import java.security.PublicKey;
+import java.util.Objects;
+
+/**
+ * @author mpolden
+ */
+public class NTokenMock implements NToken {
+
+ private static final AthensDomain domain = new AthensDomain("test");
+ private static final UserId userId = new UserId("user");
+
+ private final String rawToken;
+
+ public NTokenMock(String rawToken) {
+ this.rawToken = rawToken;
+ }
+
+ @Override
+ public AthensPrincipal getPrincipal() {
+ return new AthensPrincipal(domain, userId);
+ }
+
+ @Override
+ public UserId getUser() {
+ return userId;
+ }
+
+ @Override
+ public AthensDomain getDomain() {
+ return domain;
+ }
+
+ @Override
+ public String getToken() {
+ return "test-token";
+ }
+
+ @Override
+ public String getKeyId() {
+ return "test-key";
+ }
+
+ @Override
+ public void validateSignatureAndExpiration(PublicKey publicKey) throws InvalidTokenException {
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NTokenMock)) return false;
+ NTokenMock that = (NTokenMock) o;
+ return Objects.equals(rawToken, that.rawToken);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(rawToken);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientFactoryMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientFactoryMock.java
new file mode 100644
index 00000000000..73d971a27fe
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientFactoryMock.java
@@ -0,0 +1,55 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens.mock;
+
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClientFactory;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author bjorncs
+ */
+public class ZmsClientFactoryMock extends AbstractComponent implements ZmsClientFactory {
+
+ private static final Logger log = Logger.getLogger(ZmsClientFactoryMock.class.getName());
+
+ private final AthensDbMock athens;
+
+ public ZmsClientFactoryMock() {
+ this(new AthensDbMock());
+ }
+
+ ZmsClientFactoryMock(AthensDbMock athens) {
+ this.athens = athens;
+ }
+
+ public AthensDbMock getSetup() {
+ return athens;
+ }
+
+ @Override
+ public ZmsClient createClientWithServicePrincipal() {
+ log("createClientWithServicePrincipal()");
+ return new ZmsClientMock(athens);
+ }
+
+ @Override
+ public ZmsClient createClientWithAuthorizedServiceToken(NToken authorizedServiceToken) {
+ log("createClientWithAuthorizedServiceToken(authorizedServiceToken='%s')", authorizedServiceToken);
+ return new ZmsClientMock(athens);
+ }
+
+ @Override
+ public ZmsClient createClientWithoutPrincipal() {
+ log("createClientWithoutPrincipal()");
+ return new ZmsClientMock(athens);
+ }
+
+ private static void log(String format, Object... args) {
+ log.log(Level.INFO, String.format(format, args));
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientMock.java
new file mode 100644
index 00000000000..97f391f792d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientMock.java
@@ -0,0 +1,131 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athens.mock;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPublicKey;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensService;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClient;
+import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author bjorncs
+ */
+public class ZmsClientMock implements ZmsClient {
+
+ private static final Logger log = Logger.getLogger(ZmsClientMock.class.getName());
+
+ private final AthensDbMock athens;
+
+ public ZmsClientMock(AthensDbMock athens) {
+ this.athens = athens;
+ }
+
+ @Override
+ public void createTenant(AthensDomain tenantDomain) {
+ log("createTenant(tenantDomain='%s')", tenantDomain);
+ getDomainOrThrow(tenantDomain, false).isVespaTenant = true;
+ }
+
+ @Override
+ public void deleteTenant(AthensDomain tenantDomain) {
+ log("deleteTenant(tenantDomain='%s')", tenantDomain);
+ AthensDbMock.Domain domain = getDomainOrThrow(tenantDomain, false);
+ domain.isVespaTenant = false;
+ domain.applications.clear();
+ domain.tenantAdmins.clear();
+ }
+
+ @Override
+ public void addApplication(AthensDomain tenantDomain, ApplicationId applicationName) {
+ log("addApplication(tenantDomain='%s', applicationName='%s')", tenantDomain, applicationName);
+ AthensDbMock.Domain domain = getDomainOrThrow(tenantDomain, true);
+ if (!domain.applications.containsKey(applicationName)) {
+ domain.applications.put(applicationName, new AthensDbMock.Application());
+ }
+ }
+
+ @Override
+ public void deleteApplication(AthensDomain tenantDomain, ApplicationId applicationName) {
+ log("addApplication(tenantDomain='%s', applicationName='%s')", tenantDomain, applicationName);
+ getDomainOrThrow(tenantDomain, true).applications.remove(applicationName);
+ }
+
+ @Override
+ public boolean hasApplicationAccess(AthensPrincipal principal, ApplicationAction action, AthensDomain tenantDomain, ApplicationId applicationName) {
+ log("hasApplicationAccess(principal='%s', action='%s', tenantDomain='%s', applicationName='%s')",
+ principal, action, tenantDomain, applicationName);
+ AthensDbMock.Domain domain = getDomainOrThrow(tenantDomain, true);
+ AthensDbMock.Application application = domain.applications.get(applicationName);
+ if (application == null) {
+ throw zmsException(400, "Application '%s' not found", applicationName);
+ }
+ return domain.admins.contains(principal) || application.acl.get(action).contains(principal);
+ }
+
+ @Override
+ public boolean hasTenantAdminAccess(AthensPrincipal principal, AthensDomain tenantDomain) {
+ log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", principal, tenantDomain);
+ return isDomainAdmin(principal, tenantDomain) ||
+ getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(principal);
+ }
+
+ @Override
+ public boolean isDomainAdmin(AthensPrincipal principal, AthensDomain domain) {
+ log("isDomainAdmin(principal='%s', domain='%s')", principal, domain);
+ return getDomainOrThrow(domain, false).admins.contains(principal);
+ }
+
+ @Override
+ public List<AthensDomain> getDomainList(String prefix) {
+ log("getDomainList()");
+ return new ArrayList<>(athens.domains.keySet());
+ }
+
+ @Override
+ public List<AthensDomain> getTenantDomainsForUser(AthensPrincipal principal) {
+ log("getTenantDomainsForUser(principal='%s')", principal);
+ return athens.domains.values().stream()
+ .filter(domain -> domain.tenantAdmins.contains(principal) || domain.admins.contains(principal))
+ .map(domain -> domain.name)
+ .collect(toList());
+ }
+
+ @Override
+ public AthensPublicKey getPublicKey(AthensService service, String keyId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<AthensPublicKey> getPublicKeys(AthensService service) {
+ throw new UnsupportedOperationException();
+ }
+
+ private AthensDbMock.Domain getDomainOrThrow(AthensDomain domainName, boolean verifyVespaTenant) {
+ AthensDbMock.Domain domain = Optional.ofNullable(athens.domains.get(domainName))
+ .orElseThrow(() -> zmsException(400, "Domain '%s' not found", domainName));
+ if (verifyVespaTenant && !domain.isVespaTenant) {
+ throw zmsException(400, "Domain not a Vespa tenant: '%s'", domainName);
+ }
+ return domain;
+ }
+
+ private static ZmsException zmsException(int code, String message, Object... args) {
+ return new ZmsException(new RuntimeException(String.format(message, args)), code);
+ }
+
+ private static void log(String format, Object... args) {
+ log.log(Level.INFO, String.format(format, args));
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/package-info.java
new file mode 100644
index 00000000000..d4454503786
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author bjorncs
+ */
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.athens.mock;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/package-info.java
new file mode 100644
index 00000000000..eabe214abf2
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.athens;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/AttributeMapping.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/AttributeMapping.java
new file mode 100644
index 00000000000..87970458855
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/AttributeMapping.java
@@ -0,0 +1,35 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author mortent
+ */
+public class AttributeMapping {
+
+ private final String attribute;
+ private final List<String> chefPath;
+
+ private AttributeMapping(String attribute, List<String> chefPath) {
+ this.chefPath = chefPath;
+ this.attribute = attribute;
+ }
+
+ public static AttributeMapping simpleMapping(String attribute) {
+ return new AttributeMapping(attribute, Collections.singletonList(attribute));
+ }
+
+ public static AttributeMapping deepMapping(String attribute, List<String> chefPath) {
+ return new AttributeMapping(attribute, chefPath);
+ }
+
+ public String toString() {
+ return String.format("\"%s\": [%s]", attribute,
+ chefPath.stream().map(s -> String.format("\"%s\"", s))
+ .collect(Collectors.joining(","))
+ );
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/Chef.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/Chef.java
new file mode 100644
index 00000000000..693947b6f61
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/Chef.java
@@ -0,0 +1,42 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef;
+
+
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.ChefEnvironment;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.ChefNode;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.ChefResource;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.Client;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.CookBook;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.NodeResult;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNodeResult;
+
+import java.net.URL;
+import java.util.List;
+
+public interface Chef {
+
+ ChefResource getApi();
+
+ ChefNode getNode(String name);
+
+ Client getClient(String name);
+
+ ChefNode deleteNode(String name);
+
+ Client deleteClient(String name);
+
+ NodeResult searchNodeByFQDN(String fqdn);
+
+ NodeResult searchNodes(String query);
+
+ PartialNodeResult partialSearchNodes(String query, List<AttributeMapping> attributeMappings);
+
+ void copyChefEnvironment(String fromEnvironmentName, String toEnvironmentName);
+
+ ChefEnvironment getChefEnvironment(String environmentName);
+
+ CookBook getCookbook(String cookbookName, String cookbookVersion);
+
+ String downloadResource(URL resourceURL);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/ChefMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/ChefMock.java
new file mode 100644
index 00000000000..1b2dad34b8d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/ChefMock.java
@@ -0,0 +1,112 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef;
+
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.ChefEnvironment;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.ChefNode;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.ChefResource;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.Client;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.CookBook;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.NodeResult;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNode;
+import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNodeResult;
+
+import javax.ws.rs.NotFoundException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author mpolden
+ */
+public class ChefMock implements Chef {
+
+ private final NodeResult result;
+ private final List<String> chefEnvironments;
+
+ public ChefMock() {
+ result = new NodeResult();
+ result.rows = new ArrayList<>();
+ chefEnvironments = new ArrayList<>();
+ chefEnvironments.add("hosted-verified-prod");
+ chefEnvironments.add("hosted-infra-cd");
+ }
+
+ @Override
+ public ChefResource getApi() {
+ return null;
+ }
+
+ @Override
+ public ChefNode getNode(String name) {
+ return null;
+ }
+
+ @Override
+ public Client getClient(String name) {
+ return null;
+ }
+
+ @Override
+ public ChefNode deleteNode(String name) {
+ return null;
+ }
+
+ @Override
+ public Client deleteClient(String name) {
+ return null;
+ }
+
+ public void addSearchResult(ChefNode node) {
+ result.rows.add(node);
+ }
+
+ @Override
+ public NodeResult searchNodeByFQDN(String fqdn) {
+ return result;
+ }
+
+ @Override
+ public NodeResult searchNodes(String query) {
+ return result;
+ }
+
+ @Override
+ public PartialNodeResult partialSearchNodes(String query, List<AttributeMapping> returnAttributes) {
+ PartialNodeResult partialNodeResult = new PartialNodeResult();
+ partialNodeResult.rows = result.rows.stream()
+ .map(chefNode -> {
+ Map<String, String> data = new HashMap<>();
+ data.put("fqdn", chefNode.name);
+ return new PartialNode(data);
+ })
+ .collect(Collectors.toList());
+ return partialNodeResult;
+ }
+
+ @Override
+ public void copyChefEnvironment(String fromEnvironmentName, String toEnvironmentName) {
+ if(!chefEnvironments.contains(fromEnvironmentName)) {
+ throw new NotFoundException(String.format("Source chef environment %s does not exist", fromEnvironmentName));
+ }
+ chefEnvironments.add(toEnvironmentName);
+ }
+
+ @Override
+ public ChefEnvironment getChefEnvironment(String environmentName) {
+ return null;
+ }
+
+ @Override
+ public CookBook getCookbook(String cookbookName, String cookbookVersion) {
+ return null;
+ }
+
+ @Override
+ public String downloadResource(URL resourceURL) {
+ return "";
+ }
+}
+
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/package-info.java
new file mode 100644
index 00000000000..5d3d4b87b74
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.chef;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefEnvironment.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefEnvironment.java
new file mode 100644
index 00000000000..8576949280b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefEnvironment.java
@@ -0,0 +1,110 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ChefEnvironment {
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("default_attributes")
+ private Map<String, Object> attributes;
+ @JsonProperty("override_attributes")
+ private Map<String, Object> overrideAttributes;
+ @JsonProperty("description")
+ private String description;
+ @JsonProperty("cookbook_versions")
+ private Map<String, String> cookbookVersions;
+
+ // internal
+ @JsonProperty("json_class")
+ private final String _jsonClass = "Chef::Environment";
+ @JsonProperty("chef_type")
+ private final String _chefType = "environment";
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Builder copy() {
+ return builder()
+ .name(name)
+ .attributes(attributes)
+ .overrideAttributes(overrideAttributes)
+ .cookbookVersions(cookbookVersions)
+ .description(description);
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Map<String, String> getCookbookVersions() {
+ return cookbookVersions;
+ }
+
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ public Map<String, Object> getOverrideAttributes() {
+ return overrideAttributes;
+ }
+
+ public static class Builder {
+ private String name;
+ private Map<String, Object> attributes;
+ private String description;
+ private Map<String, Object> overrideAttributes;
+ private Map<String, String> cookbookVersions;
+
+ public Builder name(String name){
+ this.name = name;
+ return this;
+ }
+
+ public Builder attributes(Map<String, Object> defaultAttributes) {
+ this.attributes = defaultAttributes;
+ return this;
+ }
+
+ public Builder overrideAttributes(Map<String, Object> overrideAttributes) {
+ this.overrideAttributes = overrideAttributes;
+ return this;
+ }
+
+ public Builder cookbookVersions(Map<String, String> cookbookVersions) {
+ this.cookbookVersions = cookbookVersions;
+ return this;
+ }
+
+ public Builder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public ChefEnvironment build() {
+ ChefEnvironment chefEnvironment = new ChefEnvironment();
+ chefEnvironment.name = name;
+ chefEnvironment.description = description;
+ chefEnvironment.cookbookVersions = cookbookVersions;
+ chefEnvironment.attributes = attributes;
+ chefEnvironment.overrideAttributes = overrideAttributes;
+
+ return chefEnvironment;
+ }
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefNode.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefNode.java
new file mode 100644
index 00000000000..08d9a1045e8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefNode.java
@@ -0,0 +1,118 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ChefNode {
+
+ @JsonProperty("name")
+ public String name;
+
+ @JsonProperty("chef_environment")
+ public String chefEnvironment;
+
+ @JsonProperty("run_list")
+ public List<String> runList;
+
+ @JsonProperty("json_class")
+ public String jsonClass;
+
+ @JsonProperty("chef_type")
+ public String chefType;
+
+ @JsonProperty("automatic")
+ public Map<String, Object> automaticAttributes;
+
+ @JsonProperty("normal")
+ public Map<String, Object> normalAttributes;
+
+ @JsonProperty("default")
+ public Map<String, Object> defaultAttributes;
+
+ @JsonProperty("override")
+ public Map<String, Object> overrideAttributes;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(ChefNode src) {
+ return new Builder(src);
+ }
+
+ public static class Builder {
+ private String name;
+ private String chefEnvironment;
+ private List<String> runList;
+ private String jsonClass;
+ private String chefType;
+ private Map<String, Object> automaticAttributes;
+ private Map<String, Object> normalAttributes;
+ private Map<String, Object> defaultAttributes;
+ private Map<String, Object> overrideAttributes;
+
+ private Builder(){}
+
+ private Builder(ChefNode src){
+ this.name = src.name;
+ this.chefEnvironment = src.chefEnvironment;
+ this.runList = new ArrayList<>(src.runList);
+ this.jsonClass = src.jsonClass;
+ this.chefType = src.chefType;
+ this.automaticAttributes = new HashMap<>(src.automaticAttributes);
+ this.normalAttributes = new HashMap<>(src.normalAttributes);
+ this.defaultAttributes = new HashMap<>(src.defaultAttributes);
+ this.overrideAttributes = new HashMap<>(src.overrideAttributes);
+ }
+
+ public Builder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder chefEnvironment(String chefEnvironment) {
+ this.chefEnvironment = chefEnvironment;
+ return this;
+ }
+
+ public ChefNode build(){
+ ChefNode node = new ChefNode();
+ node.name = this.name;
+ node.chefEnvironment = this.chefEnvironment;
+ node.runList = this.runList;
+ node.jsonClass = this.jsonClass;
+ node.chefType = this.chefType;
+ node.automaticAttributes = this.automaticAttributes;
+ node.overrideAttributes = this.overrideAttributes;
+ node.defaultAttributes = this.defaultAttributes;
+ node.normalAttributes = this.normalAttributes;
+ return node;
+ }
+
+ }
+
+ @Override
+ public String toString() {
+ return "Node{" +
+ "name='" + name + '\'' +
+ ", chefEnvironment='" + chefEnvironment + '\'' +
+ ", runList=" + runList +
+ ", jsonClass='" + jsonClass + '\'' +
+ ", chefType='" + chefType + '\'' +
+ ", automaticAttributes=" + automaticAttributes +
+ ", normalAttributes=" + normalAttributes +
+ ", defaultAttributes=" + defaultAttributes +
+ ", overrideAttributes=" + overrideAttributes +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefResource.java
new file mode 100644
index 00000000000..98eeb0770fc
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/ChefResource.java
@@ -0,0 +1,74 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author mortent
+ * @author mpolden
+ */
+
+@Path("/")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface ChefResource {
+
+ @Path("/organizations/{organization}/environments/{environment}/nodes")
+ @Consumes("application/json")
+ @GET
+ List<String> getNodes(@PathParam("organization") String organization, @PathParam("environment") String environment);
+
+ @GET
+ @Path("/organizations/{organization}/nodes/{nodename}")
+ ChefNode getNode(@PathParam("organization") String organization, @PathParam("nodename") String nodename);
+
+ @PUT
+ @Path("/organizations/{organization}/nodes/{nodename}")
+ ChefNode updateNode(@PathParam("organization") String organization, @PathParam("nodename") String nodeName, String node);
+
+ @DELETE
+ @Path("/organizations/{organization}/nodes/{nodename}")
+ ChefNode deleteNode(@PathParam("organization") String organization, @PathParam("nodename") String nodeName);
+
+ @GET
+ @Path("/organizations/{organization}/clients/{name}")
+ Client getClient(@PathParam("organization") String organization, @PathParam("name") String name);
+
+ @DELETE
+ @Path("/organizations/{organization}/clients/{name}")
+ Client deleteClient(@PathParam("organization") String organization, @PathParam("name") String name);
+
+ @GET
+ @Path("/organizations/{organization}/environments/{environment}")
+ ChefEnvironment getEnvironment(@PathParam("organization") String organization, @PathParam("environment") String environment);
+
+ @PUT
+ @Path("/organizations/{organization}/environments/{name}")
+ String updateEnvironment(@PathParam("organization") String organization, @PathParam("name") String chefEnvironmentName, String contentAsString);
+
+ @POST
+ @Path("/organizations/{organization}/environments")
+ String createEnvironment(@PathParam("organization") String organization, String contentAsString);
+
+ @GET
+ @Path("/organizations/{organization}/search/node")
+ NodeResult searchNode(@PathParam("organization") String organization, @QueryParam("q") String query);
+
+ @POST
+ @Path("/organizations/{organization}/search/node")
+ PartialNodeResult partialSearchNode(@PathParam("organization") String organization, @QueryParam("q") String query, @QueryParam("rows") int rows, String keys);
+
+ @GET
+ @Path("/organizations/{organization}/cookbooks/{name}/{version}")
+ CookBook getCookBook(@PathParam("organization") String organization, @PathParam("name") String name, @PathParam("version") String version);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/Client.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/Client.java
new file mode 100644
index 00000000000..0ea9b0e9997
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/Client.java
@@ -0,0 +1,25 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author mpolden
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Client {
+
+ @JsonProperty("name")
+ public String name;
+ @JsonProperty("validator")
+ public boolean validator;
+
+ @Override
+ public String toString() {
+ return "Client{" +
+ "name='" + name + '\'' +
+ ", validator=" + validator +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/CookBook.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/CookBook.java
new file mode 100644
index 00000000000..ab49ac9ff60
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/CookBook.java
@@ -0,0 +1,32 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CookBook {
+ public final String name;
+ public final List<Attributes> attributes;
+
+ public CookBook(@JsonProperty("name") String name, @JsonProperty("attributes") List<Attributes> attributes) {
+ this.name = name;
+ this.attributes = attributes;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Attributes {
+ public final String name;
+ public final String url;
+
+ public Attributes(@JsonProperty("name") String name, @JsonProperty("url") String url) {
+ this.name = name;
+ this.url = url;
+ }
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/NodeResult.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/NodeResult.java
new file mode 100644
index 00000000000..e3ab431473f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/NodeResult.java
@@ -0,0 +1,20 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * @author mpolden
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class NodeResult {
+ @JsonProperty("total")
+ public int total;
+ @JsonProperty("start")
+ public int start;
+ @JsonProperty("rows")
+ public List<ChefNode> rows;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/PartialNode.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/PartialNode.java
new file mode 100644
index 00000000000..f4aa90021b1
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/PartialNode.java
@@ -0,0 +1,40 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PartialNode {
+
+ @JsonProperty("data")
+ private final Map<String, String> data;
+
+ @JsonCreator
+ public PartialNode(@JsonProperty("data") Map<String, String> data) {
+ this.data = data;
+ }
+
+ public Optional<String> getValue(String key) {
+ return Optional.ofNullable(data.get(key));
+ }
+
+ public String getFqdn() {
+ return getValue("fqdn").orElse("");
+ }
+
+ public String getName() {
+ return getValue("name").orElse("");
+ }
+
+ public Double getOhaiTime() {
+ return Double.parseDouble(getValue("ohai_time").orElse("0.0"));
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/PartialNodeResult.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/PartialNodeResult.java
new file mode 100644
index 00000000000..9925237a193
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/PartialNodeResult.java
@@ -0,0 +1,20 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PartialNodeResult {
+ @JsonProperty("total")
+ public int total;
+ @JsonProperty("start")
+ public int start;
+ @JsonProperty("rows")
+ public List<PartialNode> rows;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/package-info.java
new file mode 100644
index 00000000000..7d06571507e
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/chef/rest/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.chef.rest;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerClient.java
new file mode 100644
index 00000000000..1958c5bd0ff
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerClient.java
@@ -0,0 +1,69 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.configserver;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.yahoo.component.Version;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname;
+import com.yahoo.vespa.hosted.controller.api.rotation.Rotation;
+import com.yahoo.vespa.serviceview.bindings.ApplicationView;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author Oyvind Grønnesby
+ */
+public interface ConfigServerClient {
+
+ interface PreparedApplication {
+ void activate();
+ List<Log> messages();
+ PrepareResponse prepareResponse();
+ }
+
+ PreparedApplication prepare(DeploymentId applicationInstance, DeployOptions deployOptions, Set<String> rotationCnames, Set<Rotation> rotations, byte[] content);
+
+ List<String> getNodeQueryHost(DeploymentId applicationInstance, String type) throws NoInstanceException;
+
+ void restart(DeploymentId applicationInstance, Optional<Hostname> hostname) throws NoInstanceException;
+
+ void deactivate(DeploymentId applicationInstance) throws NoInstanceException;
+
+ JsonNode waitForConfigConverge(DeploymentId applicationInstance, long timeoutInSeconds);
+
+ JsonNode grabLog(DeploymentId applicationInstance);
+
+ ApplicationView getApplicationView(String tenantName, String applicationName, String instanceName, String environment, String region);
+
+ Map<?,?> getServiceApiResponse(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String restPath);
+
+ /** Returns the version this particular config server is running */
+ Version version(URI configserverUri);
+
+ /**
+ * Set new status on en endpoint in one zone.
+ *
+ * @param deployment The application/zone pair
+ * @param endpoint The endpoint to modify
+ * @param status The new status with metadata
+ * @throws IOException If trouble contacting the server
+ */
+ void setGlobalRotationStatus(DeploymentId deployment, String endpoint, EndpointStatus status) throws IOException;
+
+ /**
+ * Get the endpoint status for an app in one zone
+ *
+ * @param deployment The application/zone pair
+ * @param endpoint The endpoint to modify
+ * @return The endpoint status with metadata
+ * @throws IOException If trouble contacting the server
+ */
+ EndpointStatus getGlobalRotationStatus(DeploymentId deployment, String endpoint) throws IOException;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java
new file mode 100644
index 00000000000..f578322ac76
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java
@@ -0,0 +1,41 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.configserver;
+
+import java.net.URI;
+
+/**
+ * @author Tony Vaagenes
+ */
+public class ConfigServerException extends RuntimeException {
+
+ private final URI serverUri;
+ private final ErrorCode errorCode;
+
+ public ConfigServerException(URI serverUri, String message, ErrorCode errorCode, Throwable cause) {
+ super(message, cause);
+ this.serverUri = serverUri;
+ this.errorCode = errorCode;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+
+ public URI getServerUri() {
+ return serverUri;
+ }
+
+ // TODO: Copied from Vespa. Expose these in Vespa and use them here
+ public enum ErrorCode {
+ APPLICATION_LOCK_FAILURE,
+ BAD_REQUEST,
+ INTERNAL_SERVER_ERROR,
+ INVALID_APPLICATION_PACKAGE,
+ METHOD_NOT_ALLOWED,
+ NOT_FOUND,
+ OUT_OF_CAPACITY,
+ REQUEST_TIMEOUT,
+ UNKNOWN_VESPA_VERSION
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Log.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Log.java
new file mode 100644
index 00000000000..ba5d740d0e1
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Log.java
@@ -0,0 +1,15 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.configserver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * @author Tony Vaagenes
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Log {
+ public long time;
+ public String level;
+ public String message;
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NoInstanceException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NoInstanceException.java
new file mode 100644
index 00000000000..a415721407f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NoInstanceException.java
@@ -0,0 +1,11 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.configserver;
+
+/**
+ * @author Tony Vaagenes
+ */
+public class NoInstanceException extends Exception {
+ public NoInstanceException(String msg) {
+ super(msg);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/PrepareResponse.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/PrepareResponse.java
new file mode 100644
index 00000000000..6054e05149b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/PrepareResponse.java
@@ -0,0 +1,22 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.configserver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions;
+import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @author Tony Vaagenes
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PrepareResponse {
+ public TenantId tenant;
+ @JsonProperty("activate") public URI activationUri;
+ public String message;
+ public List<Log> log;
+ public ConfigChangeActions configChangeActions;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/package-info.java
new file mode 100644
index 00000000000..10eddb2628f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.configserver;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/ApplicationCost.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/ApplicationCost.java
new file mode 100644
index 00000000000..9bc9cfa8ed0
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/ApplicationCost.java
@@ -0,0 +1,105 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.cost;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Cost data model for an application instance. I.e one running vespa application in one zone.
+ *
+ * @author smorgrav
+ */
+// TODO: Make immutable
+// TODO: Make the Application own this and rename to Cost
+// TODO: Enforce constraints
+// TODO: Remove application id elements
+// TODO: Model zone as Zone
+// TODO: Cost per zone + total
+// TODO: Use doubles
+public class ApplicationCost {
+
+ /** This contains environment.region */
+ private String zone;
+
+ private String tenant;
+
+ // This must contain applicationName.instanceName. TODO: Fix
+ private String app;
+
+ private int tco;
+ private float utilization;
+ private float waste;
+ Map<String, ClusterCost> cluster;
+
+ /** Create an empty (invalid) application cost */
+ public ApplicationCost() {}
+
+ public ApplicationCost(String zone, String tenant, String app, int tco, float utilization, float waste,
+ Map<String, ClusterCost> clusterCost) {
+ this.zone = zone;
+ this.tenant = tenant;
+ this.app = app;
+ this.tco = tco;
+ this.utilization = utilization;
+ this.waste = waste;
+ cluster = new HashMap<>(clusterCost);
+ }
+
+ public String getZone() {
+ return zone;
+ }
+
+ public void setZone(String zone) {
+ this.zone = zone;
+ }
+
+ public String getApp() {
+ return app;
+ }
+
+ public void setApp(String app) {
+ this.app = app;
+ }
+
+ public Map<String, ClusterCost> getCluster() {
+ return cluster;
+ }
+
+ public void setCluster(Map<String, ClusterCost> cluster) {
+ this.cluster = cluster;
+ }
+
+ public int getTco() {
+ return tco;
+ }
+
+ public void setTco(int tco) {
+ if (tco < 0) throw new IllegalArgumentException("TCO cannot be negative");
+ this.tco = tco;
+ }
+
+ public String getTenant() {
+ return tenant;
+ }
+
+ public void setTenant(String tenant) {
+ this.tenant = tenant;
+ }
+
+ public float getUtilization() {
+ return utilization;
+ }
+
+ public void setUtilization(float utilization) {
+ if (utilization < 0) throw new IllegalArgumentException("Utilization cannot be negative");
+ this.utilization = utilization;
+ }
+
+ public float getWaste() {
+ return waste;
+ }
+
+ public void setWaste(float waste) {
+ this.waste = waste;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Backend.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Backend.java
new file mode 100644
index 00000000000..d9edf22d42c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Backend.java
@@ -0,0 +1,21 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.cost;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.vespa.hosted.controller.common.NotFoundCheckedException;
+
+import java.util.List;
+
+/**
+ * Interface for retrieving cost data directly or indirectly from yamas and
+ * the noderepository.
+ *
+ *
+ * @author smorgrav
+ */
+public interface Backend {
+ List<ApplicationCost> getApplicationCost();
+ ApplicationCost getApplicationCost(Environment env, RegionName region, ApplicationId appId) throws NotFoundCheckedException;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/ClusterCost.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/ClusterCost.java
new file mode 100644
index 00000000000..1e41325a4fd
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/ClusterCost.java
@@ -0,0 +1,182 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.cost;
+
+import java.util.List;
+
+/**
+ * Cost data model for a cluster. I.e one cluster within one vespa application in one zone.
+ *
+ * @author smorgrav
+ */
+// TODO: Use doubles
+// TODO: Make immutable
+// TODO: Enforce constraints
+// TODO: Document content
+public class ClusterCost {
+
+ private int count;
+ private String resource;
+ private float utilization;
+ private int tco;
+ private String flavor;
+ private int waste;
+ private String type;
+ private float utilMem;
+ private float utilCpu;
+ private float utilDisk;
+ private float utilDiskBusy;
+ private float usageMem;
+ private float usageCpu;
+ private float usageDisk;
+ private float usageDiskBusy;
+ private List<String> hostnames;
+
+ /** Create an empty (invalid) cluster cost */
+ public ClusterCost() {}
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public String getFlavor() {
+ return flavor;
+ }
+
+ public void setFlavor(String flavor) {
+ this.flavor = flavor;
+ }
+
+ public List<String> getHostnames() {
+ return hostnames;
+ }
+
+ public void setHostnames(List<String> hostnames) {
+ this.hostnames = hostnames;
+ }
+
+ public String getResource() {
+ return resource;
+ }
+
+ public void setResource(String resource) {
+ this.resource = resource;
+ }
+
+ public int getTco() {
+ return tco;
+ }
+
+ public void setTco(int tco) {
+ this.tco = tco;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public float getUtilization() {
+ return utilization;
+ }
+
+ public void setUtilization(float utilization) {
+ validateUtilRatio(utilization);
+ this.utilization = utilization;
+ }
+
+ public int getWaste() {
+ return waste;
+ }
+
+ public void setWaste(int waste) {
+ this.waste = waste;
+ }
+
+ public float getUsageCpu() {
+ return usageCpu;
+ }
+
+ public void setUsageCpu(float usageCpu) {
+ validateUsageRatio(usageCpu);
+ this.usageCpu = usageCpu;
+ }
+
+ public float getUsageDisk() {
+ return usageDisk;
+ }
+
+ public void setUsageDisk(float usageDisk) {
+ validateUsageRatio(usageDisk);
+ this.usageDisk = usageDisk;
+ }
+
+ public float getUsageMem() {
+ return usageMem;
+ }
+
+ public void setUsageMem(float usageMem) {
+ validateUsageRatio(usageMem);
+ this.usageMem = usageMem;
+ }
+
+ public float getUtilCpu() {
+ return utilCpu;
+ }
+
+ public void setUtilCpu(float utilCpu) {
+ validateUtilRatio(utilCpu);
+ this.utilCpu = utilCpu;
+ }
+
+ public float getUtilDisk() {
+ return utilDisk;
+ }
+
+ public void setUtilDisk(float utilDisk) {
+ validateUtilRatio(utilDisk);
+ this.utilDisk = utilDisk;
+ }
+
+ public float getUtilMem() {
+ return utilMem;
+ }
+
+ public void setUtilMem(float utilMem) {
+ validateUsageRatio(utilMem);
+ this.utilMem = utilMem;
+ }
+
+ public float getUsageDiskBusy() {
+ return usageDiskBusy;
+ }
+
+ public void setUsageDiskBusy(float usageDiskBusy) {
+ validateUsageRatio(usageDiskBusy);
+ this.usageDiskBusy = usageDiskBusy;
+ }
+
+ public float getUtilDiskBusy() {
+ return utilDiskBusy;
+ }
+
+ public void setUtilDiskBusy(float utilDiskBusy) {
+ validateUtilRatio(utilDiskBusy);
+ this.utilDiskBusy = utilDiskBusy;
+ }
+
+ private void validateUsageRatio(float ratio) {
+ if (ratio < 0) throw new IllegalArgumentException("Usage cannot be negative");
+ if (ratio > 1) throw new IllegalArgumentException("Usage exceed 1 (using more than it has available)");
+ }
+
+ private void validateUtilRatio(float ratio) {
+ if (ratio < 0) throw new IllegalArgumentException("Utilization cannot be negative");
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Cost.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Cost.java
new file mode 100644
index 00000000000..7297b60de5c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Cost.java
@@ -0,0 +1,53 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.cost;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.vespa.hosted.controller.common.NotFoundCheckedException;
+
+import java.util.List;
+
+/**
+ * Cost domain model declaration
+ *
+ * @author smorgrav
+ */
+public interface Cost {
+
+ /**
+ * Calculate a list of the applications that is wasting most
+ * in absolute terms. To improve utilization, it should make
+ * sense to focus on this list.
+ *
+ * @return An ordered set of applications with the highest potential for
+ * improved CPU utilization across all environments and regions.
+ */
+ List<ApplicationCost> getCPUAnalysis(int nofApplications);
+
+ /**
+ * Collect all information and format it as a Cvs blob for download.
+ *
+ * @return A String with comma separated values. Can be big!
+ */
+ String getCsvForLocalAnalysis();
+
+ /**
+ * Get application costs for all applications across all regions and environments
+ *
+ * @return A list of applications in given zone
+ */
+ List<ApplicationCost> getApplicationCost();
+
+ /**
+ * Get application costs for a given application instance in a given zone.
+ *
+ * @param env Environment like test, dev, perf, staging or prod
+ * @param region Region name like us-east-1
+ * @param app ApplicationId like tenant:application:instance
+ *
+ * @return A list of applications in given zone
+ */
+ ApplicationCost getApplicationCost(Environment env, RegionName region, ApplicationId app)
+ throws NotFoundCheckedException;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostJsonModelAdapter.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostJsonModelAdapter.java
new file mode 100644
index 00000000000..088b1fa12bc
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostJsonModelAdapter.java
@@ -0,0 +1,93 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.cost;
+
+import com.yahoo.slime.Cursor;
+import com.yahoo.vespa.hosted.controller.api.cost.CostJsonModel;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converting from cost data model to the JSON data model used in the cost REST API.
+ *
+ * @author smorgrav
+ */
+public class CostJsonModelAdapter {
+
+ public static CostJsonModel.Application toJsonModel(ApplicationCost appCost) {
+ CostJsonModel.Application app = new CostJsonModel.Application();
+ app.zone = appCost.getZone();
+ app.tenant = appCost.getTenant();
+ app.app = appCost.getApp();
+ app.tco = appCost.getTco();
+ app.utilization = appCost.getUtilization();
+ app.waste = appCost.getWaste();
+ app.cluster = new HashMap<>();
+ Map<String, ClusterCost> clusterMap = appCost.getCluster();
+ for (String key : clusterMap.keySet()) {
+ app.cluster.put(key, toJsonModel(clusterMap.get(key)));
+ }
+
+ return app;
+ }
+
+ public static void toSlime(ApplicationCost appCost, Cursor object) {
+ object.setString("zone", appCost.getZone());
+ object.setString("tenant", appCost.getTenant());
+ object.setString("app", appCost.getApp());
+ object.setLong("tco", appCost.getTco());
+ object.setDouble("utilization", appCost.getUtilization());
+ object.setDouble("waste", appCost.getWaste());
+ Cursor clustersObject = object.setObject("cluster");
+ for (Map.Entry<String, ClusterCost> clusterEntry : appCost.getCluster().entrySet())
+ toSlime(clusterEntry.getValue(), clustersObject.setObject(clusterEntry.getKey()));
+ }
+
+ public static CostJsonModel.Cluster toJsonModel(ClusterCost clusterCost) {
+ CostJsonModel.Cluster cluster = new CostJsonModel.Cluster();
+ cluster.count = clusterCost.getCount();
+ cluster.resource = clusterCost.getResource();
+ cluster.utilization = clusterCost.getUtilization();
+ cluster.tco = clusterCost.getTco();
+ cluster.flavor = clusterCost.getFlavor();
+ cluster.waste = clusterCost.getWaste();
+ cluster.type = clusterCost.getType();
+ cluster.util = new CostJsonModel.HardwareResources();
+ cluster.util.cpu = clusterCost.getUtilCpu();
+ cluster.util.mem = clusterCost.getUtilMem();
+ cluster.util.disk = clusterCost.getUtilDisk();
+ cluster.usage = new CostJsonModel.HardwareResources();
+ cluster.usage.cpu = clusterCost.getUsageCpu();
+ cluster.usage.mem = clusterCost.getUsageMem();
+ cluster.usage.disk = clusterCost.getUsageDisk();
+ cluster.hostnames = new ArrayList<>(clusterCost.getHostnames());
+ cluster.usage.diskBusy = clusterCost.getUsageDiskBusy();
+ cluster.util.diskBusy = clusterCost.getUtilDiskBusy();
+ return cluster;
+ }
+
+ private static void toSlime(ClusterCost clusterCost, Cursor object) {
+ object.setLong("count", clusterCost.getCount());
+ object.setString("resource", clusterCost.getResource());
+ object.setDouble("utilization", clusterCost.getUtilization());
+ object.setLong("tco", clusterCost.getTco());
+ object.setString("flavor", clusterCost.getFlavor());
+ object.setLong("waste", clusterCost.getWaste());
+ object.setString("type", clusterCost.getType());
+ Cursor utilObject = object.setObject("util");
+ utilObject.setDouble("cpu", clusterCost.getUtilCpu());
+ utilObject.setDouble("mem", clusterCost.getUtilMem());
+ utilObject.setDouble("disk", clusterCost.getUtilDisk());
+ utilObject.setDouble("diskBusy", clusterCost.getUtilDiskBusy());
+ Cursor usageObject = object.setObject("usage");
+ usageObject.setDouble("cpu", clusterCost.getUsageCpu());
+ usageObject.setDouble("mem", clusterCost.getUsageMem());
+ usageObject.setDouble("disk", clusterCost.getUsageDisk());
+ usageObject.setDouble("diskBusy", clusterCost.getUsageDiskBusy());
+ Cursor hostnamesArray = object.setArray("hostnames");
+ for (String hostname : clusterCost.getHostnames())
+ hostnamesArray.addString(hostname);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/package-info.java
new file mode 100644
index 00000000000..f08e6cc9b36
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.cost;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java
new file mode 100644
index 00000000000..f70afb3a0a0
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java
@@ -0,0 +1,31 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * An in-memory name service for testing purposes.
+ *
+ * @author mpolden
+ */
+public class MemoryNameService implements NameService {
+
+ private final Set<Record> records = new HashSet<>();
+
+ @Override
+ public RecordId createCname(String alias, String canonicalName) {
+ records.add(new Record("CNAME", alias, canonicalName));
+ return new RecordId(UUID.randomUUID().toString());
+ }
+
+ @Override
+ public Optional<Record> findRecord(Record.Type type, String name) {
+ return records.stream()
+ .filter(record -> record.type() == type && record.name().equals(name))
+ .findFirst();
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java
new file mode 100644
index 00000000000..2ccce23b60c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java
@@ -0,0 +1,24 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+import java.util.Optional;
+
+/**
+ * A managed DNS service.
+ *
+ * @author mpolden
+ */
+public interface NameService {
+
+ /**
+ * Create a new CNAME record
+ *
+ * @param alias The alias to create
+ * @param canonicalName The canonical name which the alias should point to. This must be a domain.
+ */
+ RecordId createCname(String alias, String canonicalName);
+
+ /** Find record by type and name */
+ Optional<Record> findRecord(Record.Type type, String name);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java
new file mode 100644
index 00000000000..0782a82da79
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java
@@ -0,0 +1,74 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+import java.util.Objects;
+
+/**
+ * A basic representation of a DNS resource record, containing only the record type, name and value.
+ *
+ * @author mpolden
+ */
+public class Record {
+
+ private final Type type;
+ private final String name;
+ private final String value;
+
+ public Record(Type type, String name, String value) {
+ this.type = type;
+ this.name = name;
+ this.value = value;
+ }
+
+ public Record(String type, String name, String value) {
+ this(Type.valueOf(type), name, value);
+ }
+
+ public Type type() {
+ return type;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public enum Type {
+ A,
+ AAAA,
+ CNAME,
+ MX,
+ NS,
+ PTR,
+ SOA,
+ SRV,
+ TXT
+ }
+
+ @Override
+ public String toString() {
+ return "Record{" +
+ "type=" + type +
+ ", name='" + name + '\'' +
+ ", value='" + value + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Record)) return false;
+ Record record = (Record) o;
+ return type == record.type &&
+ Objects.equals(name, record.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, name);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java
new file mode 100644
index 00000000000..9c47be12855
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java
@@ -0,0 +1,27 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+/**
+ * Unique identifier for a resource record.
+ *
+ * @author mpolden
+ */
+public class RecordId {
+
+ private final String id;
+
+ public RecordId(String id) {
+ this.id = id;
+ }
+
+ public String id() {
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return "RecordId{" +
+ "id='" + id + '\'' +
+ '}';
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/package-info.java
new file mode 100644
index 00000000000..e075b544ce8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/EntityService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/EntityService.java
new file mode 100644
index 00000000000..fc242a360f6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/EntityService.java
@@ -0,0 +1,28 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.entity;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
+import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A service which provides access to business-specific entities.
+ *
+ * @author mpolden
+ */
+public interface EntityService {
+
+ /** List all properties known by the service */
+ Map<PropertyId, Property> listProperties();
+
+ /** List all groups of which user is a member */
+ Set<UserGroup> getUserGroups(UserId user);
+
+ /** Whether user is a member of the group */
+ boolean isGroupMember(UserId user, UserGroup group);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/MemoryEntityService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/MemoryEntityService.java
new file mode 100644
index 00000000000..e5c2bbedae4
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/MemoryEntityService.java
@@ -0,0 +1,37 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.entity;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
+import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author mpolden
+ */
+public class MemoryEntityService implements EntityService {
+
+ @Override
+ public Map<PropertyId, Property> listProperties() {
+ Map<PropertyId, Property> properties = new HashMap<>();
+ properties.put(new PropertyId("1234"), new Property("foo"));
+ properties.put(new PropertyId("4321"), new Property("bar"));
+ return Collections.unmodifiableMap(properties);
+ }
+
+ @Override
+ public Set<UserGroup> getUserGroups(UserId userId) {
+ return Collections.singleton(new UserGroup("vespa"));
+ }
+
+ @Override
+ public boolean isGroupMember(UserId userId, UserGroup userGroup) {
+ return true;
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/package-info.java
new file mode 100644
index 00000000000..1e74f4ca372
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/entity/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.entity;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitHub.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitHub.java
new file mode 100644
index 00000000000..1cb3f73441b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitHub.java
@@ -0,0 +1,11 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.github;
+
+/**
+ * @author mpolden
+ */
+public interface GitHub {
+
+ GitSha getCommit(String owner, String repo, String ref);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitHubMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitHubMock.java
new file mode 100644
index 00000000000..9a398ef7cb5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitHubMock.java
@@ -0,0 +1,48 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.github;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @author jvenstad
+ */
+public class GitHubMock implements GitHub {
+
+ private final Map<String, GitSha> tags = new HashMap<>();
+ private boolean mockAny = true;
+
+ @Override
+ public GitSha getCommit(String owner, String repo, String ref) {
+ if (mockAny) {
+ String sha = UUID.randomUUID().toString();
+ return new GitSha(sha, new GitSha.GitCommit(new GitSha.GitAuthor("foo", "foo@foo.tld",
+ Date.from(Instant.EPOCH))));
+ }
+ if (tags.containsKey(ref)) {
+ return tags.get(ref);
+ }
+ throw new IllegalArgumentException("Unknown ref: " + ref);
+ }
+
+ public GitHubMock knownTag(String tag, String sha) {
+ this.tags.put(tag, new GitSha(sha, new GitSha.GitCommit(
+ new GitSha.GitAuthor("foo", "foo@foo.tld", Date.from(Instant.EPOCH)))));
+ return this;
+ }
+
+ public GitHubMock mockAny(boolean mockAny) {
+ this.mockAny = mockAny;
+ return this;
+ }
+
+ public GitHubMock reset() {
+ tags.clear();
+ mockAny = true;
+ return this;
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitSha.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitSha.java
new file mode 100644
index 00000000000..4aac98f1708
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/GitSha.java
@@ -0,0 +1,56 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.github;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Date;
+
+/**
+ * @author mpolden
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class GitSha {
+
+ @JsonProperty("sha")
+ public final String sha;
+
+ @JsonProperty("commit")
+ public final GitCommit commit;
+
+ @JsonCreator
+ public GitSha(@JsonProperty("sha") String sha, @JsonProperty("commit") GitCommit commit) {
+ this.sha = sha;
+ this.commit = commit;
+ }
+
+ public static class GitCommit {
+ @JsonProperty("author")
+ public final GitAuthor author;
+
+ @JsonCreator
+ public GitCommit(@JsonProperty("author") GitAuthor author) {
+ this.author = author;
+ }
+ }
+
+ public static class GitAuthor {
+
+ @JsonProperty("name")
+ public final String name;
+ @JsonProperty("email")
+ public final String email;
+ @JsonProperty("date")
+ public final Date date;
+
+ @JsonCreator
+ public GitAuthor(@JsonProperty("name") String name, @JsonProperty("email") String email,
+ @JsonProperty("date") Date date) {
+ this.name = name;
+ this.email = email;
+ this.date = date;
+ }
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/package-info.java
new file mode 100644
index 00000000000..ec20c05c374
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/github/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.github;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java
new file mode 100644
index 00000000000..30bf23f18a9
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java
@@ -0,0 +1,18 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.jira;
+
+import java.util.List;
+
+/**
+ * @author mortent
+ */
+public interface Jira {
+
+ List<JiraIssue> searchByProjectAndSummary(String project, String summary);
+
+ JiraIssue createIssue(JiraCreateIssue issue);
+
+ void commentIssue(JiraIssue issue, JiraComment comment);
+
+ void addAttachment(JiraIssue issue, String filename, String fileContent);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraComment.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraComment.java
new file mode 100644
index 00000000000..2d67b720fe0
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraComment.java
@@ -0,0 +1,20 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.jira;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JiraComment {
+
+ public final String body;
+
+ @JsonCreator
+ public JiraComment(@JsonProperty("body") String body) {
+ this.body = body;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraCreateIssue.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraCreateIssue.java
new file mode 100644
index 00000000000..e5e35af4475
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraCreateIssue.java
@@ -0,0 +1,86 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.jira;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JiraCreateIssue {
+
+ @JsonProperty("fields")
+ public final JiraFields fields;
+
+ public JiraCreateIssue(JiraFields fields) {
+ this.fields = fields;
+ }
+
+ public static class JiraFields {
+ @JsonProperty("summary")
+ public final String summary;
+
+ @JsonProperty("description")
+ public final String description;
+
+ @JsonProperty("project")
+ public final JiraProject project;
+
+ @JsonProperty("issuetype")
+ public final JiraIssueType issueType;
+
+ @JsonProperty("components")
+ public final List<JiraComponent> components;
+
+ public JiraFields(
+ JiraProject project,
+ String summary,
+ String description,
+ JiraIssueType issueType,
+ List<JiraComponent> components) {
+ this.project = project;
+ this.summary = summary;
+ this.description = description;
+ this.issueType = issueType;
+ this.components = components;
+ }
+
+
+ public static class JiraProject {
+ public static final JiraProject VESPA = new JiraProject("VESPA");
+
+ @JsonProperty("key")
+ public final String key;
+
+ public JiraProject(String key) {
+ this.key = key;
+ }
+ }
+
+ public static class JiraIssueType {
+ public static final JiraIssueType DEFECT = new JiraIssueType("Defect");
+
+ @JsonProperty("name")
+ public final String name;
+
+ public JiraIssueType(String name) {
+ this.name = name;
+ }
+ }
+
+ public static class JiraComponent {
+ public static final JiraComponent COREDUMPS = new JiraComponent("CoreDumps");
+
+ @JsonProperty("name")
+ public final String name;
+
+
+ public JiraComponent(String name) {
+ this.name = name;
+ }
+ }
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraIssue.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraIssue.java
new file mode 100644
index 00000000000..d88e75d3a58
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraIssue.java
@@ -0,0 +1,45 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.jira;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.Instant;
+import java.util.Date;
+
+/**
+ * @author mpolden
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JiraIssue {
+ public final String key;
+ private final Fields fields;
+
+ @JsonCreator
+ public JiraIssue(@JsonProperty("key") String key, @JsonProperty("fields") Fields fields) {
+ this.key = key;
+ this.fields = fields;
+ }
+
+ public Instant lastUpdated() {
+ return fields.lastUpdated;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Fields {
+ final Instant lastUpdated;
+
+ @JsonCreator
+ public Fields(
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'hh:mm:ss.SSSZ", timezone = "UTC")
+ @JsonProperty("updated") Date updated) {
+ lastUpdated = updated.toInstant();
+ }
+
+ public Fields(Instant instant) {
+ this.lastUpdated = instant;
+ }
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraIssues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraIssues.java
new file mode 100644
index 00000000000..809ac8360bb
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraIssues.java
@@ -0,0 +1,22 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.jira;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JiraIssues {
+ public final List<JiraIssue> issues;
+
+ @JsonCreator
+ public JiraIssues(@JsonProperty("issues") List<JiraIssue> issues) {
+ this.issues = issues == null ? Collections.emptyList() : issues;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraMock.java
new file mode 100644
index 00000000000..da653ddd8a8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/JiraMock.java
@@ -0,0 +1,50 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.jira;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author jvenstad
+ */
+// TODO: Make mock.
+public class JiraMock implements Jira {
+
+ public final Map<String, JiraCreateIssue.JiraFields> issues = new HashMap<>();
+
+ private Long counter = 0L;
+
+ @Override
+ public List<JiraIssue> searchByProjectAndSummary(String project, String summary) {
+ return issues.entrySet().stream()
+ .filter(entry -> entry.getValue().project.key.equals(project))
+ .filter(entry -> entry.getValue().summary.contains(summary))
+ .map(entry -> new JiraIssue(entry.getKey(), new JiraIssue.Fields(Instant.now())))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public JiraIssue createIssue(JiraCreateIssue issueData) {
+ JiraIssue issue = uniqueKey();
+ issues.put(issue.key, issueData.fields);
+ return issue;
+ }
+
+ @Override
+ public void commentIssue(JiraIssue issue, JiraComment comment) {
+ // Add mock when relevant.
+ }
+
+ @Override
+ public void addAttachment(JiraIssue issue, String filename, String fileContent) {
+ // Add mock when relevant.
+ }
+
+ private JiraIssue uniqueKey() {
+ return new JiraIssue((++counter).toString(), new JiraIssue.Fields(Instant.now()));
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/package-info.java
new file mode 100644
index 00000000000..efe356c69e9
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.jira;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/package-info.java
new file mode 100644
index 00000000000..265d57cadd8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/GlobalRoutingService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/GlobalRoutingService.java
new file mode 100644
index 00000000000..d49d6a9e4c2
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/GlobalRoutingService.java
@@ -0,0 +1,16 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.routing;
+
+import java.util.Map;
+
+/**
+ * A global routing service.
+ *
+ * @author mpolden
+ */
+public interface GlobalRoutingService {
+
+ /** Returns the health status for each endpoint behind the given rotation name */
+ Map<String, RotationStatus> getHealthStatus(String rotationName);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/MemoryGlobalRoutingService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/MemoryGlobalRoutingService.java
new file mode 100644
index 00000000000..9f1ac1b1f0b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/MemoryGlobalRoutingService.java
@@ -0,0 +1,20 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.routing;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author bratseth
+ */
+public class MemoryGlobalRoutingService implements GlobalRoutingService {
+
+ @Override
+ public Map<String, RotationStatus> getHealthStatus(String rotationName) {
+ HashMap<String, RotationStatus> map = new HashMap<>();
+ map.put("prod.us-west-1", RotationStatus.IN);
+ return Collections.unmodifiableMap(map);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RotationStatus.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RotationStatus.java
new file mode 100644
index 00000000000..8c59bb44fa1
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RotationStatus.java
@@ -0,0 +1,11 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.routing;
+
+/**
+ * Represents the health status of a global rotation.
+ *
+ * @author andreer
+ */
+public enum RotationStatus {
+ IN, OUT, UNKNOWN
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RoutingEndpoint.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RoutingEndpoint.java
new file mode 100644
index 00000000000..a4bf733bd2c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RoutingEndpoint.java
@@ -0,0 +1,30 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.routing;
+
+/**
+ * @author smorgrav
+ */
+public class RoutingEndpoint {
+
+ private final boolean isGlobal;
+ private final String endpoint;
+
+ public RoutingEndpoint(String endpoint, boolean isGlobal) {
+ this.endpoint = endpoint;
+ this.isGlobal = isGlobal;
+ }
+
+ /**
+ * @return True if the endpoint is global
+ */
+ public boolean isGlobal() {
+ return isGlobal;
+ }
+
+ /*
+ * @return The URI for the endpoint
+ */
+ public String getEndpoint() {
+ return endpoint;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RoutingGenerator.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RoutingGenerator.java
new file mode 100644
index 00000000000..276e19da8f6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/RoutingGenerator.java
@@ -0,0 +1,19 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.routing;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+
+import java.util.List;
+
+/**
+ * @author bratseth
+ * @author smorgrav
+ */
+public interface RoutingGenerator {
+
+ /**
+ * @param deploymentId Specifying an application in a zone
+ * @return List of endpoints for that deploymentId
+ */
+ List<RoutingEndpoint> endpoints(DeploymentId deploymentId);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/package-info.java
new file mode 100644
index 00000000000..25374003ec1
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/routing/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.routing;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/security/KeyService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/security/KeyService.java
new file mode 100644
index 00000000000..98c664eb07d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/security/KeyService.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.security;
+
+/**
+ * A service for retrieving secrets, such as API keys, private keys and passwords.
+ *
+ * @author mpolden
+ */
+public interface KeyService {
+
+ String getSecret(String key);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/security/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/security/package-info.java
new file mode 100644
index 00000000000..296eebf8ea5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/security/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.security;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/ContactsMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/ContactsMock.java
new file mode 100644
index 00000000000..9114cf20ccc
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/ContactsMock.java
@@ -0,0 +1,31 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.stubs;
+
+import com.yahoo.vespa.hosted.controller.api.integration.Contacts;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author mpolden
+ */
+public class ContactsMock implements Contacts {
+
+ private final Map<Long, List<UserContact>> userContacts = new HashMap<>();
+
+ public void addContact(long propertyId, List<UserContact> contacts) {
+ userContacts.put(propertyId, contacts);
+ }
+
+ public List<UserContact> userContactsFor(long propertyId) {
+ return userContacts.get(propertyId);
+ }
+
+ @Override
+ public URI contactsUri(long propertyId) {
+ return URI.create("http://contacts.test?propertyId=" + propertyId);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingIssues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingIssues.java
new file mode 100644
index 00000000000..160f80076bd
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingIssues.java
@@ -0,0 +1,87 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.stubs;
+
+import com.yahoo.vespa.hosted.controller.api.integration.Issues;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
+
+/**
+ * An memory backed implementation of the Issues API which logs changes and does nothing else.
+ *
+ * @author bratseth
+ */
+public class LoggingIssues implements Issues {
+
+ private static final Logger log = Logger.getLogger(LoggingIssues.class.getName());
+
+ /** Used to fabricate unique issue ids */
+ private AtomicLong issueIdSequence = new AtomicLong(0);
+
+ // These two maps should have precisely the same keys
+ private final Map<String, Issue> issues = new HashMap<>();
+ private final Map<String, IssueInfo> issueInfos = new HashMap<>();
+
+ @Override
+ public IssueInfo fetch(String issueId) {
+ return issueInfos.getOrDefault(issueId,
+ new IssueInfo(issueId, null, Instant.ofEpochMilli(0), null, IssueInfo.Status.noCategory));
+ }
+
+ @Override
+ public List<IssueInfo> fetchSimilarTo(Issue issue) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String file(Issue issue) {
+ log.info("Want to file " + issue);
+ String issueId = "issue-" + issueIdSequence.getAndIncrement();
+ issues.put(issueId, issue);
+ issueInfos.put(issueId, new IssueInfo(issueId, null, Instant.now(), null, IssueInfo.Status.noCategory));
+ return issueId;
+ }
+
+ @Override
+ public void update(String issueId, String description) {
+ log.info("Want to update " + issueId);
+ issues.put(issueId, requireIssue(issueId).withDescription(description));
+ }
+
+ @Override
+ public void reassign(String issueId, String assignee) {
+ log.info("Want to reassign issue " + issueId + " to " + assignee);
+ issueInfos.put(issueId, requireInfo(issueId).withAssignee(Optional.of(assignee)));
+ }
+
+ @Override
+ public void addWatcher(String issueId, String watcher) {
+ log.info("Want to add watcher " + watcher + " to issue " + issueId);
+ }
+
+ @Override
+ public void comment(String issueId, String comment) {
+ log.info("Want to comment on issue " + issueId);
+ }
+
+ private Issue requireIssue(String issueId) {
+ Issue issue = issues.get(issueId);
+ if (issue == null)
+ throw new IllegalArgumentException("No issue with id '" + issueId + "'");
+ return issue;
+ }
+
+ private IssueInfo requireInfo(String issueId) {
+ IssueInfo info = issueInfos.get(issueId);
+ if (info == null)
+ throw new IllegalArgumentException("No issue info with id '" + issueId + "'");
+ return info;
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/PropertiesMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/PropertiesMock.java
new file mode 100644
index 00000000000..53a31933e03
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/PropertiesMock.java
@@ -0,0 +1,26 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.stubs;
+
+import com.yahoo.vespa.hosted.controller.api.integration.Issues;
+import com.yahoo.vespa.hosted.controller.api.integration.Properties;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author mpolden
+ */
+public class PropertiesMock implements Properties {
+
+ private final Map<Long, Issues.Classification> projects = new HashMap<>();
+
+ public void addClassification(long propertyId, String classification) {
+ projects.put(propertyId, new Issues.Classification(classification));
+ }
+
+ public Optional<Issues.Classification> classificationFor(long propertyId) {
+ return Optional.ofNullable(projects.get(propertyId));
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/package-info.java
new file mode 100644
index 00000000000..2aab38dc66d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/package-info.java
@@ -0,0 +1,11 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * No-dependency implementations of integration interfaces for setups where we want to avoid contacting
+ * certain thirds-party systems.
+ *
+ * @author bratseth
+ */
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.stubs;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
new file mode 100644
index 00000000000..e7bdf786c8c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
@@ -0,0 +1,31 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.zone;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Zone;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Provides information about zones in a hosted Vespa system.
+ *
+ * @author mpolden
+ */
+public interface ZoneRegistry {
+
+ SystemName system();
+ List<Zone> zones();
+ Optional<Zone> getZone(Environment environment, RegionName region);
+ List<URI> getConfigServerUris(Environment environment, RegionName region);
+ Optional<URI> getLogServerUri(Environment environment, RegionName region);
+ Optional<Duration> getDeploymentTimeToLive(Environment environment, RegionName region);
+ URI getMonitoringSystemUri(Environment environment, RegionName name, ApplicationId application);
+ URI getDashboardUri();
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/package-info.java
new file mode 100644
index 00000000000..148564a373f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.integration.zone;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/nonpublic/HeaderFields.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/nonpublic/HeaderFields.java
new file mode 100644
index 00000000000..78a6750aedb
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/nonpublic/HeaderFields.java
@@ -0,0 +1,14 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.nonpublic;
+
+/**
+ * Non public header fields that are not part of the public api.
+ *
+ * Placed here since this is the only module we own that both the
+ * command-line client and controller-server depend on.
+ *
+ * @author Tony Vaagenes
+ */
+public class HeaderFields {
+ public static final String USER_ID_HEADER_FIELD = "vespa.hosted.trusted.username";
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java
new file mode 100644
index 00000000000..ed3e69bcac7
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java
@@ -0,0 +1,39 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.rotation;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId;
+
+import java.util.Objects;
+
+/**
+ * Represents a global routing rotation.
+ *
+ * @author Oyvind Gronnesby
+ */
+public class Rotation {
+
+ /** The ID of the allocated rotation. This value is generated by global routing system. */
+ public final RotationId rotationId;
+
+ /** The global name which the allocated rotation points to */
+ public final String rotationName;
+
+ public Rotation(RotationId rotationId, String rotationName) {
+ this.rotationId = Objects.requireNonNull(rotationId);
+ this.rotationName = Objects.requireNonNull(rotationName);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Rotation)) return false;
+ final Rotation rotation = (Rotation) o;
+ return rotationId.equals(rotation.rotationId) && rotationName.equals(rotation.rotationName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(rotationId, rotationName);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/package-info.java
new file mode 100644
index 00000000000..1626158a489
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.rotation;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/statuspage/StatusPageResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/statuspage/StatusPageResource.java
new file mode 100644
index 00000000000..65c5e0f9365
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/statuspage/StatusPageResource.java
@@ -0,0 +1,24 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.statuspage;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author andreer
+ */
+@Path("/v1/")
+@Produces(MediaType.APPLICATION_JSON)
+public interface StatusPageResource {
+
+ @GET
+ @Path("{page}")
+ @Produces(MediaType.APPLICATION_JSON)
+ JsonNode statusPage(@PathParam("page") String page, @QueryParam("since") String since);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/statuspage/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/statuspage/package-info.java
new file mode 100644
index 00000000000..3f9117bf931
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/statuspage/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.statuspage;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/ZoneApi.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/ZoneApi.java
new file mode 100644
index 00000000000..7bb4bfc6467
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/ZoneApi.java
@@ -0,0 +1,35 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.zone.v1;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * Used by build system and command-line tool.
+ *
+ * @author smorgrav
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@Path(ZoneApi.API_VERSION)
+public interface ZoneApi {
+
+ String API_VERSION = "v1";
+
+ @GET
+ @Path("")
+ List<ZoneReference.Environment> listEnvironments();
+
+ @GET
+ @Path("/environment/{environment}")
+ List<ZoneReference.Region> listRegions(@PathParam("environment") String env);
+
+ @GET
+ @Path("/environment/{environment}/default")
+ ZoneReference.Region defaultRegion(@PathParam("environment") String env);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/ZoneReference.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/ZoneReference.java
new file mode 100644
index 00000000000..82d03d72acd
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/ZoneReference.java
@@ -0,0 +1,64 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.zone.v1;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.net.URI;
+
+/**
+ * @author smorgrav
+ */
+public class ZoneReference {
+
+ public static class Environment {
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("url")
+ private URI url;
+
+ public String getName() {
+ return name;
+ }
+
+ public Environment setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public URI getUrl() {
+ return url;
+ }
+
+ public Environment setUrl(URI url) {
+ this.url = url;
+ return this;
+ }
+ }
+
+ public static class Region {
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("url")
+ private URI url;
+
+ public String getName() {
+ return name;
+ }
+
+ public Region setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public URI getUrl() {
+ return url;
+ }
+
+ public Region setUrl(URI url) {
+ this.url = url;
+ return this;
+ }
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/package-info.java
new file mode 100644
index 00000000000..e3275ff35fa
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v1/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.zone.v1;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java
new file mode 100644
index 00000000000..97d99e262b5
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java
@@ -0,0 +1,110 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.zone.v2;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.EnvironmentId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * Aka the controller proxy service.
+ *
+ * Proxies calls to correct config server with the additional feature of
+ * retry and fail detection (ping).
+ */
+@Path(ZoneApiV2.API_VERSION)
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface ZoneApiV2 {
+
+ String API_VERSION = "v2";
+
+ @GET
+ @Path("/")
+ ZoneReferences listZones();
+
+ @GET
+ @Path("/{environment}/{region}/{proxy_request: .+}")
+ Response proxyGet(
+ @PathParam("environment") String env,
+ @PathParam("region") String region,
+ @PathParam("proxy_request") String proxyRequest,
+ @Context HttpServletRequest request);
+
+ @POST
+ @Path("/{environment}/{region}/{proxy_request: .+}")
+ Response proxyPost(
+ @PathParam("environment") String env,
+ @PathParam("region") String region,
+ @PathParam("proxy_request") String proxyRequest,
+ @Context HttpServletRequest request);
+
+ @PUT
+ @Path("/{environment}/{region}/{proxy_request: .+}")
+ Response proxyPut(
+ @PathParam("environment") String env,
+ @PathParam("region") String region,
+ @PathParam("proxy_request") String proxyRequest,
+ @Context HttpServletRequest request);
+
+ @DELETE
+ @Path("/{environment}/{region}/{proxy_request: .+}")
+ Response proxyDelete(
+ @PathParam("environment") String env,
+ @PathParam("region") String region,
+ @PathParam("proxy_request") String proxyRequest,
+ @Context HttpServletRequest request);
+
+ // Explicit mappings of some proxy requests (to enable creation of proxy clients with javax.ws.rs)
+
+ @GET
+ @Path("/{environmentId}/{regionId}/application/v2/tenant/{tenantId}/application/{applicationId}/environment/{environmentId}/region/{regionId}/instance/{instanceId}/serviceconverge")
+ Response waitForConfigConvergeV2(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("instanceId") InstanceId instanceId,
+ @QueryParam("timeout") long timeoutInSeconds);
+ @GET
+ @Path("/{environmentId}/{regionId}/application/v2/tenant/{tenantId}/application/{applicationId}/environment/{environmentId}/region/{regionId}/instance/{instanceId}/serviceconverge/{host}")
+ Response waitForConfigConvergeV2(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("instanceId") InstanceId instanceId,
+ @PathParam("host") String host,
+ @QueryParam("timeout") long timeoutInSeconds);
+
+ @GET
+ @Path("/{environmentId}/{regionId}/config/v2/tenant/{tenantId}/application/{applicationId}/prelude.fastsearch.documentdb-info/{clusterid}/search/cluster.{clusterid}")
+ JsonNode getConfigWithDocumentTypes(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @PathParam("clusterid") String clusterid,
+ @QueryParam("timeout") long timeoutInSeconds);
+
+ @GET
+ @Path("/{environmentId}/{regionId}/config/v2/tenant/{tenantId}/application/{applicationId}/cloud.config.cluster-list")
+ JsonNode getVespaConfigClusterList(@PathParam("tenantId") TenantId tenantId,
+ @PathParam("applicationId") ApplicationId applicationId,
+ @PathParam("environmentId") EnvironmentId environmentId,
+ @PathParam("regionId") RegionId regionId,
+ @QueryParam("timeout") long timeoutInSeconds);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneReference.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneReference.java
new file mode 100644
index 00000000000..95dc1c2ee7c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneReference.java
@@ -0,0 +1,27 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.zone.v2;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.hosted.controller.api.configserver.Environment;
+import com.yahoo.vespa.hosted.controller.api.configserver.Region;
+
+/**
+ * @author mpolden
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ZoneReference {
+
+ @JsonProperty("environment")
+ public final Environment environment;
+ @JsonProperty("region")
+ public final Region region;
+
+ @JsonCreator
+ public ZoneReference(@JsonProperty("environment") Environment environment, @JsonProperty("region") Region region) {
+ this.environment = environment;
+ this.region = region;
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneReferences.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneReferences.java
new file mode 100644
index 00000000000..3a219afa0a6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneReferences.java
@@ -0,0 +1,31 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.zone.v2;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Wire format for listing the controller URIs for all the available zones
+ *
+ * @author smorgrav
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ZoneReferences {
+
+ @JsonProperty("uris")
+ public final List<String> uris;
+
+ @JsonProperty("zones")
+ public final List<ZoneReference> zones;
+
+ @JsonCreator
+ public ZoneReferences(@JsonProperty("uris") List<String> uris, @JsonProperty("zones") List<ZoneReference> zones) {
+ this.uris = Collections.unmodifiableList(uris);
+ this.zones = Collections.unmodifiableList(zones);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/package-info.java
new file mode 100644
index 00000000000..5d4b1310981
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.zone.v2;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/ContextAttributes.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/ContextAttributes.java
new file mode 100644
index 00000000000..1cdff0f920b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/ContextAttributes.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.common;
+
+/**
+ * Constants for request context attributes used in our APIs.
+ *
+ * @author mpolden
+ */
+public interface ContextAttributes {
+
+ String SECURITY_CONTEXT_ATTRIBUTE = "vespa.hosted.security_context";
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/NotFoundCheckedException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/NotFoundCheckedException.java
new file mode 100644
index 00000000000..a55a7e2bdfc
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/NotFoundCheckedException.java
@@ -0,0 +1,23 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.common;
+
+/**
+ * We have tons of places where we throw exceptions when
+ * some hosted resource is not found. This is usually
+ * done with IllegalArgumentExceptions, java.ws.rs exceptions or
+ * the servermodel runtime exceptions in the controller-server module.
+ *
+ * This is a checked alternative to do the same thing.
+ *
+ * @author smorgrav
+ */
+public class NotFoundCheckedException extends Exception {
+
+ public NotFoundCheckedException() {
+ super();
+ }
+
+ public NotFoundCheckedException(String msg) {
+ super(msg);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/package-info.java
new file mode 100644
index 00000000000..95decd86e8b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/common/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.common;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeployOptionsTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeployOptionsTest.java
new file mode 100644
index 00000000000..4a02fe23dec
--- /dev/null
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeployOptionsTest.java
@@ -0,0 +1,31 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.yahoo.component.Version;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author mortent
+ */
+public class DeployOptionsTest {
+
+ @Test
+ public void it_serializes_version() throws IOException {
+ DeployOptions options = new DeployOptions(Optional.empty(), Optional.of(new Version("6.98.227")), false, false);
+ final ObjectMapper objectMapper = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .registerModule(new Jdk8Module());
+
+ String string = objectMapper.writeValueAsString(options);
+ assertEquals("{\"screwdriverBuildJob\":null,\"vespaVersion\":\"6.98.227\",\"ignoreValidationErrors\":false,\"deployCurrentVersion\":false}", string);
+ }
+}
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java
new file mode 100644
index 00000000000..56825cf7c61
--- /dev/null
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java
@@ -0,0 +1,153 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.identifiers;
+
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class IdentifierTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void existing_tenant_id_not_empty() {
+ new TenantId("");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void existing_tenant_id_must_check_pattern() {
+ new TenantId("`");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void default_not_allowed_for_tenants() {
+ new TenantId("default");
+ }
+
+ @Test
+ public void existing_tenant_id_must_accept_valid_id() {
+ new TenantId("msbe");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void existing_tenant_id_cannot_be_uppercase() {
+ new TenantId("MixedCaseTenant");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void existing_tenant_id_cannot_contain_dots() {
+ new TenantId("tenant.with.dots");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_tenant_id_cannot_contain_underscore() {
+ TenantId.validate("underscore_tenant");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_tenant_id_cannot_contain_dot() {
+ TenantId.validate("tenant.with.dots");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_tenant_id_cannot_contain_uppercase() {
+ TenantId.validate("UppercaseTenant");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_tenant_id_cannot_start_with_dash() {
+ TenantId.validate("-tenant");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_tenant_id_cannot_end_with_dash() {
+ TenantId.validate("tenant-");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void existing_application_id_cannot_be_uppercase() {
+ new ApplicationId("MixedCaseApplication");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void existing_application_id_cannot_contain_dots() {
+ new ApplicationId("application.with.dots");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_application_id_cannot_contain_underscore() {
+ ApplicationId.validate("underscore_application");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_application_id_cannot_contain_dot() {
+ ApplicationId.validate("application.with.dots");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_application_id_cannot_contain_uppercase() {
+ ApplicationId.validate("UppercaseApplication");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_application_id_cannot_start_with_dash() {
+ ApplicationId.validate("-application");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void new_application_id_cannot_end_with_dash() {
+ ApplicationId.validate("application-");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void instance_id_cannot_be_uppercase() {
+ new InstanceId("MixedCaseInstance");
+ }
+
+ @Test
+ public void rotation_id_may_contain_dot() {
+ new RotationId("rotation.id.with.dot");
+ }
+
+ @Test
+ public void user_tenant_id_does_not_contain_underscore() {
+ assertEquals("by-under-score-user", new UserId("under_score_user").toTenantId().id());
+ }
+
+ @Test
+ public void athens_parent_domain_is_without_name_suffix() {
+ assertEquals(new AthensDomain("yby.john"), new AthensDomain("yby.john.myapp").getParent());
+ }
+
+ @Test
+ public void athens_domain_name_is_last_suffix() {
+ assertEquals("myapp", new AthensDomain("yby.john.myapp").getName());
+ }
+
+ @Test
+ public void domain_without_dot_is_toplevel() {
+ assertTrue(new AthensDomain("toplevel").isTopLevelDomain());
+ assertFalse(new AthensDomain("not.toplevel").isTopLevelDomain());
+ }
+
+ @Test
+ public void dns_names_has_no_underscore() {
+ assertEquals("a-b-c", new ApplicationId("a_b_c").toDns());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void identifiers_cannot_be_named_api() {
+ new ApplicationId("api");
+ }
+
+
+ @Test
+ public void application_instance_id_dotted_string_is_subindentifers_concatinated_with_dots() {
+ DeploymentId id = new DeploymentId(com.yahoo.config.provision.ApplicationId.from("tenant", "application", "instance"),
+ new Zone(Environment.prod, RegionName.from("region")));
+ assertEquals("tenant.application.prod.region.instance", id.dottedString());
+ }
+}