diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Option.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Option.java new file mode 100644 index 000000000000..853b678a2ebe --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Option.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.gcloud.spi.ResourceManagerRpc; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Base class for Resource Manager operation options + */ +class Option implements Serializable { + + private static final long serialVersionUID = 2655177550880762967L; + + private final ResourceManagerRpc.Option rpcOption; + private final Object value; + + Option(ResourceManagerRpc.Option rpcOption, Object value) { + this.rpcOption = checkNotNull(rpcOption); + this.value = value; + } + + ResourceManagerRpc.Option rpcOption() { + return rpcOption; + } + + Object value() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Option)) { + return false; + } + Option other = (Option) obj; + return Objects.equals(rpcOption, other.rpcOption) + && Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(rpcOption, value); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", rpcOption.value()) + .add("value", value) + .toString(); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java new file mode 100644 index 000000000000..a08d7e3714be --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A Google Cloud Resource Manager project object. + * + * A Project is a high-level Google Cloud Platform entity. It is a container for ACLs, APIs, + * AppEngine Apps, VMs, and other Google Cloud Platform resources. This class' member variables are + * immutable. Methods that change or update the underlying Project information return a new Project + * instance. + */ +public class Project { + + private final ResourceManager resourceManager; + private final ProjectInfo info; + + /** + * Constructs a Project object that contains the ProjectInfo given. + */ + public Project(ResourceManager resourceManager, ProjectInfo projectInfo) { + this.resourceManager = checkNotNull(resourceManager); + this.info = checkNotNull(projectInfo); + } + + /** + * Constructs a Project object that contains project information loaded from the server. + * + * @return Project object containing the project's metadata + * @throws ResourceManagerException upon failure + */ + public static Project load(ResourceManager resourceManager, String projectId) { + ProjectInfo projectInfo = resourceManager.get(projectId); + return new Project(resourceManager, projectInfo); + } + + /** + * Returns the {@link ProjectInfo} object associated with this Project. + */ + public ProjectInfo info() { + return info; + } + + /** + * Returns the {@link ResourceManager} service object associated with this Project. + */ + public ResourceManager resourceManager() { + return resourceManager; + } + + /** + * Returns a Project object with updated project information. + * + * @return Project object containing the project's updated metadata + * @throws ResourceManagerException upon failure + */ + public Project reload() { + return Project.load(resourceManager, info.projectId()); + } + + /** + * Marks the project identified by the specified project ID for deletion. + * + * This method will only affect the project if the following criteria are met: + * + * This method changes the project's lifecycle state from {@link ProjectInfo.State#ACTIVE} to + * {@link ProjectInfo.State#DELETE_REQUESTED}. The deletion starts at an unspecified time, at + * which point the lifecycle state changes to {@link ProjectInfo.State#DELETE_IN_PROGRESS}. Until + * the deletion completes, you can check the lifecycle state checked by retrieving the project + * with {@link ResourceManager#get}, and the project remains visible to + * {@link ResourceManager#list}. However, you cannot update the project. After the deletion + * completes, the project is not retrievable by the {@link ResourceManager#get} and + * {@link ResourceManager#list} methods. The caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager delete + * @throws ResourceManagerException upon failure + */ + public void delete() { + resourceManager.delete(info.projectId()); + } + + /** + * Restores the project identified by the specified project ID. + * + * You can only use this method for a project that has a lifecycle state of + * {@link ProjectInfo.State#DELETE_REQUESTED}. After deletion starts, as indicated by a lifecycle + * state of {@link ProjectInfo.State#DELETE_IN_PROGRESS}, the project cannot be restored. The + * caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager undelete + * @throws ResourceManagerException upon failure (including when the project can't be restored) + */ + public void undelete() { + resourceManager.undelete(info.projectId()); + } + + /** + * Replaces the attributes of the project. + * + * The caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager update + * @return the ProjectInfo representing the new project metadata + * @throws ResourceManagerException upon failure + */ + public Project replace(ProjectInfo projectInfo) { + return new Project(resourceManager, resourceManager.replace(checkNotNull(projectInfo))); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java new file mode 100644 index 000000000000..5d490e662df0 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java @@ -0,0 +1,355 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.gcloud.resourcemanager; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.util.Data; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import org.joda.time.DateTime; +import org.joda.time.format.ISODateTimeFormat; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * A Google Cloud Resource Manager project metadata object. + * + * A Project is a high-level Google Cloud Platform entity. It is a container for ACLs, APIs, + * AppEngine Apps, VMs, and other Google Cloud Platform resources. + */ +public class ProjectInfo implements Serializable { + + private static final long serialVersionUID = 9148970963697734236L; + private final String name; + private final String projectId; + private final Map labels; + private final Long projectNumber; + private final State state; + private final Long createTimeMillis; + private final ResourceId parent; + + /** + * The project lifecycle states. + */ + public enum State { + /** + * Only used/useful for distinguishing unset values + */ + LIFECYCLE_STATE_UNSPECIFIED, + + /** + * The normal and active state + */ + ACTIVE, + + /** + * The project has been marked for deletion by the user or by the system (Google Cloud + * Platform). This can generally be reversed by calling {@link ResourceManager#undelete}. + */ + DELETE_REQUESTED, + + /** + * the process of deleting the project has begun. Reversing the deletion is no longer possible. + */ + DELETE_IN_PROGRESS + } + + static class ResourceId implements Serializable { + + private static final long serialVersionUID = -325199985993344726L; + + private final String id; + private final String type; + + ResourceId(String id, String type) { + this.id = checkNotNull(id); + this.type = checkNotNull(type); + } + + String id() { + return id; + } + + String type() { + return type; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ResourceId && Objects.equals(toPb(), ((ResourceId) obj).toPb()); + } + + @Override + public int hashCode() { + return Objects.hash(id, type); + } + + com.google.api.services.cloudresourcemanager.model.ResourceId toPb() { + com.google.api.services.cloudresourcemanager.model.ResourceId resourceIdPb = + new com.google.api.services.cloudresourcemanager.model.ResourceId(); + resourceIdPb.setId(id); + resourceIdPb.setType(type.toString().toLowerCase()); + return resourceIdPb; + } + + static ResourceId fromPb( + com.google.api.services.cloudresourcemanager.model.ResourceId resourceIdPb) { + return new ResourceId(resourceIdPb.getId(), resourceIdPb.getType()); + } + } + + public static class Builder { + + private String name; + private String projectId; + private Map labels = new HashMap<>(); + private Long projectNumber; + private State state; + private Long createTimeMillis; + private ResourceId parent; + + private Builder() { + } + + Builder(ProjectInfo info) { + this.name = info.name; + this.projectId = info.projectId; + this.labels.putAll(info.labels); + this.projectNumber = info.projectNumber; + this.state = info.state; + this.createTimeMillis = info.createTimeMillis; + this.parent = info.parent; + } + + /** + * Set the user-assigned name of the project. + * + * This field is optional and can remain unset. Allowed characters are: lowercase and uppercase + * letters, numbers, hyphen, single-quote, double-quote, space, and exclamation point. This + * field can be changed after project creation. + */ + public Builder name(String name) { + this.name = firstNonNull(name, Data.nullOf(String.class)); + return this; + } + + /** + * Set the unique, user-assigned ID of the project. + * + * The ID must be 6 to 30 lowercase letters, digits, or hyphens. It must start with a letter. + * Trailing hyphens are prohibited. This field cannot be changed after the server creates the + * project. + */ + public Builder projectId(String projectId) { + this.projectId = checkNotNull(projectId); + return this; + } + + /** + * Add a label associated with this project. + * + * See {@link #labels} for label restrictions. + */ + public Builder addLabel(String key, String value) { + this.labels.put(key, value); + return this; + } + + /** + * Remove a label associated with this project. + */ + public Builder removeLabel(String key) { + this.labels.remove(key); + return this; + } + + /** + * Clear the labels associated with this project. + */ + public Builder clearLabels() { + this.labels.clear(); + return this; + } + + /** + * Set the labels associated with this project. + * + * Label keys must be between 1 and 63 characters long and must conform to the following regular + * expression: [a-z]([-a-z0-9]*[a-z0-9])?. Label values must be between 0 and 63 characters long + * and must conform to the regular expression ([a-z]([-a-z0-9]*[a-z0-9])?)?. No more than 256 + * labels can be associated with a given resource. This field can be changed after project + * creation. + */ + public Builder labels(Map labels) { + this.labels = Maps.newHashMap(checkNotNull(labels)); + return this; + } + + Builder projectNumber(Long projectNumber) { + this.projectNumber = projectNumber; + return this; + } + + Builder state(State state) { + this.state = state; + return this; + } + + Builder createTimeMillis(Long createTimeMillis) { + this.createTimeMillis = createTimeMillis; + return this; + } + + Builder parent(ResourceId parent) { + this.parent = parent; + return this; + } + + public ProjectInfo build() { + return new ProjectInfo(this); + } + } + + ProjectInfo(Builder builder) { + this.name = builder.name; + this.projectId = builder.projectId; + this.labels = ImmutableMap.copyOf(builder.labels); + this.projectNumber = builder.projectNumber; + this.state = builder.state; + this.createTimeMillis = builder.createTimeMillis; + this.parent = builder.parent; + } + + /** + * Get the unique, user-assigned ID of the project. + * + * This field cannot be changed after the server creates the project. + */ + public String projectId() { + return projectId; + } + + /** + * Get the user-assigned name of the project. + * + * This field is optional, can remain unset, and can be changed after project creation. + */ + public String name() { + return Data.isNull(name) ? null : name; + } + + /** + * Get number uniquely identifying the project. + * + * This field is set by the server and is read-only. + */ + public Long projectNumber() { + return projectNumber; + } + + /** + * Get the immutable map of labels associated with this project. + */ + public Map labels() { + return labels; + } + + /** + * Get the project's lifecycle state. + * + * This is a read-only field. To change the lifecycle state of your project, use the + * {@code delete} or {@code undelete} method. + */ + public State state() { + return state; + } + + ResourceId parent() { + return parent; + } + + /** + * Get the project's creation time (in milliseconds). + * + * This field is set by the server and is read-only. + */ + public Long createTimeMillis() { + return createTimeMillis; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ProjectInfo && Objects.equals(toPb(), ((ProjectInfo) obj).toPb()); + } + + @Override + public int hashCode() { + return Objects.hash(name, projectId, labels, projectNumber, state, createTimeMillis, parent); + } + + public static Builder builder(String id) { + return new Builder().projectId(id); + } + + public Builder toBuilder() { + return new Builder(this); + } + + com.google.api.services.cloudresourcemanager.model.Project toPb() { + com.google.api.services.cloudresourcemanager.model.Project projectPb = + new com.google.api.services.cloudresourcemanager.model.Project(); + projectPb.setName(name); + projectPb.setProjectId(projectId); + projectPb.setLabels(labels); + projectPb.setProjectNumber(projectNumber); + if (state != null) { + projectPb.setLifecycleState(state.toString()); + } + if (createTimeMillis != null) { + projectPb.setCreateTime(ISODateTimeFormat.dateTime().print(createTimeMillis)); + } + if (parent != null) { + projectPb.setParent(parent.toPb()); + } + return projectPb; + } + + static ProjectInfo fromPb(com.google.api.services.cloudresourcemanager.model.Project projectPb) { + ProjectInfo.Builder builder = + ProjectInfo.builder(projectPb.getProjectId()).projectNumber(projectPb.getProjectNumber()); + if (projectPb.getName() != null) { + builder.name(projectPb.getName()); + } + if (projectPb.getLabels() != null) { + builder.labels(projectPb.getLabels()); + } + if (projectPb.getLifecycleState() != null) { + builder.state(State.valueOf(projectPb.getLifecycleState())); + } + if (projectPb.getCreateTime() != null) { + builder.createTimeMillis(DateTime.parse(projectPb.getCreateTime()).getMillis()); + } + if (projectPb.getParent() != null) { + builder.parent(ResourceId.fromPb(projectPb.getParent())); + } + return builder.build(); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java index 8a9966faa653..1562fe51dad1 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java @@ -16,7 +16,13 @@ package com.google.gcloud.resourcemanager; +import com.google.common.base.Joiner; +import com.google.common.collect.Sets; +import com.google.gcloud.Page; import com.google.gcloud.Service; +import com.google.gcloud.spi.ResourceManagerRpc; + +import java.util.HashSet; /** * An interface for Google Cloud Resource Manager. @@ -27,5 +33,236 @@ public interface ResourceManager extends Service { public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; - // TODO(ajaykannan): Fix me! Add in missing methods. + /** + * The fields of a project. + * + * These values can be used to specify the fields to include in a partial response when calling + * {@link ResourceManager#get} or {@link ResourceManager#list}. Project ID is always returned, + * even if not specified. + */ + enum ProjectField { + PROJECT_ID("projectId"), + NAME("name"), + LABELS("labels"), + PROJECT_NUMBER("projectNumber"), + STATE("lifecycleState"), + CREATE_TIME("createTime"); + + private final String selector; + + ProjectField(String selector) { + this.selector = selector; + } + + public String selector() { + return selector; + } + + static String selector(ProjectField... fields) { + HashSet fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1); + fieldStrings.add(PROJECT_ID.selector()); + for (ProjectField field : fields) { + fieldStrings.add(field.selector()); + } + return Joiner.on(',').join(fieldStrings); + } + } + + /** + * Class for specifying project get options. + */ + public class ProjectGetOption extends Option { + + private static final long serialVersionUID = 270185129961146874L; + + private ProjectGetOption(ResourceManagerRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify the project's fields to be returned by the RPC call. + * + * If this option is not provided all project fields are returned. + * {@code ProjectGetOption.fields} can be used to specify only the fields of interest. Project + * ID is always returned, even if not specified. {@link ProjectField} provides a list of fields + * that can be used. + */ + public static ProjectGetOption fields(ProjectField... fields) { + return new ProjectGetOption(ResourceManagerRpc.Option.FIELDS, ProjectField.selector(fields)); + } + } + + /** + * Class for specifying project list options. + */ + public class ProjectListOption extends Option { + + private static final long serialVersionUID = 7888768979702012328L; + + private ProjectListOption(ResourceManagerRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify a filter. + * + * Filter rules are case insensitive. The fields eligible for filtering are: + *
    + *
  • name + *
  • project ID + *
  • labels.key, where key is the name of a label + *
