diff options
author | Martin Polden <martin.polden@gmail.com> | 2017-08-22 13:33:30 +0200 |
---|---|---|
committer | Martin Polden <martin.polden@gmail.com> | 2017-08-24 12:45:09 +0200 |
commit | 56b9aae782b782066d7e9603fce9095aa5cafd30 (patch) | |
tree | 8f4d1a590e3745737bf60b0b47ead54d186e5821 /controller-api/src | |
parent | 08c2a0490261a4666b65882616756ecfbe1c8c9b (diff) |
Import controller
Diffstat (limited to 'controller-api/src')
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()); + } +} |