+ * + * You can specify multiple filters by adding a space between each filter. Multiple filters + * are composed using "and". + * + * Some examples of filters: + *
    + *
  • name:* The project has a name. + *
  • name:Howl The project's name is Howl or howl. + *
  • name:HOWL Equivalent to above. + *
  • NAME:howl Equivalent to above. + *
  • labels.color:* The project has the label color. + *
  • labels.color:red The project's label color has the value red. + *
  • labels.color:red label.size:big The project's label color has the value red and its + * label size has the value big. + *
+ */ + public static ProjectListOption filter(String filter) { + return new ProjectListOption(ResourceManagerRpc.Option.FILTER, filter); + } + + /** + * Returns an option to specify a page token. + * + * The page token (returned from a previous call to list) indicates from where listing should + * continue. + */ + public static ProjectListOption pageToken(String pageToken) { + return new ProjectListOption(ResourceManagerRpc.Option.PAGE_TOKEN, pageToken); + } + + /** + * The maximum number of projects to return per RPC. + * + * The server can return fewer projects than requested. When there are more results than the + * page size, the server will return a page token that can be used to fetch other results. + * Note: pagination is not yet supported; the server currently ignores this field and returns + * all results. + */ + public static ProjectListOption pageSize(int pageSize) { + return new ProjectListOption(ResourceManagerRpc.Option.PAGE_SIZE, pageSize); + } + + /** + * Returns an option to specify the project's fields to be returned by the RPC call. + * + * If this option is not provided all project fields are returned. + * {@code ProjectListOption.fields} can be used to specify only the fields of interest. Project + * ID is always returned, even if not specified. {@link ProjectField} provides a list of fields + * that can be used. + */ + public static ProjectListOption fields(ProjectField... fields) { + StringBuilder builder = new StringBuilder(); + builder.append("projects(").append(ProjectField.selector(fields)).append(")"); + return new ProjectListOption(ResourceManagerRpc.Option.FIELDS, builder.toString()); + } + } + + /** + * Create a new project. + * + * Initially, the project resource is owned by its creator exclusively. The creator can later + * grant permission to others to read or update the project. Several APIs are activated + * automatically for the project, including Google Cloud Storage. + * + * @see Cloud + * Resource Manager create + * @return ProjectInfo object representing the new project's metadata. The returned object will + * include the following read-only fields supplied by the server: project number, lifecycle + * state, and creation time. + * @throws ResourceManagerException upon failure + */ + ProjectInfo create(ProjectInfo project); + + /** + * Marks the project identified by the specified project ID for deletion. + * + * This method will only affect the project if the following criteria are met: + *
    + *
  • The project does not have a billing account associated with it. + *
  • The project has a lifecycle state of {@link ProjectInfo.State#ACTIVE}. + *
+ * This method changes the project's lifecycle state from {@link ProjectInfo.State#ACTIVE} to + * {@link ProjectInfo.State#DELETE_REQUESTED}. The deletion starts at an unspecified time, at + * which point the lifecycle state changes to {@link ProjectInfo.State#DELETE_IN_PROGRESS}. Until + * the deletion completes, you can check the lifecycle state checked by retrieving the project + * with {@link ResourceManager#get}, and the project remains visible to + * {@link ResourceManager#list}. However, you cannot update the project. After the deletion + * completes, the project is not retrievable by the {@link ResourceManager#get} and + * {@link ResourceManager#list} methods. The caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager delete + * @throws ResourceManagerException upon failure + */ + void delete(String projectId); + + /** + * Retrieves the project identified by the specified project ID. + * + * The caller must have read permissions for this project. + * + * @see Cloud + * Resource Manager get + * @throws ResourceManagerException upon failure + */ + ProjectInfo get(String projectId, ProjectGetOption... options); + + /** + * Lists the projects visible to the current user. + * + * This method returns projects in an unspecified order. New projects do not necessarily appear at + * the end of the list. Use {@link ProjectListOption} to filter this list, set page size, and set + * page tokens. Note that pagination is currently not implemented by the Cloud Resource Manager + * API. + * + * @see Cloud + * Resource Manager list + * @return {@code Page}, a page of projects. + * @throws ResourceManagerException upon failure + */ + Page list(ProjectListOption... options); + + /** + * Replaces the attributes of the project. + * + * The caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager update + * @return the ProjectInfo representing the new project metadata + * @throws ResourceManagerException upon failure + */ + ProjectInfo replace(ProjectInfo newProject); + + /** + * Restores the project identified by the specified project ID. + * + * You can only use this method for a project that has a lifecycle state of + * {@link ProjectInfo.State#DELETE_REQUESTED}. After deletion starts, as indicated by a lifecycle + * state of {@link ProjectInfo.State#DELETE_IN_PROGRESS}, the project cannot be restored. The + * caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager undelete + * @throws ResourceManagerException + */ + void undelete(String projectId); } diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java index e136db8fd339..22b5e8bfed7c 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java @@ -16,6 +16,8 @@ package com.google.gcloud.resourcemanager; +import com.google.gcloud.BaseServiceException; +import com.google.gcloud.RetryHelper; import com.google.gcloud.RetryHelper.RetryHelperException; import com.google.gcloud.RetryHelper.RetryInterruptedException; @@ -25,29 +27,13 @@ * @see Google Cloud * Resource Manager error codes */ -public class ResourceManagerException extends RuntimeException { +public class ResourceManagerException extends BaseServiceException { private static final long serialVersionUID = 6841689911565501705L; private static final int UNKNOWN_CODE = -1; - private final int code; - private final boolean retryable; - public ResourceManagerException(int code, String message, boolean retryable) { - super(message); - this.code = code; - this.retryable = retryable; - } - - /** - * Returns the code associated with this exception. - */ - public int code() { - return code; - } - - public boolean retryable() { - return retryable; + super(code, message, retryable); } /** @@ -59,7 +45,12 @@ public boolean retryable() { * @throws RetryInterruptedException when {@code ex} is a {@code RetryInterruptedException} */ static ResourceManagerException translateAndThrow(RetryHelperException ex) { + if (ex.getCause() instanceof ResourceManagerException) { + throw (ResourceManagerException) ex.getCause(); + } + if (ex instanceof RetryHelper.RetryInterruptedException) { + RetryHelper.RetryInterruptedException.propagate(); + } throw new ResourceManagerException(UNKNOWN_CODE, ex.getMessage(), false); - // TODO(ajaykannan): Fix me! } } diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java index e43609be95c1..990163c459da 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java @@ -40,6 +40,13 @@ public ResourceManager create(ResourceManagerOptions options) { } } + /** + * Returns a default {@code ResourceManagerOptions} instance. + */ + public static ResourceManagerOptions defaultInstance() { + return builder().build(); + } + public static class DefaultResourceManagerRpcFactory implements ResourceManagerRpcFactory { private static final ResourceManagerRpcFactory INSTANCE = new DefaultResourceManagerRpcFactory(); @@ -70,6 +77,11 @@ private ResourceManagerOptions(Builder builder) { super(ResourceManagerFactory.class, ResourceManagerRpcFactory.class, builder); } + @Override + protected boolean projectIdRequired() { + return false; + } + @Override protected ResourceManagerFactory defaultServiceFactory() { return DefaultResourceManagerFactory.INSTANCE; diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java new file mode 100644 index 000000000000..d73c6ae4015c --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java @@ -0,0 +1,88 @@ +package com.google.gcloud.spi; + +import com.google.api.client.googleapis.json.GoogleJsonError; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.jackson.JacksonFactory; +import com.google.api.services.cloudresourcemanager.Cloudresourcemanager; +import com.google.api.services.cloudresourcemanager.model.Project; +import com.google.common.collect.ImmutableSet; +import com.google.gcloud.resourcemanager.ResourceManagerException; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +public class DefaultResourceManagerRpc implements ResourceManagerRpc { + + // see https://cloud.google.com/resource-manager/v1/errors/core_errors + private static final Set RETRYABLE_CODES = ImmutableSet.of(503, 500, 429, 417); + + private final ResourceManagerOptions options; + private final Cloudresourcemanager resourceManager; + + public DefaultResourceManagerRpc(ResourceManagerOptions options) { + HttpTransport transport = options.httpTransportFactory().create(); + HttpRequestInitializer initializer = options.httpRequestInitializer(); + this.options = options; + resourceManager = + new Cloudresourcemanager.Builder(transport, new JacksonFactory(), initializer) + .setRootUrl(options.host()) + .setApplicationName(options.applicationName()) + .build(); + } + + private static ResourceManagerException translate(IOException exception) { + ResourceManagerException translated; + if (exception instanceof GoogleJsonResponseException) { + translated = translate(((GoogleJsonResponseException) exception).getDetails()); + } else { + translated = new ResourceManagerException(0, exception.getMessage(), false); + } + translated.initCause(exception); + return translated; + } + + private static ResourceManagerException translate(GoogleJsonError exception) { + boolean retryable = RETRYABLE_CODES.contains(exception.getCode()); + return new ResourceManagerException(exception.getCode(), exception.getMessage(), retryable); + } + + @Override + public Project create(Project project) throws ResourceManagerException { + // TODO(ajaykannan): fix me! + return null; + } + + @Override + public void delete(String projectId) throws ResourceManagerException { + // TODO(ajaykannan): fix me! + } + + @Override + public Project get(String projectId, Map options) throws ResourceManagerException { + // TODO(ajaykannan): fix me! + return null; + } + + @Override + public Tuple> list(Map options) + throws ResourceManagerException { + // TODO(ajaykannan): fix me! + return null; + } + + @Override + public void undelete(String projectId) throws ResourceManagerException { + // TODO(ajaykannan): fix me! + } + + @Override + public Project replace(Project project) throws ResourceManagerException { + // TODO(ajaykannan): fix me! + return null; + } +} + diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java index 1223627bbfcb..3302b3571efa 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java @@ -16,26 +16,41 @@ package com.google.gcloud.spi; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.api.services.cloudresourcemanager.model.Policy; import com.google.api.services.cloudresourcemanager.model.Project; -import com.google.common.collect.ImmutableList; import com.google.gcloud.resourcemanager.ResourceManagerException; -import java.util.Collections; -import java.util.List; +import java.util.Map; public interface ResourceManagerRpc { - public enum Permission { - CREATE, - DELETE, - GET, - LIST, - REPLACE, - GET_IAM_POLICY, - REPLACE_IAM_POLICY + enum Option { + FILTER("filter"), + FIELDS("fields"), + PAGE_SIZE("pageSize"), + PAGE_TOKEN("pageToken"); + + private final String value; + + Option(String value) { + this.value = value; + } + + public String value() { + return value; + } + + @SuppressWarnings("unchecked") + T get(Map options) { + return (T) options.get(this); + } + + String getString(Map options) { + return get(options); + } + + Long getInt(Map options) { + return get(options); + } } class Tuple { @@ -60,60 +75,17 @@ public Y y() { } } - public class ListOptions { - private List filters; - private String pageToken; - private int pageSize; - - private static final ListOptions DEFAULT_INSTANCE = - new ListOptions(Collections.emptyList(), null, -1); - - ListOptions(List filters, String pageToken, int pageSize) { - this.filters = checkNotNull(ImmutableList.copyOf(filters)); - this.pageToken = pageToken; - this.pageSize = pageSize; - } - - public static ListOptions getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - public static ListOptions createListOption( - List filters, String pageToken, int pageSize) { - return new ListOptions(filters, pageToken, pageSize); - } - - public String pageToken() { - return pageToken; - } - - public List filters() { - return filters; - } - - public int pageSize() { - return pageSize; - } - } - Project create(Project project) throws ResourceManagerException; void delete(String projectId) throws ResourceManagerException; - Project get(String projectId) throws ResourceManagerException; + Project get(String projectId, Map options) throws ResourceManagerException; - Tuple> list(ListOptions listOptions) throws ResourceManagerException; + Tuple> list(Map options) throws ResourceManagerException; void undelete(String projectId) throws ResourceManagerException; Project replace(Project project) throws ResourceManagerException; - Policy getIamPolicy(String projectId) throws ResourceManagerException; - - boolean replaceIamPolicy(String projectId, Policy policy) throws ResourceManagerException; - - List hasPermissions(String projectId, List permissions) - throws ResourceManagerException; - // TODO(ajaykannan): implement "Organization" functionality when available (issue #319) } diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java new file mode 100644 index 000000000000..c066ccf39ca4 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableMap; + +import org.junit.Test; + +import java.util.Map; + +public class ProjectInfoTest { + + private static final String PROJECT_ID = "project-id"; + private static final String NAME = "myProj"; + private static final Map LABELS = ImmutableMap.of("k1", "v1", "k2", "v2"); + private static final Long PROJECT_NUMBER = 123L; + private static final Long CREATE_TIME_MILLIS = 123456789L; + private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; + private static final ProjectInfo.ResourceId PARENT = + new ProjectInfo.ResourceId("id", "organization"); + private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder(PROJECT_ID) + .name(NAME) + .labels(LABELS) + .projectNumber(PROJECT_NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .parent(PARENT) + .build(); + private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder(PROJECT_ID).build(); + + @Test + public void testBuilder() { + assertEquals(PROJECT_ID, FULL_PROJECT_INFO.projectId()); + assertEquals(NAME, FULL_PROJECT_INFO.name()); + assertEquals(LABELS, FULL_PROJECT_INFO.labels()); + assertEquals(PROJECT_NUMBER, FULL_PROJECT_INFO.projectNumber()); + assertEquals(CREATE_TIME_MILLIS, FULL_PROJECT_INFO.createTimeMillis()); + assertEquals(STATE, FULL_PROJECT_INFO.state()); + + assertEquals(PROJECT_ID, PARTIAL_PROJECT_INFO.projectId()); + assertEquals(null, PARTIAL_PROJECT_INFO.name()); + assertTrue(PARTIAL_PROJECT_INFO.labels().isEmpty()); + assertEquals(null, PARTIAL_PROJECT_INFO.projectNumber()); + assertEquals(null, PARTIAL_PROJECT_INFO.createTimeMillis()); + assertEquals(null, PARTIAL_PROJECT_INFO.state()); + } + + @Test + public void testToBuilder() { + compareProjects(FULL_PROJECT_INFO, FULL_PROJECT_INFO.toBuilder().build()); + compareProjects(PARTIAL_PROJECT_INFO, PARTIAL_PROJECT_INFO.toBuilder().build()); + } + + @Test + public void testToAndFromPb() { + compareProjects(FULL_PROJECT_INFO, ProjectInfo.fromPb(FULL_PROJECT_INFO.toPb())); + compareProjects(PARTIAL_PROJECT_INFO, ProjectInfo.fromPb(PARTIAL_PROJECT_INFO.toPb())); + } + + @Test + public void testEquals() { + compareProjects( + FULL_PROJECT_INFO, + ProjectInfo.builder(PROJECT_ID) + .name(NAME) + .labels(LABELS) + .projectNumber(PROJECT_NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .parent(PARENT) + .build()); + compareProjects(PARTIAL_PROJECT_INFO, ProjectInfo.builder(PROJECT_ID).build()); + assertNotEquals(FULL_PROJECT_INFO, PARTIAL_PROJECT_INFO); + } + + private void compareProjects(ProjectInfo expected, ProjectInfo value) { + assertEquals(expected, value); + assertEquals(expected.projectId(), value.projectId()); + assertEquals(expected.name(), value.name()); + assertEquals(expected.labels(), value.labels()); + assertEquals(expected.projectNumber(), value.projectNumber()); + assertEquals(expected.createTimeMillis(), value.createTimeMillis()); + assertEquals(expected.state(), value.state()); + assertEquals(expected.parent(), value.parent()); + } +} + diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java new file mode 100644 index 000000000000..65bb37dbccf9 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import com.google.common.collect.ImmutableMap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Map; + +public class ProjectTest { + private static final String PROJECT_ID = "project-id"; + private static final String NAME = "myProj"; + private static final Map LABELS = ImmutableMap.of("k1", "v1", "k2", "v2"); + private static final Long PROJECT_NUMBER = 123L; + private static final Long CREATE_TIME_MILLIS = 123456789L; + private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; + private static final ProjectInfo PROJECT_INFO = ProjectInfo.builder(PROJECT_ID) + .name(NAME) + .labels(LABELS) + .projectNumber(PROJECT_NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .build(); + + private ResourceManager resourceManager; + private Project project; + + @Before + public void setUp() throws Exception { + resourceManager = createStrictMock(ResourceManager.class); + project = new Project(resourceManager, PROJECT_INFO); + } + + @After + public void tearDown() throws Exception { + verify(resourceManager); + } + + @Test + public void testLoad() { + expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(PROJECT_INFO); + replay(resourceManager); + Project loadedProject = Project.load(resourceManager, PROJECT_INFO.projectId()); + assertEquals(PROJECT_INFO, loadedProject.info()); + } + + @Test + public void testReload() { + ProjectInfo newInfo = PROJECT_INFO.toBuilder().addLabel("k3", "v3").build(); + expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(newInfo); + replay(resourceManager); + Project newProject = project.reload(); + assertSame(resourceManager, newProject.resourceManager()); + assertEquals(newInfo, newProject.info()); + } + + @Test + public void testInfo() { + replay(resourceManager); + assertEquals(PROJECT_INFO, project.info()); + } + + @Test + public void testResourceManager() { + replay(resourceManager); + assertEquals(resourceManager, project.resourceManager()); + } + + @Test + public void testDelete() { + resourceManager.delete(PROJECT_INFO.projectId()); + replay(resourceManager); + project.delete(); + } + + @Test + public void testUndelete() { + resourceManager.undelete(PROJECT_INFO.projectId()); + replay(resourceManager); + project.undelete(); + } + + @Test + public void testReplace() { + ProjectInfo newInfo = PROJECT_INFO.toBuilder().addLabel("k3", "v3").build(); + expect(resourceManager.replace(newInfo)).andReturn(newInfo); + replay(resourceManager); + Project newProject = project.replace(newInfo); + assertSame(resourceManager, newProject.resourceManager()); + assertEquals(newInfo, newProject.info()); + } +} diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java new file mode 100644 index 000000000000..8b61519fc106 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; + +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.AuthCredentials; +import com.google.gcloud.PageImpl; +import com.google.gcloud.RetryParams; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collections; + +public class SerializationTest { + + private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder("id1").build(); + private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder("id") + .name("name") + .labels(ImmutableMap.of("key", "value")) + .projectNumber(123L) + .state(ProjectInfo.State.ACTIVE) + .createTimeMillis(1234L) + .build(); + private static final PageImpl PAGE_RESULT = + new PageImpl<>(null, "c", Collections.singletonList(PARTIAL_PROJECT_INFO)); + private static final ResourceManager.ProjectGetOption PROJECT_GET_OPTION = + ResourceManager.ProjectGetOption.fields(ResourceManager.ProjectField.NAME); + private static final ResourceManager.ProjectListOption PROJECT_LIST_OPTION = + ResourceManager.ProjectListOption.filter("name:*"); + + @Test + public void testServiceOptions() throws Exception { + ResourceManagerOptions options = ResourceManagerOptions.builder().build(); + ResourceManagerOptions serializedCopy = serializeAndDeserialize(options); + assertEquals(options, serializedCopy); + options = options.toBuilder() + .projectId("some-unnecessary-project-ID") + .retryParams(RetryParams.defaultInstance()) + .authCredentials(AuthCredentials.noCredentials()) + .build(); + serializedCopy = serializeAndDeserialize(options); + assertEquals(options, serializedCopy); + } + + @Test + public void testModelAndRequests() throws Exception { + Serializable[] objects = {PARTIAL_PROJECT_INFO, FULL_PROJECT_INFO, PAGE_RESULT, + PROJECT_GET_OPTION, PROJECT_LIST_OPTION}; + for (Serializable obj : objects) { + Object copy = serializeAndDeserialize(obj); + assertEquals(obj, obj); + assertEquals(obj, copy); + assertNotSame(obj, copy); + assertEquals(copy, copy); + } + } + + @SuppressWarnings("unchecked") + private T serializeAndDeserialize(T obj) throws IOException, ClassNotFoundException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream output = new ObjectOutputStream(bytes)) { + output.writeObject(obj); + } + try (ObjectInputStream input = + new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) { + return (T) input.readObject(); + } + } +}