From 4899c2711fc266d41c9f4f7535b6919cb5afc5a4 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 2 Nov 2015 08:02:55 -0800 Subject: [PATCH 01/35] Create packages for resource manager and outline spi layer. --- gcloud-java-resourcemanager/README.md | 79 ++++++++++++++ gcloud-java-resourcemanager/pom.xml | 44 ++++++++ .../resourcemanager/ResourceManager.java | 31 ++++++ .../ResourceManagerException.java | 65 +++++++++++ .../ResourceManagerFactory.java | 25 +++++ .../ResourceManagerOptions.java | 101 ++++++++++++++++++ .../gcloud/resourcemanager/package-info.java | 23 ++++ .../google/gcloud/spi/ResourceManagerRpc.java | 69 ++++++++++++ .../gcloud/spi/ResourceManagerRpcFactory.java | 27 +++++ 9 files changed, 464 insertions(+) create mode 100644 gcloud-java-resourcemanager/README.md create mode 100644 gcloud-java-resourcemanager/pom.xml create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerFactory.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpcFactory.java diff --git a/gcloud-java-resourcemanager/README.md b/gcloud-java-resourcemanager/README.md new file mode 100644 index 000000000000..11c505c14920 --- /dev/null +++ b/gcloud-java-resourcemanager/README.md @@ -0,0 +1,79 @@ +Google Cloud Java Client for Resource Manager +============================================= + +Java idiomatic client for [Google Cloud Resource Manager] (https://cloud.google.com/resource-manager/). + +[![Build Status](https://travis-ci.org/GoogleCloudPlatform/gcloud-java.svg?branch=master)](https://travis-ci.org/GoogleCloudPlatform/gcloud-java) +[![Coverage Status](https://coveralls.io/repos/GoogleCloudPlatform/gcloud-java/badge.svg?branch=master)](https://coveralls.io/r/GoogleCloudPlatform/gcloud-java?branch=master) + + + +- [Homepage] (https://googlecloudplatform.github.io/gcloud-java/) + + + +> Note: This client is a work-in-progress, and may occasionally +> make backwards-incompatible changes. + +Quickstart +---------- +This library is currently under development and will be available soon! + + + + +Authentication +-------------- + +See the [Authentication](https://github.com/GoogleCloudPlatform/gcloud-java#authentication) section in the base directory's README. + +About Google Cloud Resource Manager +----------------------------------- + +Google [Cloud Resource Manager][cloud-resourcemanager] provides a programmatic way to manage your Google Cloud Platform projects. Google Cloud Resource Manager is currently in beta and may occasionally make backwards incompatible changes. + +Be sure to activate the Google Cloud Resource Manager API on the Developer's Console to use Resource Manager from your project. + +See the ``gcloud-java`` API [Resource Manager documentation][resourcemanager-api] to learn how to interact +with the Cloud Resource Manager using this client Library. + + + +Java Versions +------------- + +Java 7 or above is required for using this client. + + + +Versioning +---------- + +This library follows [Semantic Versioning] (http://semver.org/). + +It is currently in major version zero (``0.y.z``), which means that anything +may change at any time and the public API should not be considered +stable. + +Contributing +------------ + +Contributions to this library are always welcome and highly encouraged. + +See [CONTRIBUTING] for more information on how to get started. + +License +------- + +Apache 2.0 - See [LICENSE] for more information. + + +[CONTRIBUTING]:https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/CONTRIBUTING.md +[LICENSE]: https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/LICENSE +[TESTING]: https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/TESTING.md#testing-code-that-uses-resource-manager +[cloud-platform]: https://cloud.google.com/ +[cloud-resourcemanager]: https://cloud.google.com/resource-manager/docs +[resourcemanager-api]: http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/resourcemanager/package-summary.html + diff --git a/gcloud-java-resourcemanager/pom.xml b/gcloud-java-resourcemanager/pom.xml new file mode 100644 index 000000000000..4a4899231177 --- /dev/null +++ b/gcloud-java-resourcemanager/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + com.google.gcloud + gcloud-java-resourcemanager + jar + GCloud Java resource manager + + Java idiomatic client for Google Cloud Resource Manager. + + + com.google.gcloud + gcloud-java-pom + 0.0.11-SNAPSHOT + + + gcloud-java-resourcemanager + + + + ${project.groupId} + gcloud-java-core + ${project.version} + + + com.google.apis + google-api-services-cloudresourcemanager + v1beta1-rev6-1.19.0 + compile + + + junit + junit + 4.12 + test + + + org.easymock + easymock + 3.3 + test + + + 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 new file mode 100644 index 000000000000..8a9966faa653 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java @@ -0,0 +1,31 @@ +/* + * 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 com.google.gcloud.Service; + +/** + * An interface for Google Cloud Resource Manager. + * + * @see Google Cloud Resource Manager + */ +public interface ResourceManager extends Service { + + public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; + + // TODO(ajaykannan): Fix me! Add in missing methods. +} 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 new file mode 100644 index 000000000000..e136db8fd339 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java @@ -0,0 +1,65 @@ +/* + * 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 com.google.gcloud.RetryHelper.RetryHelperException; +import com.google.gcloud.RetryHelper.RetryInterruptedException; + +/** + * Resource Manager service exception. + * + * @see Google Cloud + * Resource Manager error codes + */ +public class ResourceManagerException extends RuntimeException { + + 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; + } + + /** + * Translate RetryHelperException to the ResourceManagerException that caused the error. This + * method will always throw an exception. + * + * @throws ResourceManagerException when {@code ex} was caused by a {@code + * ResourceManagerException} + * @throws RetryInterruptedException when {@code ex} is a {@code RetryInterruptedException} + */ + static ResourceManagerException translateAndThrow(RetryHelperException ex) { + 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/ResourceManagerFactory.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerFactory.java new file mode 100644 index 000000000000..256fc321e4e1 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerFactory.java @@ -0,0 +1,25 @@ +/* + * 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 com.google.gcloud.ServiceFactory; + +/** + * An interface for ResourceManager factories. + */ +public interface ResourceManagerFactory + extends ServiceFactory {} 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 new file mode 100644 index 000000000000..e43609be95c1 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java @@ -0,0 +1,101 @@ +/* + * 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 com.google.common.collect.ImmutableSet; +import com.google.gcloud.ServiceOptions; +import com.google.gcloud.spi.ResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpcFactory; + +import java.util.Set; + +public class ResourceManagerOptions + extends ServiceOptions { + + private static final long serialVersionUID = 538303101192527452L; + private static final String GCRM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; + private static final Set SCOPES = ImmutableSet.of(GCRM_SCOPE); + + public static class DefaultResourceManagerFactory implements ResourceManagerFactory { + private static final ResourceManagerFactory INSTANCE = new DefaultResourceManagerFactory(); + + @Override + public ResourceManager create(ResourceManagerOptions options) { + // return new ResourceManagerImpl(options); + return null; // TODO(ajaykannan): Fix me! + } + } + + public static class DefaultResourceManagerRpcFactory implements ResourceManagerRpcFactory { + private static final ResourceManagerRpcFactory INSTANCE = + new DefaultResourceManagerRpcFactory(); + + @Override + public ResourceManagerRpc create(ResourceManagerOptions options) { + // return new DefaultResourceManagerRpc(options); + return null; // TODO(ajaykannan): Fix me! + } + } + + public static class Builder extends ServiceOptions.Builder { + + private Builder() {} + + private Builder(ResourceManagerOptions options) { + super(options); + } + + @Override + public ResourceManagerOptions build() { + return new ResourceManagerOptions(this); + } + } + + private ResourceManagerOptions(Builder builder) { + super(ResourceManagerFactory.class, ResourceManagerRpcFactory.class, builder); + } + + @Override + protected ResourceManagerFactory defaultServiceFactory() { + return DefaultResourceManagerFactory.INSTANCE; + } + + @Override + protected ResourceManagerRpcFactory defaultRpcFactory() { + return DefaultResourceManagerRpcFactory.INSTANCE; + } + + @Override + protected Set scopes() { + return SCOPES; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ResourceManagerOptions && baseEquals((ResourceManagerOptions) obj); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + public static Builder builder() { + return new Builder(); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java new file mode 100644 index 000000000000..3beaa0967443 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * A client to Google Cloud Resource Manager. + * //TODO(ajaykannan): add code example + * @see Google Cloud Resource Manager + */ + +package com.google.gcloud.resourcemanager; 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 new file mode 100644 index 000000000000..492b18a96c8a --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java @@ -0,0 +1,69 @@ +/* + * 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.spi; + +import com.google.api.services.cloudresourcemanager.model.Policy; +import com.google.api.services.cloudresourcemanager.model.Project; +import com.google.gcloud.resourcemanager.ResourceManagerException; + +import java.util.List; + +public interface ResourceManagerRpc { + + class Tuple { + private final X x; + private final Y y; + + private Tuple(X x, Y y) { + this.x = x; + this.y = y; + } + + public static Tuple of(X x, Y y) { + return new Tuple<>(x, y); + } + + public X x() { + return x; + } + + public Y y() { + return y; + } + } + + Project create(Project project) throws ResourceManagerException; + + void delete(String projectId) throws ResourceManagerException; + + Project get(String projectId) throws ResourceManagerException; + + Tuple> list() throws ResourceManagerException; + + Tuple> list(String filter) throws ResourceManagerException; + + void undelete(String projectId) throws ResourceManagerException; + + Project update(String projectId, Project project) throws ResourceManagerException; + + Policy getIamPolicy(String projectId) throws ResourceManagerException; + + void setIamPolicy(String projectId, Policy policy) throws ResourceManagerException; + + List testIamPermissions(String projectId, List permissions) + throws ResourceManagerException; +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpcFactory.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpcFactory.java new file mode 100644 index 000000000000..c2c607c0c205 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpcFactory.java @@ -0,0 +1,27 @@ +/* + * 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.spi; + +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +/** + * An interface for Resource Manager RPC factory. + * Implementation will be loaded via {@link java.util.ServiceLoader}. + */ +public interface ResourceManagerRpcFactory + extends ServiceRpcFactory { +} From 3687cb16aec0347bad882da69bc961740b118ea3 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 2 Nov 2015 17:08:59 -0800 Subject: [PATCH 02/35] Fixes to the ResourceManagerRpc layer, and also add resource manager to pom files spi api fixes --- .../google/gcloud/spi/ResourceManagerRpc.java | 34 ++++++++++++++++--- gcloud-java/pom.xml | 5 +++ pom.xml | 1 + 3 files changed, 36 insertions(+), 4 deletions(-) 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 492b18a96c8a..527e448521ed 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 @@ -46,15 +46,41 @@ public Y y() { } } + public class ListOptions { + private List filters; + private String pageToken; + + private static final ListOptions DEFAULT_INSTANCE = new ListOptions(null, null); + + ListOptions(List filters, String pageToken) { + this.filters = filters; + this.pageToken = pageToken; + } + + public static ListOptions getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + public static ListOptions createListOption(List filters, String pageToken) { + return new ListOptions(filters, pageToken); + } + + public String pageToken() { + return pageToken; + } + + public List filters() { + return filters; + } + } + Project create(Project project) throws ResourceManagerException; void delete(String projectId) throws ResourceManagerException; Project get(String projectId) throws ResourceManagerException; - Tuple> list() throws ResourceManagerException; - - Tuple> list(String filter) throws ResourceManagerException; + Tuple> list(ListOptions listOptions) throws ResourceManagerException; void undelete(String projectId) throws ResourceManagerException; @@ -64,6 +90,6 @@ public Y y() { void setIamPolicy(String projectId, Policy policy) throws ResourceManagerException; - List testIamPermissions(String projectId, List permissions) + boolean hasPermissions(String projectId, List permissions) throws ResourceManagerException; } diff --git a/gcloud-java/pom.xml b/gcloud-java/pom.xml index 7d8e251b54fb..655ef8f70e62 100644 --- a/gcloud-java/pom.xml +++ b/gcloud-java/pom.xml @@ -24,6 +24,11 @@ gcloud-java-datastore ${project.version} + + ${project.groupId} + gcloud-java-resourcemanager + ${project.version} + ${project.groupId} gcloud-java-storage diff --git a/pom.xml b/pom.xml index 5b11a09fb382..7d1751ee179d 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,7 @@ gcloud-java-core gcloud-java-datastore + gcloud-java-resourcemanager gcloud-java-storage gcloud-java gcloud-java-examples From 3892eedce3ca670eb5a8f2e83e914264c0406b2e Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Tue, 3 Nov 2015 08:36:02 -0800 Subject: [PATCH 03/35] minor changes to ResourceManagerRpc --- .../google/gcloud/spi/ResourceManagerRpc.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) 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 527e448521ed..285d4e887e14 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,14 +16,27 @@ 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.gcloud.resourcemanager.ResourceManagerException; +import java.util.Collections; import java.util.List; public interface ResourceManagerRpc { + public enum Permission { + CREATE, + DELETE, + GET, + LIST, + UPDATE, + GET_IAM_POLICY, + SET_IAM_POLICY; + } + class Tuple { private final X x; private final Y y; @@ -49,20 +62,24 @@ public Y y() { public class ListOptions { private List filters; private String pageToken; + private int pageSize; - private static final ListOptions DEFAULT_INSTANCE = new ListOptions(null, null); + private static final ListOptions DEFAULT_INSTANCE = + new ListOptions(Collections.emptyList(), null, -1); - ListOptions(List filters, String pageToken) { - this.filters = filters; + ListOptions(List filters, String pageToken, int pageSize) { + this.filters = checkNotNull(filters); this.pageToken = pageToken; + this.pageSize = pageSize; } public static ListOptions getDefaultInstance() { return DEFAULT_INSTANCE; } - public static ListOptions createListOption(List filters, String pageToken) { - return new ListOptions(filters, pageToken); + public static ListOptions createListOption( + List filters, String pageToken, int pageSize) { + return new ListOptions(filters, pageToken, pageSize); } public String pageToken() { @@ -72,6 +89,10 @@ public String pageToken() { public List filters() { return filters; } + + public int pageSize() { + return pageSize; + } } Project create(Project project) throws ResourceManagerException; @@ -90,6 +111,6 @@ public List filters() { void setIamPolicy(String projectId, Policy policy) throws ResourceManagerException; - boolean hasPermissions(String projectId, List permissions) + List hasPermissions(String projectId, List permissions) throws ResourceManagerException; } From a60f7d16050a4a639af80697e1d5c6c6d2dc4601 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Tue, 3 Nov 2015 09:32:36 -0800 Subject: [PATCH 04/35] Style updates to ResourceManagerRpc --- .../java/com/google/gcloud/spi/ResourceManagerRpc.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 285d4e887e14..de412b87278e 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 @@ -20,6 +20,7 @@ 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; @@ -34,7 +35,7 @@ public enum Permission { LIST, UPDATE, GET_IAM_POLICY, - SET_IAM_POLICY; + SET_IAM_POLICY } class Tuple { @@ -68,7 +69,7 @@ public class ListOptions { new ListOptions(Collections.emptyList(), null, -1); ListOptions(List filters, String pageToken, int pageSize) { - this.filters = checkNotNull(filters); + this.filters = checkNotNull(ImmutableList.copyOf(filters)); this.pageToken = pageToken; this.pageSize = pageSize; } @@ -105,7 +106,7 @@ public int pageSize() { void undelete(String projectId) throws ResourceManagerException; - Project update(String projectId, Project project) throws ResourceManagerException; + Project update(Project project) throws ResourceManagerException; Policy getIamPolicy(String projectId) throws ResourceManagerException; @@ -113,4 +114,6 @@ public int pageSize() { List hasPermissions(String projectId, List permissions) throws ResourceManagerException; + + // TODO(ajaykannan): implement "Organization" functionality when available } From c69af1acb3ee4cb24876f1444540c5b543e40c0c Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Tue, 3 Nov 2015 10:20:18 -0800 Subject: [PATCH 05/35] add return values to delete, undelete, and setIamPolicy --- .../google/gcloud/spi/ResourceManagerRpc.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) 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 de412b87278e..7511114a4500 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 @@ -38,6 +38,22 @@ public enum Permission { SET_IAM_POLICY } + public enum DeleteResult { + SUCCESS, + ALREADY_DELETED + } + + public enum UndeleteResult { + SUCCESS, + DELETE_IN_PROGRESS, + GONE + } + + public enum SetIamPolicyResult { + SUCCESS, + ETAG_NOT_MATCH + } + class Tuple { private final X x; private final Y y; @@ -98,19 +114,19 @@ public int pageSize() { Project create(Project project) throws ResourceManagerException; - void delete(String projectId) throws ResourceManagerException; + DeleteResult delete(String projectId) throws ResourceManagerException; Project get(String projectId) throws ResourceManagerException; Tuple> list(ListOptions listOptions) throws ResourceManagerException; - void undelete(String projectId) throws ResourceManagerException; + UndeleteResult undelete(String projectId) throws ResourceManagerException; Project update(Project project) throws ResourceManagerException; Policy getIamPolicy(String projectId) throws ResourceManagerException; - void setIamPolicy(String projectId, Policy policy) throws ResourceManagerException; + SetIamPolicyResult setIamPolicy(String projectId, Policy policy) throws ResourceManagerException; List hasPermissions(String projectId, List permissions) throws ResourceManagerException; From 249fae8f180a61e7a4ef619169c62d45a90b8076 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Wed, 4 Nov 2015 13:00:26 -0800 Subject: [PATCH 06/35] Remove spi result enums, change 'update/set' terminology to 'replace' --- .../google/gcloud/spi/ResourceManagerRpc.java | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) 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 7511114a4500..1223627bbfcb 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 @@ -33,25 +33,9 @@ public enum Permission { DELETE, GET, LIST, - UPDATE, + REPLACE, GET_IAM_POLICY, - SET_IAM_POLICY - } - - public enum DeleteResult { - SUCCESS, - ALREADY_DELETED - } - - public enum UndeleteResult { - SUCCESS, - DELETE_IN_PROGRESS, - GONE - } - - public enum SetIamPolicyResult { - SUCCESS, - ETAG_NOT_MATCH + REPLACE_IAM_POLICY } class Tuple { @@ -114,22 +98,22 @@ public int pageSize() { Project create(Project project) throws ResourceManagerException; - DeleteResult delete(String projectId) throws ResourceManagerException; + void delete(String projectId) throws ResourceManagerException; Project get(String projectId) throws ResourceManagerException; Tuple> list(ListOptions listOptions) throws ResourceManagerException; - UndeleteResult undelete(String projectId) throws ResourceManagerException; + void undelete(String projectId) throws ResourceManagerException; - Project update(Project project) throws ResourceManagerException; + Project replace(Project project) throws ResourceManagerException; Policy getIamPolicy(String projectId) throws ResourceManagerException; - SetIamPolicyResult setIamPolicy(String projectId, Policy policy) 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 + // TODO(ajaykannan): implement "Organization" functionality when available (issue #319) } From 65a6240d9dda87434786e016e3cc2cd4d5d26f53 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Thu, 12 Nov 2015 14:11:56 -0800 Subject: [PATCH 07/35] Project, ProjectInfo, Policy, and ResourceId classes added. --- .../google/gcloud/resourcemanager/Policy.java | 389 ++++++++++++++++++ .../gcloud/resourcemanager/Project.java | 96 +++++ .../gcloud/resourcemanager/ProjectInfo.java | 238 +++++++++++ .../gcloud/resourcemanager/ResourceId.java | 82 ++++ .../resourcemanager/ResourceManager.java | 80 +++- .../ResourceManagerException.java | 23 +- .../ResourceManagerOptions.java | 5 + .../google/gcloud/spi/ResourceManagerRpc.java | 27 +- .../gcloud/resourcemanager/PolicyTest.java | 108 +++++ .../resourcemanager/ProjectInfoTest.java | 73 ++++ .../gcloud/resourcemanager/ProjectTest.java | 214 ++++++++++ .../resourcemanager/ResourceIdTest.java | 42 ++ .../resourcemanager/SerializationTest.java | 126 ++++++ 13 files changed, 1475 insertions(+), 28 deletions(-) create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java create mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java create mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java create mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java create mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java create mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java new file mode 100644 index 000000000000..765e38d3c2f8 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java @@ -0,0 +1,389 @@ +/* + * 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.collect.ImmutableList; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A Google Cloud IAM Policy object + */ +public class Policy implements Serializable { + + private static final long serialVersionUID = 3493286111316914094L; + private final List bindings; + private final Integer version; + private final String etag; + + public static enum MemberType { + ALL_USERS("allUsers"), + ALL_AUTHENTICATED_USERS("allAuthenticatedUsers"), + USER("user:"), + SERVICE_ACCOUNT("serviceAccount:"), + GROUP("group:"), + DOMAIN("domain:"); + + private final String prefix; + + MemberType(String prefix) { + this.prefix = prefix; + } + + String prefix() { + return prefix; + } + } + + public enum RoleType { + OWNER, + EDITOR, + VIEWER; + } + + /** + * Represents a member belonging to an IAM policy binding + */ + public static final class Member implements Serializable { + + private static final long serialVersionUID = 6496912037577986137L; + private final MemberType memberType; + private final String emailOrDomain; + + Member(MemberType memberType, String emailOrDomain) { + this.memberType = memberType; + this.emailOrDomain = emailOrDomain; + } + + public static Member allUsers() { + throw new UnsupportedOperationException( + "Google Cloud Resource Manager does not support the \"all users\" member type yet."); + // return new Member(MemberType.ALL_USERS, null); + } + + public static Member allAuthenticatedUsers() { + throw new UnsupportedOperationException("Google Cloud Resource Manager does not support the " + + "\"all authenticated users\" member type yet."); + // return new Member(MemberType.ALL_AUTHENTICATED_USERS, null); + } + + public static Member user(String email) { + return new Member(MemberType.USER, email); + } + + public static Member serviceAccount(String email) { + return new Member(MemberType.SERVICE_ACCOUNT, email); + } + + public static Member group(String email) { + return new Member(MemberType.GROUP, email); + } + + public static Member domain(String domain) { + throw new UnsupportedOperationException( + "Google Cloud Resource Manager does not support domain members yet."); + // return new Member(MemberType.DOMAIN, domain); + } + + public MemberType type() { + return memberType; + } + + public String emailOrDomain() { + return emailOrDomain; + } + + @Override + public int hashCode() { + return Objects.hash(memberType, emailOrDomain); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Member && Objects.equals(this.memberType, ((Member) obj).memberType) + && Objects.equals(this.emailOrDomain, ((Member) obj).emailOrDomain); + } + } + + /** + * Represents an IAM policy binding + */ + public static class Binding implements Serializable { + + private static final long serialVersionUID = -8493421092718338925L; + private final RoleType role; + private final List members; + + public static class Builder { + private RoleType role; + private List members; + + Builder() { + members = new ArrayList(); + } + + public Builder role(RoleType role) { + this.role = role; + return this; + } + + public Builder members(List members) { + this.members = checkNotNull(members); + return this; + } + + public Builder clearMembers() { + this.members = new ArrayList<>(); + return this; + } + + public Builder addMember(Member member) { + this.members.add(member); + return this; + } + + public Builder removeMember(Member member) { + this.members.remove(member); + return this; + } + + public Binding build() { + return new Binding(role, members); + } + } + + private Binding(RoleType role, List members) { + this.role = role; + ImmutableList.Builder membersListBuilder = new ImmutableList.Builder<>(); + for (Member member : members) { + membersListBuilder.add(member); + } + this.members = membersListBuilder.build(); + } + + public static Binding binding(RoleType role, List members) { + return new Binding(role, members); + } + + public RoleType role() { + return role; + } + + public List members() { + return members; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + List mutableMembers = new ArrayList<>(); + for (Member member : members) { + mutableMembers.add(member); + } + return new Builder().role(role).members(mutableMembers); + } + + com.google.api.services.cloudresourcemanager.model.Binding toPb() { + com.google.api.services.cloudresourcemanager.model.Binding bindingPb = + new com.google.api.services.cloudresourcemanager.model.Binding(); + if (role != null) { + bindingPb.setRole("roles/" + role.toString().toLowerCase()); + } + List membersPb = new ArrayList<>(members.size()); + for (Member member : members) { + if (member.emailOrDomain() != null) { + membersPb.add(member.type().prefix() + member.emailOrDomain()); + } else { + membersPb.add(member.type().prefix()); + } + } + bindingPb.setMembers(membersPb); + return bindingPb; + } + + static Binding fromPb(com.google.api.services.cloudresourcemanager.model.Binding bindingPb) { + RoleType role = + (bindingPb.getRole() == null) + ? null : RoleType.valueOf(bindingPb.getRole().split("/")[1].toUpperCase()); + List members = new ArrayList<>(); + if (bindingPb.getMembers() != null) { + for (String memberPb : bindingPb.getMembers()) { + String[] memberInfo = memberPb.split(":", 2); + String memberTypeStr = memberInfo[0]; + String emailOrDomain = (memberInfo.length > 1) ? emailOrDomain = memberInfo[1] : null; + switch (memberTypeStr) { + case "allUsers": + members.add(new Member(MemberType.ALL_USERS, null)); + break; + case "allAuthenticatedUsers": + members.add(new Member(MemberType.ALL_AUTHENTICATED_USERS, null)); + break; + case "user": + members.add(new Member(MemberType.USER, checkNotNull(emailOrDomain))); + break; + case "serviceAccount": + members.add(new Member(MemberType.SERVICE_ACCOUNT, checkNotNull(emailOrDomain))); + break; + case "group": + members.add(new Member(MemberType.GROUP, checkNotNull(emailOrDomain))); + break; + case "domain": + members.add(new Member(MemberType.DOMAIN, checkNotNull(emailOrDomain))); + break; + default: + throw new UnsupportedOperationException("Unsupported member type: " + memberTypeStr); + } + } + } + return new Binding(role, members); + } + + @Override + public int hashCode() { + return Objects.hash(role, members); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Binding && Objects.equals(this.role, ((Binding) obj).role) + && Objects.equals(this.members, ((Binding) obj).members); + } + } + + public static final class Builder { + private List bindings; + private String etag; + private Integer version; + + private Builder() { + bindings = new ArrayList<>(); + } + + public Builder addBinding(Binding binding) { + this.bindings.add(binding); + return this; + } + + public Builder removeBinding(Binding binding) { + this.bindings.remove(binding); + return this; + } + + public Builder clearBindings() { + this.bindings = new ArrayList<>(); + return this; + } + + public Builder bindings(List bindings) { + this.bindings = checkNotNull(bindings); + return this; + } + + public Builder etag(String etag) { + this.etag = etag; + return this; + } + + public Builder version(Integer version) { + this.version = version; + return this; + } + + public Policy build() { + return new Policy(this); + } + } + + Policy(Builder builder) { + ImmutableList.Builder bindingsListBuilder = new ImmutableList.Builder<>(); + for (Binding binding : builder.bindings) { + bindingsListBuilder.add(binding); + } + bindings = bindingsListBuilder.build(); + version = builder.version; + etag = builder.etag; + } + + public List bindings() { + return bindings; + } + + public Integer version() { + return version; + } + + public String etag() { + return etag; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + List mutableBindings = new ArrayList<>(); + for (Binding binding : bindings) { + mutableBindings.add(binding); + } + return new Builder().bindings(mutableBindings).etag(etag).version(version); + } + + @Override + public int hashCode() { + return Objects.hash(bindings, etag, version); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Policy && Objects.equals(this.bindings, ((Policy) obj).bindings) + && Objects.equals(this.etag, ((Policy) obj).etag) + && Objects.equals(this.version, ((Policy) obj).version); + } + + com.google.api.services.cloudresourcemanager.model.Policy toPb() { + com.google.api.services.cloudresourcemanager.model.Policy policyPb = + new com.google.api.services.cloudresourcemanager.model.Policy(); + List bindingsPb = new ArrayList<>(); + for (Binding binding : bindings) { + bindingsPb.add(binding.toPb()); + } + policyPb.setBindings(bindingsPb); + policyPb.setVersion(version); + policyPb.setEtag(etag); + return policyPb; + } + + static Policy fromPb(com.google.api.services.cloudresourcemanager.model.Policy policyPb) { + Builder policyBuilder = Policy.builder(); + if (policyPb.getBindings() != null) { + for (com.google.api.services.cloudresourcemanager.model.Binding bindingPb : + policyPb.getBindings()) { + policyBuilder.addBinding(Binding.fromPb(bindingPb)); + } + } + policyBuilder.version(policyPb.getVersion()); + policyBuilder.etag(policyPb.getEtag()); + return policyBuilder.build(); + } +} 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..1813d1083018 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java @@ -0,0 +1,96 @@ +/* + * 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.gcloud.spi.ResourceManagerRpc.Permission; + +import java.util.ArrayList; +import java.util.List; + +/** + * A Google Cloud Resource Manager project object. + * + * 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; + private final Policy policy; + + public Project(ResourceManager resourceManager, ProjectInfo projectInfo, Policy policy) { + this.resourceManager = checkNotNull(resourceManager); + this.info = checkNotNull(projectInfo); + this.policy = checkNotNull(policy); + } + + public static Project load(ResourceManager resourceManager, String projectId) { + ProjectInfo projectInfo = resourceManager.get(projectId); + Policy policy = resourceManager.getIamPolicy(projectId); + return new Project(resourceManager, projectInfo, policy); + } + + public ProjectInfo info() { + return info; + } + + public Policy policy() { + return policy; + } + + public ResourceManager resourceManager() { + return resourceManager; + } + + public Project reload() { + return new Project( + resourceManager, resourceManager.get(info.id()), resourceManager.getIamPolicy(info.id())); + } + + public void delete() { + resourceManager.delete(info.id()); + } + + public void undelete() { + resourceManager.undelete(info.id()); + } + + public Project replace(ProjectInfo projectInfo) { + return new Project(resourceManager, resourceManager.replace(checkNotNull(projectInfo)), policy); + } + + public Project replaceIamPolicy(Policy policy) { + return new Project( + resourceManager, info, resourceManager.replaceIamPolicy(info.id(), checkNotNull(policy))); + } + + public List hasPermissions(Permission first, Permission... others) { + List permissions = new ArrayList<>(); + permissions.add(first); + for (Permission other : others) { + permissions.add(other); + } + return resourceManager.hasPermissions(info.id(), permissions); + } + + public boolean hasAllPermissions(Permission first, Permission... others) { + return !(hasPermissions(first, others).contains(false)); + } +} 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..d69f94c530d5 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java @@ -0,0 +1,238 @@ +/* + * 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.collect.ImmutableMap; + +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. + */ +public class ProjectInfo implements Serializable { + + private static final long serialVersionUID = 9148970963697734236L; + private final String name; + private final String id; + private final Map labels; + private final Long number; + private final State state; + private final Long createTimeMillis; + private final ResourceId parent; + + public enum State { + LIFECYCLE_STATE_UNSPECIFIED, + ACTIVE, + DELETE_REQUESTED, + DELETE_IN_PROGRESS; + } + + public static class Builder { + private String name; + private String id; + private Map labels; + private Long number; + private State state; + private Long createTimeMillis; + private ResourceId parent; + + Builder() { + labels = new HashMap(); + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder addLabel(String key, String value) { + this.labels.put(key, value); + return this; + } + + public Builder removeLabel(String key) { + this.labels.remove(key); + return this; + } + + public Builder clearLabels() { + this.labels.clear(); + return this; + } + + public Builder labels(Map labels) { + this.labels = checkNotNull(labels); + return this; + } + + Builder number(Long number) { + this.number = number; + return this; + } + + Builder state(State state) { + this.state = state; + return this; + } + + Builder createTimeMillis(Long createTimeMillis) { + this.createTimeMillis = createTimeMillis; + return this; + } + + public Builder parent(ResourceId parent) { + this.parent = parent; + return this; + } + + public ProjectInfo build() { + return new ProjectInfo(name, id, labels, number, state, createTimeMillis, parent); + } + } + + ProjectInfo(String name, String id, Map labels, Long number, State state, + Long createTimeMillis, ResourceId parent) { + this.name = name; + this.id = checkNotNull(id); + ImmutableMap.Builder labelsMapBuilder = ImmutableMap.builder(); + for (Map.Entry entry : labels.entrySet()) { + labelsMapBuilder.put(entry.getKey(), entry.getValue()); + } + this.labels = (labels == null) ? null : labelsMapBuilder.build(); + this.number = number; + this.state = state; + this.createTimeMillis = createTimeMillis; + this.parent = parent; + } + + public String id() { + return id; + } + + public String name() { + return name; + } + + public Long number() { + return number; + } + + public Map labels() { + return labels; + } + + public State state() { + return state; + } + + public Long createTimeMillis() { + return createTimeMillis; + } + + public ResourceId parent() { + return parent; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof ProjectInfo) { + ProjectInfo other = (ProjectInfo) obj; + return Objects.equals(this.name, other.name) && Objects.equals(this.id, other.id) + && Objects.equals(this.labels, other.labels) && Objects.equals(this.number, other.number) + && Objects.equals(this.state, other.state) + && Objects.equals(this.createTimeMillis, other.createTimeMillis) + && Objects.equals(this.parent, other.parent); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(name, id, labels, number, state, createTimeMillis, parent); + } + + public static Builder builder(String id) { + return new Builder().id(id); + } + + public Builder toBuilder() { + Map mutableLabels = new HashMap(); + for (Map.Entry entry : labels.entrySet()) { + mutableLabels.put(entry.getKey(), entry.getValue()); + } + return new Builder() + .name(name) + .id(id) + .labels(mutableLabels) + .number(number) + .state(state) + .createTimeMillis(createTimeMillis) + .parent(parent); + } + + 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(id); + projectPb.setLabels(labels); + projectPb.setProjectNumber(number); + 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()) + .name(projectPb.getName()) + .number(projectPb.getProjectNumber()); + 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/ResourceId.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java new file mode 100644 index 000000000000..c640709d6faa --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java @@ -0,0 +1,82 @@ +/* + * 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 java.io.Serializable; +import java.util.Objects; + +/** + * Represents a Google Cloud Resource Manager Resource ID + */ +public class ResourceId implements Serializable { + + private static final long serialVersionUID = 7928469304338358885L; + private final String id; + private final Type type; + + public enum Type { + ORGANIZATION("organization"), + UNKNOWN("unknown"); + + private final String strValue; + + Type(String strValue) { + this.strValue = strValue; + } + } + + private ResourceId(String id, Type type) { + this.id = checkNotNull(id); + this.type = checkNotNull(type); + } + + public String id() { + return id; + } + + public Type type() { + return type; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ResourceId && Objects.equals(this.id, ((ResourceId) obj).id) + && Objects.equals(this.type, ((ResourceId) obj).type); + } + + @Override + public int hashCode() { + return Objects.hash(id, type); + } + + public static ResourceId of(String id, Type type) { + return new ResourceId(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.strValue); + return resourceIdPb; + } + + static ResourceId fromPb( + com.google.api.services.cloudresourcemanager.model.ResourceId resourceIdPb) { + return new ResourceId(resourceIdPb.getId(), Type.valueOf(resourceIdPb.getType().toUpperCase())); + } +} 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..29fddd409e8a 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,12 @@ package com.google.gcloud.resourcemanager; +import com.google.gcloud.Page; import com.google.gcloud.Service; +import com.google.gcloud.spi.ResourceManagerRpc.ListOptions; +import com.google.gcloud.spi.ResourceManagerRpc.Permission; + +import java.util.List; /** * An interface for Google Cloud Resource Manager. @@ -27,5 +32,78 @@ public interface ResourceManager extends Service { public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; - // TODO(ajaykannan): Fix me! Add in missing methods. + /** + * Create a new project. + * + * @return ProjectInfo object representing the new project's metadata. The returned object will + * include additional read-only information, namely project number, lifecycle state, and creation + * time. + * @throws ResourceManagerException upon failure + */ + ProjectInfo create(ProjectInfo project); + + /** + * Delete the requested project. + * + * @throws ResourceManagerException upon failure + */ + void delete(String projectId); + + /** + * Return the requested project or {@code null} if not found. + * + * @throws ResourceManagerException upon failure + */ + ProjectInfo get(String projectId); + + /** + * List the projects viewable by the current user. Use {@link ListOptions} to filter this list, + * set page size, and set page tokens. Note that pagination is currently not implemented by the + * Cloud Resource Manager API. + * + * @return {@code Page}, a paginated list of projects. + * @throws ResourceManagerException upon failure + */ + Page list(ListOptions listOptions); + + /** + * Replace project metadata. + * + * @return the ProjectInfo representing the new project metadata + * @throws ResourceManagerException upon failure + */ + ProjectInfo replace(ProjectInfo newProject); + + /** + * Undo a delete request. This will only succeed if the project's lifecycle state is + * DELETE_REQUESTED. + * + * @throws ResourceManagerException + */ + void undelete(String projectId); + + /** + * Get the IAM policy for the project specified. + * + * @return IAM Policy + * @throws ResourceManagerException upon failure + */ + Policy getIamPolicy(String projectId); + + /** + * Replace the IAM Policy for a project with the policy given. + * + * @return the new IAM Policy + * @throws ResourceManagerException upon failure + */ + Policy replaceIamPolicy(String projectId, Policy policy); + + /** + * Test whether the caller of this function has the permissions provided as arguments. + * + * @return List of booleans representing whether the caller has the corresponding permission in + * the given permissions list. + * @throws ResourceManagerException upon failure + */ + List hasPermissions(String projectId, List permissions); } 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..8287ed167557 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,7 @@ package com.google.gcloud.resourcemanager; +import com.google.gcloud.BaseServiceException; import com.google.gcloud.RetryHelper.RetryHelperException; import com.google.gcloud.RetryHelper.RetryInterruptedException; @@ -25,29 +26,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); } /** @@ -60,6 +45,6 @@ public boolean retryable() { */ static ResourceManagerException translateAndThrow(RetryHelperException ex) { throw new ResourceManagerException(UNKNOWN_CODE, ex.getMessage(), false); - // TODO(ajaykannan): Fix me! + // 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..51d8e5411212 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 @@ -70,6 +70,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/ResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java index 1223627bbfcb..9410891771c9 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 @@ -29,13 +29,24 @@ public interface ResourceManagerRpc { public enum Permission { - CREATE, - DELETE, - GET, - LIST, - REPLACE, - GET_IAM_POLICY, - REPLACE_IAM_POLICY + CREATE("resourcemanager.projects.create"), + DELETE("resourcemanager.projects.delete"), + GET("resourcemanager.projects.get"), + LIST("resourcemanager.projects.list"), + REPLACE("resourcemanager.projects.replace"), + UNDELETE("resourcemanager.projects.undelete"), + GET_IAM_POLICY("resourcemanager.projects.getIamPolicy"), + REPLACE_IAM_POLICY("resourcemanager.projects.setIamPolicy"); + + String permissionPb; + + Permission(String permissionPb) { + this.permissionPb = permissionPb; + } + + String toPb() { + return permissionPb; + } } class Tuple { @@ -110,7 +121,7 @@ public int pageSize() { Policy getIamPolicy(String projectId) throws ResourceManagerException; - boolean replaceIamPolicy(String projectId, Policy policy) throws ResourceManagerException; + Policy replaceIamPolicy(String projectId, Policy policy) throws ResourceManagerException; List hasPermissions(String projectId, List permissions) throws ResourceManagerException; diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java new file mode 100644 index 000000000000..77037d2996c6 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java @@ -0,0 +1,108 @@ +/* + * 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.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.gcloud.resourcemanager.Policy.Binding; +import com.google.gcloud.resourcemanager.Policy.Member; +import com.google.gcloud.resourcemanager.Policy.RoleType; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class PolicyTest { + + private static final Binding OWNER_BINDING; + private static final Binding EDITOR_BINDING; + private static final Binding VIEWER_BINDING; + private static final Binding EMPTY_BINDING = Policy.Binding.builder().build(); + private static final List OWNER_MEMBER_LIST = new ArrayList<>(); + private static final List EDITOR_MEMBER_LIST = new ArrayList<>(); + private static final List VIEWER_MEMBER_LIST = new ArrayList<>(); + static { + OWNER_MEMBER_LIST.add(Member.user("first-owner@email.com")); + OWNER_MEMBER_LIST.add(Member.group("group-of-owners@email.com")); + OWNER_BINDING = + Policy.Binding.builder().role(RoleType.OWNER).members(OWNER_MEMBER_LIST).build(); + EDITOR_MEMBER_LIST.add(Member.serviceAccount("editor@someemail.com")); + EDITOR_BINDING = + Policy.Binding.builder().role(RoleType.EDITOR).members(EDITOR_MEMBER_LIST).build(); + VIEWER_MEMBER_LIST.add(Member.serviceAccount("app@someemail.com")); + VIEWER_MEMBER_LIST.add(Member.user("viewer@email.com")); + VIEWER_BINDING = + Policy.Binding.builder().role(RoleType.VIEWER).members(VIEWER_MEMBER_LIST).build(); + } + private static final Policy EMPTY_POLICY = Policy.builder().build(); + private static final Integer VERSION = 1; + private static final String ETAG = "some-etag-value"; + private static final Policy FULL_POLICY = + Policy.builder() + .addBinding(OWNER_BINDING) + .addBinding(EDITOR_BINDING) + .addBinding(VIEWER_BINDING) + .version(VERSION) + .etag(ETAG) + .build(); + + @Test + public void testBindingBuilder() { + assertEquals(OWNER_BINDING.role(), RoleType.OWNER); + assertEquals(OWNER_BINDING.members(), OWNER_MEMBER_LIST); + assertNull(EMPTY_BINDING.role()); + assertTrue(EMPTY_BINDING.members().isEmpty()); + } + + @Test + public void testBindingToBuilder() { + assertEquals(OWNER_BINDING, OWNER_BINDING.toBuilder().build()); + assertEquals(EMPTY_BINDING, EMPTY_BINDING.toBuilder().build()); + } + + @Test + public void testBindingToAndFromPb() { + assertEquals(OWNER_BINDING, Binding.fromPb(OWNER_BINDING.toPb())); + assertEquals(EDITOR_BINDING, Binding.fromPb(EDITOR_BINDING.toPb())); + assertEquals(VIEWER_BINDING, Binding.fromPb(VIEWER_BINDING.toPb())); + assertEquals(EMPTY_BINDING, Binding.fromPb(EMPTY_BINDING.toPb())); + } + + @Test + public void testPolicyBuilder() { + assertEquals(OWNER_BINDING, FULL_POLICY.bindings().get(0)); + assertEquals(EDITOR_BINDING, FULL_POLICY.bindings().get(1)); + assertEquals(VIEWER_BINDING, FULL_POLICY.bindings().get(2)); + assertEquals(VERSION, FULL_POLICY.version()); + assertEquals(ETAG, FULL_POLICY.etag()); + } + + @Test + public void testPolicyToBuilder() { + assertEquals(FULL_POLICY, FULL_POLICY.toBuilder().build()); + assertEquals(EMPTY_POLICY, EMPTY_POLICY.toBuilder().build()); + } + + @Test + public void testPolicyToAndFromPb() { + assertEquals(EMPTY_POLICY, Policy.fromPb(EMPTY_POLICY.toPb())); + assertEquals(FULL_POLICY, Policy.fromPb(FULL_POLICY.toPb())); + } +} 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..d6e27aecd103 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java @@ -0,0 +1,73 @@ +/* + * 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 org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class ProjectInfoTest { + + private static final String ID = "project-id"; + private static final String NAME = "myProj"; + private static final Map LABELS = new HashMap(); + static { + LABELS.put("k1", "v1"); + LABELS.put("k2", "k2"); + } + private static final Long NUMBER = 123L; + private static final Long CREATE_TIME_MILLIS = 123456789L; + private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; + private static final ResourceId PARENT = ResourceId.of("owner-id", ResourceId.Type.ORGANIZATION); + private static final ProjectInfo FULL_PROJECT_INFO = + ProjectInfo.builder(ID) + .name(NAME) + .labels(LABELS) + .number(NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .parent(PARENT) + .build(); + private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder(ID).build(); + + @Test + public void testBuilder() { + assertEquals(ID, FULL_PROJECT_INFO.id()); + assertEquals(NAME, FULL_PROJECT_INFO.name()); + assertEquals(LABELS, FULL_PROJECT_INFO.labels()); + assertEquals(NUMBER, FULL_PROJECT_INFO.number()); + assertEquals(CREATE_TIME_MILLIS, FULL_PROJECT_INFO.createTimeMillis()); + assertEquals(STATE, FULL_PROJECT_INFO.state()); + assertEquals(PARENT, FULL_PROJECT_INFO.parent()); + } + + @Test + public void testToBuilder() { + assertEquals(FULL_PROJECT_INFO, FULL_PROJECT_INFO.toBuilder().build()); + assertEquals(PARTIAL_PROJECT_INFO, PARTIAL_PROJECT_INFO.toBuilder().build()); + } + + @Test + public void testToAndFromPb() { + assertEquals(FULL_PROJECT_INFO, ProjectInfo.fromPb(FULL_PROJECT_INFO.toPb())); + assertEquals(PARTIAL_PROJECT_INFO, ProjectInfo.fromPb(PARTIAL_PROJECT_INFO.toPb())); + } +} + 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..6d36c024d0d2 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java @@ -0,0 +1,214 @@ +/* + * 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.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.gcloud.resourcemanager.Policy.Binding; +import com.google.gcloud.resourcemanager.Policy.Member; +import com.google.gcloud.resourcemanager.Policy.RoleType; +import com.google.gcloud.spi.ResourceManagerRpc.Permission; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ProjectTest { + private static final String ID = "project-id"; + private static final String NAME = "myProj"; + private static final Map LABELS = new HashMap(); + static { + LABELS.put("k1", "v1"); + LABELS.put("k2", "k2"); + } + private static final Long NUMBER = 123L; + private static final Long CREATE_TIME_MILLIS = 123456789L; + private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; + private static final ResourceId PARENT = ResourceId.of("owner-id", ResourceId.Type.ORGANIZATION); + private static final ProjectInfo PROJECT_INFO = + ProjectInfo.builder(ID) + .name(NAME) + .labels(LABELS) + .number(NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .parent(PARENT) + .build(); + private static final Binding OWNER_BINDING; + private static final Binding EDITOR_BINDING; + private static final Binding VIEWER_BINDING; + static { + List ownerMemberList = new ArrayList<>(); + List editorMemberList = new ArrayList<>(); + List viewerMemberList = new ArrayList<>(); + + ownerMemberList.add(Member.user("first-owner@email.com")); + ownerMemberList.add(Member.group("group-of-owners@email.com")); + OWNER_BINDING = Policy.Binding.builder().role(RoleType.OWNER).members(ownerMemberList).build(); + editorMemberList.add(Member.serviceAccount("editor@someemail.com")); + EDITOR_BINDING = + Policy.Binding.builder().role(RoleType.EDITOR).members(editorMemberList).build(); + viewerMemberList.add(Member.serviceAccount("app@someemail.com")); + viewerMemberList.add(Member.user("viewer@email.com")); + VIEWER_BINDING = + Policy.Binding.builder().role(RoleType.VIEWER).members(viewerMemberList).build(); + } + private static final Policy POLICY = + Policy.builder() + .addBinding(OWNER_BINDING) + .addBinding(EDITOR_BINDING) + .addBinding(VIEWER_BINDING) + .version(1) + .etag("some-etag-value") + .build(); + private static final List PERMISSIONS_REQUESTED = + Arrays.asList(new Permission[] {Permission.REPLACE, Permission.GET}); + private static final List PERMISSIONS_OWNED = Arrays.asList(new Boolean[] {false, true}); + + private ResourceManager resourceManager; + private Project project; + + @Before + public void setUp() throws Exception { + resourceManager = createStrictMock(ResourceManager.class); + project = new Project(resourceManager, PROJECT_INFO, POLICY); + } + + @After + public void tearDown() throws Exception { + verify(resourceManager); + } + + @Test + public void testLoad() { + expect(resourceManager.get(PROJECT_INFO.id())).andReturn(PROJECT_INFO); + expect(resourceManager.getIamPolicy(PROJECT_INFO.id())).andReturn(POLICY); + replay(resourceManager); + Project loadedProject = Project.load(resourceManager, PROJECT_INFO.id()); + assertEquals(PROJECT_INFO, loadedProject.info()); + assertEquals(POLICY, loadedProject.policy()); + } + + @Test + public void testReload() { + ProjectInfo newInfo = PROJECT_INFO.toBuilder().addLabel("k3", "v3").build(); + Policy newPolicy = POLICY.toBuilder().removeBinding(VIEWER_BINDING).build(); + expect(resourceManager.get(PROJECT_INFO.id())).andReturn(newInfo); + expect(resourceManager.getIamPolicy(PROJECT_INFO.id())).andReturn(newPolicy); + replay(resourceManager); + Project newProject = project.reload(); + assertSame(resourceManager, newProject.resourceManager()); + assertEquals(newInfo, newProject.info()); + assertEquals(newPolicy, newProject.policy()); + } + + @Test + public void testPolicy() { + assertEquals(project.policy(), POLICY); + replay(resourceManager); + } + + @Test + public void testInfo() { + assertEquals(project.info(), PROJECT_INFO); + replay(resourceManager); + } + + @Test + public void testResourceManager() { + assertEquals(project.resourceManager(), resourceManager); + replay(resourceManager); + } + + @Test + public void testDelete() { + resourceManager.delete(PROJECT_INFO.id()); + expectLastCall(); + replay(resourceManager); + project.delete(); + } + + @Test + public void testUndelete() { + resourceManager.undelete(PROJECT_INFO.id()); + expectLastCall(); + 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(newProject.resourceManager(), resourceManager); + assertEquals(newProject.info(), newInfo); + assertEquals(newProject.policy(), POLICY); + } + + @Test + public void testReplaceIamPolicy() { + Policy newPolicy = POLICY.toBuilder().removeBinding(VIEWER_BINDING).build(); + expect(resourceManager.replaceIamPolicy(PROJECT_INFO.id(), newPolicy)).andReturn(newPolicy); + replay(resourceManager); + Project newProject = project.replaceIamPolicy(newPolicy); + assertSame(newProject.resourceManager(), resourceManager); + assertEquals(newProject.info(), PROJECT_INFO); + assertEquals(newProject.policy(), newPolicy); + } + + @Test + public void testHasPermissions() { + expect(resourceManager.hasPermissions(PROJECT_INFO.id(), PERMISSIONS_REQUESTED)) + .andReturn(PERMISSIONS_OWNED); + replay(resourceManager); + List response = + project.hasPermissions(PERMISSIONS_REQUESTED.get(0), PERMISSIONS_REQUESTED.get(1)); + assertEquals(response, PERMISSIONS_OWNED); + } + + @Test + public void testHasAllPermissions() { + expect(resourceManager.hasPermissions(PROJECT_INFO.id(), PERMISSIONS_REQUESTED)) + .andReturn(PERMISSIONS_OWNED); + List permissionsRequested2 = + Arrays.asList(new Permission[] {Permission.UNDELETE, Permission.DELETE}); + List permissionsOwned2 = Arrays.asList(new Boolean[] {true, true}); + expect(resourceManager.hasPermissions(PROJECT_INFO.id(), permissionsRequested2)) + .andReturn(permissionsOwned2); + replay(resourceManager); + assertFalse( + project.hasAllPermissions(PERMISSIONS_REQUESTED.get(0), PERMISSIONS_REQUESTED.get(1))); + assertTrue( + project.hasAllPermissions(permissionsRequested2.get(0), permissionsRequested2.get(1))); + } +} diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java new file mode 100644 index 000000000000..1f66017312a6 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java @@ -0,0 +1,42 @@ +/* + * 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 org.junit.Test; + +public class ResourceIdTest { + +private static final ResourceId RESOURCE_ID = ResourceId.of("id", ResourceId.Type.ORGANIZATION); + + @Test + public void testOf() { + assertEquals(RESOURCE_ID.id(), "id"); + assertEquals(RESOURCE_ID.type(), ResourceId.Type.ORGANIZATION); + } + + @Test + public void testEquals() { + assertEquals(RESOURCE_ID, ResourceId.of("id", ResourceId.Type.ORGANIZATION)); + } + + @Test + public void testToAndFromPb() { + assertEquals(RESOURCE_ID, ResourceId.fromPb(RESOURCE_ID.toPb())); + } +} 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..e848c3a61680 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java @@ -0,0 +1,126 @@ +/* + * 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.gcloud.AuthCredentials; +import com.google.gcloud.PageImpl; +import com.google.gcloud.RetryParams; +import com.google.gcloud.resourcemanager.Policy.Binding; +import com.google.gcloud.resourcemanager.Policy.Member; +import com.google.gcloud.resourcemanager.Policy.RoleType; + +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.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SerializationTest { + + private static final ResourceId RESOURCE_ID = + ResourceId.of("some id", ResourceId.Type.ORGANIZATION); + private static final Binding OWNER_BINDING; + private static final Binding EDITOR_BINDING; + private static final Binding VIEWER_BINDING; + private static final Binding EMPTY_BINDING = Policy.Binding.builder().build(); + static { + List ownerMemberList = new ArrayList<>(); + List editorMemberList = new ArrayList<>(); + List viewerMemberList = new ArrayList<>(); + ownerMemberList.add(Member.user("first-owner@email.com")); + ownerMemberList.add(Member.group("group-of-owners@email.com")); + OWNER_BINDING = Policy.Binding.builder().role(RoleType.OWNER).members(ownerMemberList).build(); + editorMemberList.add(Member.serviceAccount("editor@someemail.com")); + EDITOR_BINDING = + Policy.Binding.builder().role(RoleType.EDITOR).members(editorMemberList).build(); + viewerMemberList.add(Member.serviceAccount("app@someemail.com")); + viewerMemberList.add(Member.user("viewer@email.com")); + VIEWER_BINDING = + Policy.Binding.builder().role(RoleType.VIEWER).members(viewerMemberList).build(); + } + private static final Policy POLICY = + Policy.builder() + .addBinding(OWNER_BINDING) + .addBinding(EDITOR_BINDING) + .addBinding(VIEWER_BINDING) + .version(1) + .etag("some-etag-value") + .build(); + private static final Policy EMPTY_POLICY = Policy.builder().build(); + private static final ProjectInfo PROJECT_INFO1 = ProjectInfo.builder("id1").build(); + private static final ProjectInfo PROJECT_INFO2; + static { + Map labels = new HashMap(); + labels.put("key", "value"); + PROJECT_INFO2 = + new ProjectInfo("name", "id", labels, 123L, ProjectInfo.State.ACTIVE, 1234L, RESOURCE_ID); + } + private static final PageImpl PAGE_RESULT = + new PageImpl<>(null, "c", Collections.singletonList(PROJECT_INFO1)); + + @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 = {RESOURCE_ID, OWNER_BINDING.members().get(0), OWNER_BINDING, + EDITOR_BINDING, VIEWER_BINDING, EMPTY_BINDING, POLICY, EMPTY_POLICY, PROJECT_INFO1, + PROJECT_INFO2, PAGE_RESULT}; + 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(); + } + } +} From 9d6fbff4ea8172f01a64a51dc78f58131e5120f0 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 16 Nov 2015 17:18:12 -0800 Subject: [PATCH 08/35] Fix style, simplify equals methods, fix tests, and add Project javadocs --- .../google/gcloud/resourcemanager/Policy.java | 102 +++++++----------- .../gcloud/resourcemanager/Project.java | 65 +++++++++-- .../gcloud/resourcemanager/ProjectInfo.java | 44 +++----- .../gcloud/resourcemanager/ResourceId.java | 15 +-- .../resourcemanager/ResourceManager.java | 21 ++-- .../ResourceManagerOptions.java | 7 ++ .../gcloud/resourcemanager/PolicyTest.java | 75 +++++++------ .../resourcemanager/ProjectInfoTest.java | 54 ++++++++-- .../gcloud/resourcemanager/ProjectTest.java | 84 ++++++--------- .../resourcemanager/ResourceIdTest.java | 20 +++- .../resourcemanager/SerializationTest.java | 64 +++++------ 11 files changed, 295 insertions(+), 256 deletions(-) diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java index 765e38d3c2f8..efa95d9f7c73 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.io.Serializable; import java.util.ArrayList; @@ -122,6 +123,32 @@ public boolean equals(Object obj) { return obj instanceof Member && Objects.equals(this.memberType, ((Member) obj).memberType) && Objects.equals(this.emailOrDomain, ((Member) obj).emailOrDomain); } + + String toPb() { + return emailOrDomain != null ? memberType.prefix() + emailOrDomain : memberType.prefix(); + } + + static Member fromPb(String memberPb) { + String[] memberInfo = memberPb.split(":", 2); + String memberStr = memberInfo[0]; + String emailOrDomain = (memberInfo.length > 1) ? memberInfo[1] : null; + switch (memberStr) { + case "allUsers": + return new Member(MemberType.ALL_USERS, null); + case "allAuthenticatedUsers": + return new Member(MemberType.ALL_AUTHENTICATED_USERS, null); + case "user": + return new Member(MemberType.USER, checkNotNull(emailOrDomain)); + case "serviceAccount": + return new Member(MemberType.SERVICE_ACCOUNT, checkNotNull(emailOrDomain)); + case "group": + return new Member(MemberType.GROUP, checkNotNull(emailOrDomain)); + case "domain": + return new Member(MemberType.DOMAIN, checkNotNull(emailOrDomain)); + default: + throw new UnsupportedOperationException("Unsupported member type: " + memberStr); + } + } } /** @@ -147,7 +174,7 @@ public Builder role(RoleType role) { } public Builder members(List members) { - this.members = checkNotNull(members); + this.members = Lists.newArrayList(checkNotNull(members)); return this; } @@ -172,15 +199,11 @@ public Binding build() { } private Binding(RoleType role, List members) { - this.role = role; - ImmutableList.Builder membersListBuilder = new ImmutableList.Builder<>(); - for (Member member : members) { - membersListBuilder.add(member); - } - this.members = membersListBuilder.build(); + this.role = checkNotNull(role); + this.members = ImmutableList.copyOf(members); } - public static Binding binding(RoleType role, List members) { + public static Binding of(RoleType role, List members) { return new Binding(role, members); } @@ -197,26 +220,16 @@ public static Builder builder() { } public Builder toBuilder() { - List mutableMembers = new ArrayList<>(); - for (Member member : members) { - mutableMembers.add(member); - } - return new Builder().role(role).members(mutableMembers); + return new Builder().role(role).members(members); } com.google.api.services.cloudresourcemanager.model.Binding toPb() { com.google.api.services.cloudresourcemanager.model.Binding bindingPb = new com.google.api.services.cloudresourcemanager.model.Binding(); - if (role != null) { - bindingPb.setRole("roles/" + role.toString().toLowerCase()); - } + bindingPb.setRole("roles/" + role.toString().toLowerCase()); List membersPb = new ArrayList<>(members.size()); for (Member member : members) { - if (member.emailOrDomain() != null) { - membersPb.add(member.type().prefix() + member.emailOrDomain()); - } else { - membersPb.add(member.type().prefix()); - } + membersPb.add(member.toPb()); } bindingPb.setMembers(membersPb); return bindingPb; @@ -229,31 +242,7 @@ static Binding fromPb(com.google.api.services.cloudresourcemanager.model.Binding List members = new ArrayList<>(); if (bindingPb.getMembers() != null) { for (String memberPb : bindingPb.getMembers()) { - String[] memberInfo = memberPb.split(":", 2); - String memberTypeStr = memberInfo[0]; - String emailOrDomain = (memberInfo.length > 1) ? emailOrDomain = memberInfo[1] : null; - switch (memberTypeStr) { - case "allUsers": - members.add(new Member(MemberType.ALL_USERS, null)); - break; - case "allAuthenticatedUsers": - members.add(new Member(MemberType.ALL_AUTHENTICATED_USERS, null)); - break; - case "user": - members.add(new Member(MemberType.USER, checkNotNull(emailOrDomain))); - break; - case "serviceAccount": - members.add(new Member(MemberType.SERVICE_ACCOUNT, checkNotNull(emailOrDomain))); - break; - case "group": - members.add(new Member(MemberType.GROUP, checkNotNull(emailOrDomain))); - break; - case "domain": - members.add(new Member(MemberType.DOMAIN, checkNotNull(emailOrDomain))); - break; - default: - throw new UnsupportedOperationException("Unsupported member type: " + memberTypeStr); - } + members.add(Member.fromPb(memberPb)); } } return new Binding(role, members); @@ -266,8 +255,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return obj instanceof Binding && Objects.equals(this.role, ((Binding) obj).role) - && Objects.equals(this.members, ((Binding) obj).members); + return obj instanceof Binding && Objects.equals(toPb(), ((Binding) obj).toPb()); } } @@ -296,7 +284,7 @@ public Builder clearBindings() { } public Builder bindings(List bindings) { - this.bindings = checkNotNull(bindings); + this.bindings = new ArrayList<>(checkNotNull(bindings)); return this; } @@ -316,11 +304,7 @@ public Policy build() { } Policy(Builder builder) { - ImmutableList.Builder bindingsListBuilder = new ImmutableList.Builder<>(); - for (Binding binding : builder.bindings) { - bindingsListBuilder.add(binding); - } - bindings = bindingsListBuilder.build(); + bindings = ImmutableList.copyOf(builder.bindings); version = builder.version; etag = builder.etag; } @@ -342,11 +326,7 @@ public static Builder builder() { } public Builder toBuilder() { - List mutableBindings = new ArrayList<>(); - for (Binding binding : bindings) { - mutableBindings.add(binding); - } - return new Builder().bindings(mutableBindings).etag(etag).version(version); + return new Builder().bindings(bindings).etag(etag).version(version); } @Override @@ -356,9 +336,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return obj instanceof Policy && Objects.equals(this.bindings, ((Policy) obj).bindings) - && Objects.equals(this.etag, ((Policy) obj).etag) - && Objects.equals(this.version, ((Policy) obj).version); + return obj instanceof Policy && Objects.equals(toPb(), ((Policy) obj).toPb()); } com.google.api.services.cloudresourcemanager.model.Policy toPb() { 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 index 1813d1083018..74296a0be773 100644 --- 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 @@ -20,7 +20,6 @@ import com.google.gcloud.spi.ResourceManagerRpc.Permission; -import java.util.ArrayList; import java.util.List; /** @@ -35,12 +34,22 @@ public class Project { private final ProjectInfo info; private final Policy policy; + /** + * Constructs a Project object that contains the ProjectInfo and Policy given. + */ public Project(ResourceManager resourceManager, ProjectInfo projectInfo, Policy policy) { this.resourceManager = checkNotNull(resourceManager); this.info = checkNotNull(projectInfo); this.policy = checkNotNull(policy); } + /** + * Constructs a Project object that contains project and policy information loaded from the + * server. + * + * @return Project object containing the project's metadata and IAM policy + * @throws ResourceManagerException upon failure + */ public static Project load(ResourceManager resourceManager, String projectId) { ProjectInfo projectInfo = resourceManager.get(projectId); Policy policy = resourceManager.getIamPolicy(projectId); @@ -59,38 +68,76 @@ public ResourceManager resourceManager() { return resourceManager; } + /** + * Returns a Project object with updated project and policy information. + * + * @return Project object containing the project's updated metadata and IAM policy + * @throws ResourceManagerException upon failure + */ public Project reload() { return new Project( resourceManager, resourceManager.get(info.id()), resourceManager.getIamPolicy(info.id())); } + /** + * Requests that this project be deleted. For an unspecified amount of time, this action can be + * undone by calling {@link #undelete}. + * + * @throws ResourceManagerException upon failure + */ public void delete() { resourceManager.delete(info.id()); } + /** + * Requests that a project's lifecycle status be changed from {@code DELETE_REQUESTED} to + * {@code ACTIVE}. + * + * @throws ResourceManagerException upon failure + */ public void undelete() { resourceManager.undelete(info.id()); } + /** + * Replaces the project metadata (not including the IAM policy) using the given ProjectInfo. + * + * @return Project object containing the project's updated metadata + * @throws ResourceManagerException upon failure + */ public Project replace(ProjectInfo projectInfo) { return new Project(resourceManager, resourceManager.replace(checkNotNull(projectInfo)), policy); } + /** + * Replaces the project's IAM policy using the given policy. + * + * @return Project object containing the project's updated IAM policy + * @throws ResourceManagerException upon failure + */ public Project replaceIamPolicy(Policy policy) { return new Project( resourceManager, info, resourceManager.replaceIamPolicy(info.id(), checkNotNull(policy))); } - public List hasPermissions(Permission first, Permission... others) { - List permissions = new ArrayList<>(); - permissions.add(first); - for (Permission other : others) { - permissions.add(other); - } + /** + * Returns whether the caller has the permissions specified in the parameters. + * + * @return List of booleans representing whether the user has the corresponding permission + * provided as a parameter + * @throws ResourceManagerException upon failure + */ + public List hasPermissions(Permission... permissions) { return resourceManager.hasPermissions(info.id(), permissions); } - public boolean hasAllPermissions(Permission first, Permission... others) { - return !(hasPermissions(first, others).contains(false)); + /** + * Returns whether the caller has all the permissions specified in the parameters. + * + * @return true if the caller has all the permissions specified, otherwise false. + * @throws ResourceManagerException upon failure + */ + public boolean hasAllPermissions(Permission... permissions) { + return !(hasPermissions(permissions).contains(false)); } } 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 index d69f94c530d5..4e3aed325db7 100644 --- 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 @@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import org.joda.time.DateTime; import org.joda.time.format.ISODateTimeFormat; @@ -86,7 +87,7 @@ public Builder clearLabels() { } public Builder labels(Map labels) { - this.labels = checkNotNull(labels); + this.labels = Maps.newHashMap(checkNotNull(labels)); return this; } @@ -111,23 +112,18 @@ public Builder parent(ResourceId parent) { } public ProjectInfo build() { - return new ProjectInfo(name, id, labels, number, state, createTimeMillis, parent); + return new ProjectInfo(this); } } - ProjectInfo(String name, String id, Map labels, Long number, State state, - Long createTimeMillis, ResourceId parent) { - this.name = name; - this.id = checkNotNull(id); - ImmutableMap.Builder labelsMapBuilder = ImmutableMap.builder(); - for (Map.Entry entry : labels.entrySet()) { - labelsMapBuilder.put(entry.getKey(), entry.getValue()); - } - this.labels = (labels == null) ? null : labelsMapBuilder.build(); - this.number = number; - this.state = state; - this.createTimeMillis = createTimeMillis; - this.parent = parent; + ProjectInfo(Builder builder) { + this.name = builder.name; + this.id = checkNotNull(builder.id); + this.labels = ImmutableMap.copyOf(builder.labels); + this.number = builder.number; + this.state = builder.state; + this.createTimeMillis = builder.createTimeMillis; + this.parent = builder.parent; } public String id() { @@ -160,17 +156,7 @@ public ResourceId parent() { @Override public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof ProjectInfo) { - ProjectInfo other = (ProjectInfo) obj; - return Objects.equals(this.name, other.name) && Objects.equals(this.id, other.id) - && Objects.equals(this.labels, other.labels) && Objects.equals(this.number, other.number) - && Objects.equals(this.state, other.state) - && Objects.equals(this.createTimeMillis, other.createTimeMillis) - && Objects.equals(this.parent, other.parent); - } - return false; + return obj instanceof ProjectInfo && Objects.equals(toPb(), ((ProjectInfo) obj).toPb()); } @Override @@ -183,14 +169,10 @@ public static Builder builder(String id) { } public Builder toBuilder() { - Map mutableLabels = new HashMap(); - for (Map.Entry entry : labels.entrySet()) { - mutableLabels.put(entry.getKey(), entry.getValue()); - } return new Builder() .name(name) .id(id) - .labels(mutableLabels) + .labels(labels) .number(number) .state(state) .createTimeMillis(createTimeMillis) diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java index c640709d6faa..6e6022460e04 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java @@ -29,14 +29,8 @@ public class ResourceId implements Serializable { private final Type type; public enum Type { - ORGANIZATION("organization"), - UNKNOWN("unknown"); - - private final String strValue; - - Type(String strValue) { - this.strValue = strValue; - } + ORGANIZATION, + UNKNOWN; } private ResourceId(String id, Type type) { @@ -54,8 +48,7 @@ public Type type() { @Override public boolean equals(Object obj) { - return obj instanceof ResourceId && Objects.equals(this.id, ((ResourceId) obj).id) - && Objects.equals(this.type, ((ResourceId) obj).type); + return obj instanceof ResourceId && Objects.equals(toPb(), ((ResourceId) obj).toPb()); } @Override @@ -71,7 +64,7 @@ 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.strValue); + resourceIdPb.setType(type.toString().toLowerCase()); return resourceIdPb; } 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 29fddd409e8a..ca6a1cb24b27 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 @@ -35,15 +35,16 @@ public interface ResourceManager extends Service { /** * Create a new project. * - * @return ProjectInfo object representing the new project's metadata. The returned object will - * include additional read-only information, namely project number, lifecycle state, and creation - * time. + * @return ProjectInfo object representing the new project's metadata. The returned object will + * include additional read-only information, namely project number, lifecycle state, and + * creation time. * @throws ResourceManagerException upon failure */ ProjectInfo create(ProjectInfo project); /** - * Delete the requested project. + * Sends a request to delete a project. For an unspecified amount of time, this action can be + * undone using {@link #undelete}. * * @throws ResourceManagerException upon failure */ @@ -57,8 +58,8 @@ public interface ResourceManager extends Service { ProjectInfo get(String projectId); /** - * List the projects viewable by the current user. Use {@link ListOptions} to filter this list, - * set page size, and set page tokens. Note that pagination is currently not implemented by the + * List the projects viewable by the current user. Use {@link ListOptions} to filter this list, + * set page size, and set page tokens. Note that pagination is currently not implemented by the * Cloud Resource Manager API. * * @return {@code Page}, a paginated list of projects. @@ -75,8 +76,8 @@ public interface ResourceManager extends Service { ProjectInfo replace(ProjectInfo newProject); /** - * Undo a delete request. This will only succeed if the project's lifecycle state is - * DELETE_REQUESTED. + * Undo a delete request. This will only succeed if the server processes the undelete request + * while the project's state is {@code DELETE_REQUESTED}. * * @throws ResourceManagerException */ @@ -102,8 +103,8 @@ public interface ResourceManager extends Service { * Test whether the caller of this function has the permissions provided as arguments. * * @return List of booleans representing whether the caller has the corresponding permission in - * the given permissions list. + * the given permissions array. * @throws ResourceManagerException upon failure */ - List hasPermissions(String projectId, List permissions); + List hasPermissions(String projectId, Permission... permissions); } 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 51d8e5411212..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(); diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java index 77037d2996c6..6b52f20f3fae 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java @@ -17,40 +17,31 @@ package com.google.gcloud.resourcemanager; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; +import com.google.common.collect.ImmutableList; import com.google.gcloud.resourcemanager.Policy.Binding; import com.google.gcloud.resourcemanager.Policy.Member; import com.google.gcloud.resourcemanager.Policy.RoleType; import org.junit.Test; -import java.util.ArrayList; import java.util.List; public class PolicyTest { - private static final Binding OWNER_BINDING; - private static final Binding EDITOR_BINDING; - private static final Binding VIEWER_BINDING; - private static final Binding EMPTY_BINDING = Policy.Binding.builder().build(); - private static final List OWNER_MEMBER_LIST = new ArrayList<>(); - private static final List EDITOR_MEMBER_LIST = new ArrayList<>(); - private static final List VIEWER_MEMBER_LIST = new ArrayList<>(); - static { - OWNER_MEMBER_LIST.add(Member.user("first-owner@email.com")); - OWNER_MEMBER_LIST.add(Member.group("group-of-owners@email.com")); - OWNER_BINDING = - Policy.Binding.builder().role(RoleType.OWNER).members(OWNER_MEMBER_LIST).build(); - EDITOR_MEMBER_LIST.add(Member.serviceAccount("editor@someemail.com")); - EDITOR_BINDING = - Policy.Binding.builder().role(RoleType.EDITOR).members(EDITOR_MEMBER_LIST).build(); - VIEWER_MEMBER_LIST.add(Member.serviceAccount("app@someemail.com")); - VIEWER_MEMBER_LIST.add(Member.user("viewer@email.com")); - VIEWER_BINDING = - Policy.Binding.builder().role(RoleType.VIEWER).members(VIEWER_MEMBER_LIST).build(); - } + private static final List OWNER_MEMBER_LIST = ImmutableList.of( + Member.user("first-owner@email.com"), Member.group("group-of-owners@email.com")); + private static final List EDITOR_MEMBER_LIST = + ImmutableList.of(Member.serviceAccount("editor@someemail.com")); + private static final List VIEWER_MEMBER_LIST = + ImmutableList.of(Member.serviceAccount("app@someemail.com"), Member.user("viewer@email.com")); + private static final Binding OWNER_BINDING = + Policy.Binding.builder().role(RoleType.OWNER).members(OWNER_MEMBER_LIST).build(); + private static final Binding EDITOR_BINDING = + Policy.Binding.builder().role(RoleType.EDITOR).members(EDITOR_MEMBER_LIST).build(); + private static final Binding VIEWER_BINDING = + Policy.Binding.builder().role(RoleType.VIEWER).members(VIEWER_MEMBER_LIST).build(); private static final Policy EMPTY_POLICY = Policy.builder().build(); private static final Integer VERSION = 1; private static final String ETAG = "some-etag-value"; @@ -65,16 +56,13 @@ public class PolicyTest { @Test public void testBindingBuilder() { - assertEquals(OWNER_BINDING.role(), RoleType.OWNER); - assertEquals(OWNER_BINDING.members(), OWNER_MEMBER_LIST); - assertNull(EMPTY_BINDING.role()); - assertTrue(EMPTY_BINDING.members().isEmpty()); + assertEquals(RoleType.OWNER, OWNER_BINDING.role()); + assertEquals(OWNER_MEMBER_LIST, OWNER_BINDING.members()); } @Test public void testBindingToBuilder() { assertEquals(OWNER_BINDING, OWNER_BINDING.toBuilder().build()); - assertEquals(EMPTY_BINDING, EMPTY_BINDING.toBuilder().build()); } @Test @@ -82,7 +70,6 @@ public void testBindingToAndFromPb() { assertEquals(OWNER_BINDING, Binding.fromPb(OWNER_BINDING.toPb())); assertEquals(EDITOR_BINDING, Binding.fromPb(EDITOR_BINDING.toPb())); assertEquals(VIEWER_BINDING, Binding.fromPb(VIEWER_BINDING.toPb())); - assertEquals(EMPTY_BINDING, Binding.fromPb(EMPTY_BINDING.toPb())); } @Test @@ -96,13 +83,35 @@ public void testPolicyBuilder() { @Test public void testPolicyToBuilder() { - assertEquals(FULL_POLICY, FULL_POLICY.toBuilder().build()); - assertEquals(EMPTY_POLICY, EMPTY_POLICY.toBuilder().build()); + comparePolicies(FULL_POLICY, FULL_POLICY.toBuilder().build()); + comparePolicies(EMPTY_POLICY, EMPTY_POLICY.toBuilder().build()); } @Test public void testPolicyToAndFromPb() { - assertEquals(EMPTY_POLICY, Policy.fromPb(EMPTY_POLICY.toPb())); - assertEquals(FULL_POLICY, Policy.fromPb(FULL_POLICY.toPb())); + comparePolicies(FULL_POLICY, Policy.fromPb(FULL_POLICY.toPb())); + comparePolicies(EMPTY_POLICY, Policy.fromPb(EMPTY_POLICY.toPb())); + } + + @Test + public void testEquals() { + comparePolicies( + FULL_POLICY, + Policy.builder() + .addBinding(OWNER_BINDING) + .addBinding(EDITOR_BINDING) + .addBinding(VIEWER_BINDING) + .version(VERSION) + .etag(ETAG) + .build()); + comparePolicies(EMPTY_POLICY, Policy.builder().build()); + assertNotEquals(FULL_POLICY, EMPTY_POLICY); + } + + private void comparePolicies(Policy expected, Policy value) { + assertEquals(expected, value); + assertEquals(expected.bindings(), value.bindings()); + assertEquals(expected.version(), value.version()); + assertEquals(expected.etag(), value.etag()); } } 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 index d6e27aecd103..c0bfb73af3fd 100644 --- 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 @@ -17,21 +17,20 @@ 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.HashMap; import java.util.Map; public class ProjectInfoTest { private static final String ID = "project-id"; private static final String NAME = "myProj"; - private static final Map LABELS = new HashMap(); - static { - LABELS.put("k1", "v1"); - LABELS.put("k2", "k2"); - } + private static final Map LABELS = ImmutableMap.of("k1", "v1", "k2", "v2"); private static final Long NUMBER = 123L; private static final Long CREATE_TIME_MILLIS = 123456789L; private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; @@ -56,18 +55,53 @@ public void testBuilder() { assertEquals(CREATE_TIME_MILLIS, FULL_PROJECT_INFO.createTimeMillis()); assertEquals(STATE, FULL_PROJECT_INFO.state()); assertEquals(PARENT, FULL_PROJECT_INFO.parent()); + + assertEquals(ID, PARTIAL_PROJECT_INFO.id()); + assertEquals(null, PARTIAL_PROJECT_INFO.name()); + assertTrue(PARTIAL_PROJECT_INFO.labels().isEmpty()); + assertEquals(null, PARTIAL_PROJECT_INFO.number()); + assertEquals(null, PARTIAL_PROJECT_INFO.createTimeMillis()); + assertEquals(null, PARTIAL_PROJECT_INFO.state()); + assertEquals(null, PARTIAL_PROJECT_INFO.parent()); } @Test public void testToBuilder() { - assertEquals(FULL_PROJECT_INFO, FULL_PROJECT_INFO.toBuilder().build()); - assertEquals(PARTIAL_PROJECT_INFO, PARTIAL_PROJECT_INFO.toBuilder().build()); + compareProjects(FULL_PROJECT_INFO, FULL_PROJECT_INFO.toBuilder().build()); + compareProjects(PARTIAL_PROJECT_INFO, PARTIAL_PROJECT_INFO.toBuilder().build()); } @Test public void testToAndFromPb() { - assertEquals(FULL_PROJECT_INFO, ProjectInfo.fromPb(FULL_PROJECT_INFO.toPb())); - assertEquals(PARTIAL_PROJECT_INFO, ProjectInfo.fromPb(PARTIAL_PROJECT_INFO.toPb())); + 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(ID) + .name(NAME) + .labels(LABELS) + .number(NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .parent(PARENT) + .build()); + compareProjects(PARTIAL_PROJECT_INFO, ProjectInfo.builder(ID).build()); + assertNotEquals(FULL_PROJECT_INFO, PARTIAL_PROJECT_INFO); + } + + private void compareProjects(ProjectInfo expected, ProjectInfo value) { + assertEquals(expected, value); + assertEquals(expected.id(), value.id()); + assertEquals(expected.name(), value.name()); + assertEquals(expected.labels(), value.labels()); + assertEquals(expected.number(), value.number()); + 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 index 6d36c024d0d2..a4388d8a725e 100644 --- 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 @@ -26,6 +26,8 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.gcloud.resourcemanager.Policy.Binding; import com.google.gcloud.resourcemanager.Policy.Member; import com.google.gcloud.resourcemanager.Policy.RoleType; @@ -35,20 +37,13 @@ import org.junit.Before; import org.junit.Test; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; public class ProjectTest { private static final String ID = "project-id"; private static final String NAME = "myProj"; - private static final Map LABELS = new HashMap(); - static { - LABELS.put("k1", "v1"); - LABELS.put("k2", "k2"); - } + private static final Map LABELS = ImmutableMap.of("k1", "v1", "k2", "v2"); private static final Long NUMBER = 123L; private static final Long CREATE_TIME_MILLIS = 123456789L; private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; @@ -62,25 +57,18 @@ public class ProjectTest { .state(STATE) .parent(PARENT) .build(); - private static final Binding OWNER_BINDING; - private static final Binding EDITOR_BINDING; - private static final Binding VIEWER_BINDING; - static { - List ownerMemberList = new ArrayList<>(); - List editorMemberList = new ArrayList<>(); - List viewerMemberList = new ArrayList<>(); - - ownerMemberList.add(Member.user("first-owner@email.com")); - ownerMemberList.add(Member.group("group-of-owners@email.com")); - OWNER_BINDING = Policy.Binding.builder().role(RoleType.OWNER).members(ownerMemberList).build(); - editorMemberList.add(Member.serviceAccount("editor@someemail.com")); - EDITOR_BINDING = - Policy.Binding.builder().role(RoleType.EDITOR).members(editorMemberList).build(); - viewerMemberList.add(Member.serviceAccount("app@someemail.com")); - viewerMemberList.add(Member.user("viewer@email.com")); - VIEWER_BINDING = - Policy.Binding.builder().role(RoleType.VIEWER).members(viewerMemberList).build(); - } + private static final List OWNER_MEMBER_LIST = ImmutableList.of( + Member.user("first-owner@email.com"), Member.group("group-of-owners@email.com")); + private static final List EDITOR_MEMBER_LIST = + ImmutableList.of(Member.serviceAccount("editor@someemail.com")); + private static final List VIEWER_MEMBER_LIST = + ImmutableList.of(Member.serviceAccount("app@someemail.com"), Member.user("viewer@email.com")); + private static final Binding OWNER_BINDING = + Policy.Binding.builder().role(RoleType.OWNER).members(OWNER_MEMBER_LIST).build(); + private static final Binding EDITOR_BINDING = + Policy.Binding.builder().role(RoleType.EDITOR).members(EDITOR_MEMBER_LIST).build(); + private static final Binding VIEWER_BINDING = + Policy.Binding.builder().role(RoleType.VIEWER).members(VIEWER_MEMBER_LIST).build(); private static final Policy POLICY = Policy.builder() .addBinding(OWNER_BINDING) @@ -89,9 +77,8 @@ public class ProjectTest { .version(1) .etag("some-etag-value") .build(); - private static final List PERMISSIONS_REQUESTED = - Arrays.asList(new Permission[] {Permission.REPLACE, Permission.GET}); - private static final List PERMISSIONS_OWNED = Arrays.asList(new Boolean[] {false, true}); + private static final Permission[] PERMISSIONS_REQUESTED = {Permission.REPLACE, Permission.GET}; + private static final List PERMISSIONS_OWNED = ImmutableList.of(false, true); private ResourceManager resourceManager; private Project project; @@ -132,20 +119,20 @@ public void testReload() { @Test public void testPolicy() { - assertEquals(project.policy(), POLICY); replay(resourceManager); + assertEquals(POLICY, project.policy()); } @Test public void testInfo() { - assertEquals(project.info(), PROJECT_INFO); replay(resourceManager); + assertEquals(PROJECT_INFO, project.info()); } @Test public void testResourceManager() { - assertEquals(project.resourceManager(), resourceManager); replay(resourceManager); + assertEquals(resourceManager, project.resourceManager()); } @Test @@ -170,9 +157,9 @@ public void testReplace() { expect(resourceManager.replace(newInfo)).andReturn(newInfo); replay(resourceManager); Project newProject = project.replace(newInfo); - assertSame(newProject.resourceManager(), resourceManager); - assertEquals(newProject.info(), newInfo); - assertEquals(newProject.policy(), POLICY); + assertSame(resourceManager, newProject.resourceManager()); + assertEquals(newInfo, newProject.info()); + assertEquals(POLICY, newProject.policy()); } @Test @@ -181,9 +168,9 @@ public void testReplaceIamPolicy() { expect(resourceManager.replaceIamPolicy(PROJECT_INFO.id(), newPolicy)).andReturn(newPolicy); replay(resourceManager); Project newProject = project.replaceIamPolicy(newPolicy); - assertSame(newProject.resourceManager(), resourceManager); - assertEquals(newProject.info(), PROJECT_INFO); - assertEquals(newProject.policy(), newPolicy); + assertSame(resourceManager, newProject.resourceManager()); + assertEquals(PROJECT_INFO, newProject.info()); + assertEquals(newPolicy, newProject.policy()); } @Test @@ -192,23 +179,20 @@ public void testHasPermissions() { .andReturn(PERMISSIONS_OWNED); replay(resourceManager); List response = - project.hasPermissions(PERMISSIONS_REQUESTED.get(0), PERMISSIONS_REQUESTED.get(1)); - assertEquals(response, PERMISSIONS_OWNED); + project.hasPermissions(PERMISSIONS_REQUESTED[0], PERMISSIONS_REQUESTED[1]); + assertEquals(PERMISSIONS_OWNED, response); } @Test public void testHasAllPermissions() { expect(resourceManager.hasPermissions(PROJECT_INFO.id(), PERMISSIONS_REQUESTED)) .andReturn(PERMISSIONS_OWNED); - List permissionsRequested2 = - Arrays.asList(new Permission[] {Permission.UNDELETE, Permission.DELETE}); - List permissionsOwned2 = Arrays.asList(new Boolean[] {true, true}); - expect(resourceManager.hasPermissions(PROJECT_INFO.id(), permissionsRequested2)) - .andReturn(permissionsOwned2); + Permission[] permissionsRequestAllOwned = {Permission.UNDELETE, Permission.DELETE}; + List permissionsResponseAllOwned = ImmutableList.of(true, true); + expect(resourceManager.hasPermissions(PROJECT_INFO.id(), permissionsRequestAllOwned)) + .andReturn(permissionsResponseAllOwned); replay(resourceManager); - assertFalse( - project.hasAllPermissions(PERMISSIONS_REQUESTED.get(0), PERMISSIONS_REQUESTED.get(1))); - assertTrue( - project.hasAllPermissions(permissionsRequested2.get(0), permissionsRequested2.get(1))); + assertFalse(project.hasAllPermissions(PERMISSIONS_REQUESTED)); + assertTrue(project.hasAllPermissions(permissionsRequestAllOwned)); } } diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java index 1f66017312a6..3879a38457fc 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java @@ -17,26 +17,36 @@ package com.google.gcloud.resourcemanager; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import org.junit.Test; public class ResourceIdTest { -private static final ResourceId RESOURCE_ID = ResourceId.of("id", ResourceId.Type.ORGANIZATION); + private static final String ID = "id"; + private static final ResourceId.Type TYPE = ResourceId.Type.ORGANIZATION; + private static final ResourceId RESOURCE_ID = ResourceId.of(ID, TYPE); @Test public void testOf() { - assertEquals(RESOURCE_ID.id(), "id"); - assertEquals(RESOURCE_ID.type(), ResourceId.Type.ORGANIZATION); + assertEquals(ID, RESOURCE_ID.id()); + assertEquals(TYPE, RESOURCE_ID.type()); } @Test public void testEquals() { - assertEquals(RESOURCE_ID, ResourceId.of("id", ResourceId.Type.ORGANIZATION)); + assertEquals(RESOURCE_ID, ResourceId.of(ID, TYPE)); + assertEquals(ID, RESOURCE_ID.id()); + assertEquals(TYPE, RESOURCE_ID.type()); + assertNotEquals(ResourceId.of("another-ID", TYPE), RESOURCE_ID); + assertNotEquals(ResourceId.of(ID, ResourceId.Type.UNKNOWN), RESOURCE_ID); } @Test public void testToAndFromPb() { - assertEquals(RESOURCE_ID, ResourceId.fromPb(RESOURCE_ID.toPb())); + ResourceId copy = ResourceId.fromPb(RESOURCE_ID.toPb()); + assertEquals(RESOURCE_ID, copy); + assertEquals(ID, copy.id()); + assertEquals(TYPE, copy.type()); } } 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 index e848c3a61680..72c8de7e9527 100644 --- 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 @@ -19,6 +19,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.gcloud.AuthCredentials; import com.google.gcloud.PageImpl; import com.google.gcloud.RetryParams; @@ -34,36 +36,27 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class SerializationTest { private static final ResourceId RESOURCE_ID = ResourceId.of("some id", ResourceId.Type.ORGANIZATION); - private static final Binding OWNER_BINDING; - private static final Binding EDITOR_BINDING; - private static final Binding VIEWER_BINDING; - private static final Binding EMPTY_BINDING = Policy.Binding.builder().build(); - static { - List ownerMemberList = new ArrayList<>(); - List editorMemberList = new ArrayList<>(); - List viewerMemberList = new ArrayList<>(); - ownerMemberList.add(Member.user("first-owner@email.com")); - ownerMemberList.add(Member.group("group-of-owners@email.com")); - OWNER_BINDING = Policy.Binding.builder().role(RoleType.OWNER).members(ownerMemberList).build(); - editorMemberList.add(Member.serviceAccount("editor@someemail.com")); - EDITOR_BINDING = - Policy.Binding.builder().role(RoleType.EDITOR).members(editorMemberList).build(); - viewerMemberList.add(Member.serviceAccount("app@someemail.com")); - viewerMemberList.add(Member.user("viewer@email.com")); - VIEWER_BINDING = - Policy.Binding.builder().role(RoleType.VIEWER).members(viewerMemberList).build(); - } - private static final Policy POLICY = + private static final List OWNER_MEMBER_LIST = ImmutableList.of( + Member.user("first-owner@email.com"), Member.group("group-of-owners@email.com")); + private static final List EDITOR_MEMBER_LIST = + ImmutableList.of(Member.serviceAccount("editor@someemail.com")); + private static final List VIEWER_MEMBER_LIST = + ImmutableList.of(Member.serviceAccount("app@someemail.com"), Member.user("viewer@email.com")); + private static final Binding OWNER_BINDING = + Policy.Binding.builder().role(RoleType.OWNER).members(OWNER_MEMBER_LIST).build(); + private static final Binding EDITOR_BINDING = + Policy.Binding.builder().role(RoleType.EDITOR).members(EDITOR_MEMBER_LIST).build(); + private static final Binding VIEWER_BINDING = + Policy.Binding.builder().role(RoleType.VIEWER).members(VIEWER_MEMBER_LIST).build(); + private static final Policy EMPTY_POLICY = Policy.builder().build(); + private static final Policy FULL_POLICY = Policy.builder() .addBinding(OWNER_BINDING) .addBinding(EDITOR_BINDING) @@ -71,17 +64,18 @@ public class SerializationTest { .version(1) .etag("some-etag-value") .build(); - private static final Policy EMPTY_POLICY = Policy.builder().build(); - private static final ProjectInfo PROJECT_INFO1 = ProjectInfo.builder("id1").build(); - private static final ProjectInfo PROJECT_INFO2; - static { - Map labels = new HashMap(); - labels.put("key", "value"); - PROJECT_INFO2 = - new ProjectInfo("name", "id", labels, 123L, ProjectInfo.State.ACTIVE, 1234L, RESOURCE_ID); - } + 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")) + .number(123L) + .state(ProjectInfo.State.ACTIVE) + .createTimeMillis(1234L) + .parent(RESOURCE_ID) + .build(); private static final PageImpl PAGE_RESULT = - new PageImpl<>(null, "c", Collections.singletonList(PROJECT_INFO1)); + new PageImpl<>(null, "c", Collections.singletonList(PARTIAL_PROJECT_INFO)); @Test public void testServiceOptions() throws Exception { @@ -101,8 +95,8 @@ public void testServiceOptions() throws Exception { @Test public void testModelAndRequests() throws Exception { Serializable[] objects = {RESOURCE_ID, OWNER_BINDING.members().get(0), OWNER_BINDING, - EDITOR_BINDING, VIEWER_BINDING, EMPTY_BINDING, POLICY, EMPTY_POLICY, PROJECT_INFO1, - PROJECT_INFO2, PAGE_RESULT}; + EDITOR_BINDING, VIEWER_BINDING, EMPTY_POLICY, FULL_POLICY, PARTIAL_PROJECT_INFO, + FULL_PROJECT_INFO, PAGE_RESULT}; for (Serializable obj : objects) { Object copy = serializeAndDeserialize(obj); assertEquals(obj, obj); From e625c20f67ee7b096f944ed744d95ffba6ad3f6d Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Fri, 20 Nov 2015 10:41:07 -0800 Subject: [PATCH 09/35] Add documentation and make resource ID type string --- .../gcloud/resourcemanager/ProjectInfo.java | 94 ++++++++++++++++++- .../gcloud/resourcemanager/ResourceId.java | 15 +-- .../resourcemanager/ProjectInfoTest.java | 2 +- .../gcloud/resourcemanager/ProjectTest.java | 2 +- .../resourcemanager/ResourceIdTest.java | 3 +- .../resourcemanager/SerializationTest.java | 3 +- 6 files changed, 101 insertions(+), 18 deletions(-) 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 index 4e3aed325db7..b5913b763ea0 100644 --- 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 @@ -41,11 +41,24 @@ public class ProjectInfo implements Serializable { private final Long createTimeMillis; private final ResourceId parent; + /** + * The project lifecycle state. + * + *
    + *
  • LIFECYCLE_STATE_UNSPECIFIED: only used/useful for distinguishing unset values + *
  • ACTIVE: the normal and active state + *
  • DELETE_REQUESTED: 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_IN_PROGRESS: the process of deleting the project has begun. Reversing the deletion + * is no longer possible. + *
      + */ public enum State { LIFECYCLE_STATE_UNSPECIFIED, ACTIVE, DELETE_REQUESTED, - DELETE_IN_PROGRESS; + DELETE_IN_PROGRESS } public static class Builder { @@ -58,34 +71,68 @@ public static class Builder { private ResourceId parent; Builder() { - labels = new HashMap(); + labels = new HashMap<>(); } + /** + * 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 = name; 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 id(String id) { this.id = id; 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; @@ -106,6 +153,15 @@ Builder createTimeMillis(Long createTimeMillis) { return this; } + /** + * Set the parent of the project. + * + * If this field is left unset in a project creation request, the server will set this field by + * default to the creator of the project. The parent cannot be changed after the server creates + * the project. When calling {@link ResourceManager#replace}, be sure to set the parent of the + * new ProjectInfo instance. Leaving the parent unset or setting it to null in a replace request + * will cause an error. + */ public Builder parent(ResourceId parent) { this.parent = parent; return this; @@ -126,30 +182,64 @@ public ProjectInfo build() { 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 id() { return id; } + /** + * 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 name; } + /** + * Get number uniquely identifying the project. + * + * This field is set by the server and is read-only. + */ public Long number() { return number; } + /** + * 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; } + /** + * Get the project's creation time (in milliseconds). + * + * This field is set by the server and is read-only. + */ public Long createTimeMillis() { return createTimeMillis; } + /** + * Get the parent of the project. + * + * The parent cannot be changed after the server creates the project. + */ public ResourceId parent() { return parent; } diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java index 6e6022460e04..f2907b9dca6a 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java @@ -26,14 +26,9 @@ public class ResourceId implements Serializable { private static final long serialVersionUID = 7928469304338358885L; private final String id; - private final Type type; + private final String type; - public enum Type { - ORGANIZATION, - UNKNOWN; - } - - private ResourceId(String id, Type type) { + private ResourceId(String id, String type) { this.id = checkNotNull(id); this.type = checkNotNull(type); } @@ -42,7 +37,7 @@ public String id() { return id; } - public Type type() { + public String type() { return type; } @@ -56,7 +51,7 @@ public int hashCode() { return Objects.hash(id, type); } - public static ResourceId of(String id, Type type) { + public static ResourceId of(String id, String type) { return new ResourceId(id, type); } @@ -70,6 +65,6 @@ com.google.api.services.cloudresourcemanager.model.ResourceId toPb() { static ResourceId fromPb( com.google.api.services.cloudresourcemanager.model.ResourceId resourceIdPb) { - return new ResourceId(resourceIdPb.getId(), Type.valueOf(resourceIdPb.getType().toUpperCase())); + return new ResourceId(resourceIdPb.getId(), resourceIdPb.getType()); } } 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 index c0bfb73af3fd..ef4d213721de 100644 --- 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 @@ -34,7 +34,7 @@ public class ProjectInfoTest { private static final Long NUMBER = 123L; private static final Long CREATE_TIME_MILLIS = 123456789L; private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; - private static final ResourceId PARENT = ResourceId.of("owner-id", ResourceId.Type.ORGANIZATION); + private static final ResourceId PARENT = ResourceId.of("owner-id", "organization"); private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder(ID) .name(NAME) 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 index a4388d8a725e..99bc534d952c 100644 --- 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 @@ -47,7 +47,7 @@ public class ProjectTest { private static final Long NUMBER = 123L; private static final Long CREATE_TIME_MILLIS = 123456789L; private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; - private static final ResourceId PARENT = ResourceId.of("owner-id", ResourceId.Type.ORGANIZATION); + private static final ResourceId PARENT = ResourceId.of("owner-id", "organization"); private static final ProjectInfo PROJECT_INFO = ProjectInfo.builder(ID) .name(NAME) diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java index 3879a38457fc..9fed10759931 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java @@ -24,7 +24,7 @@ public class ResourceIdTest { private static final String ID = "id"; - private static final ResourceId.Type TYPE = ResourceId.Type.ORGANIZATION; + private static final String TYPE = "organization"; private static final ResourceId RESOURCE_ID = ResourceId.of(ID, TYPE); @Test @@ -39,7 +39,6 @@ public void testEquals() { assertEquals(ID, RESOURCE_ID.id()); assertEquals(TYPE, RESOURCE_ID.type()); assertNotEquals(ResourceId.of("another-ID", TYPE), RESOURCE_ID); - assertNotEquals(ResourceId.of(ID, ResourceId.Type.UNKNOWN), RESOURCE_ID); } @Test 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 index 72c8de7e9527..0d035af52f6f 100644 --- 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 @@ -41,8 +41,7 @@ public class SerializationTest { - private static final ResourceId RESOURCE_ID = - ResourceId.of("some id", ResourceId.Type.ORGANIZATION); + private static final ResourceId RESOURCE_ID = ResourceId.of("some id", "organization"); private static final List OWNER_MEMBER_LIST = ImmutableList.of( Member.user("first-owner@email.com"), Member.group("group-of-owners@email.com")); private static final List EDITOR_MEMBER_LIST = From ebcac0b12c00bfb26e12ff87bb1f0ec722301728 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 23 Nov 2015 17:51:41 -0800 Subject: [PATCH 10/35] Remove Policy and add docs --- .../google/gcloud/resourcemanager/Policy.java | 367 ------------------ .../gcloud/resourcemanager/Project.java | 111 +++--- .../resourcemanager/ResourceManager.java | 135 +++++-- .../google/gcloud/spi/ResourceManagerRpc.java | 72 ++-- .../gcloud/resourcemanager/PolicyTest.java | 117 ------ .../gcloud/resourcemanager/ProjectTest.java | 73 +--- .../resourcemanager/SerializationTest.java | 30 +- 7 files changed, 179 insertions(+), 726 deletions(-) delete mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java delete mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java deleted file mode 100644 index efa95d9f7c73..000000000000 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * 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.collect.ImmutableList; -import com.google.common.collect.Lists; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * A Google Cloud IAM Policy object - */ -public class Policy implements Serializable { - - private static final long serialVersionUID = 3493286111316914094L; - private final List bindings; - private final Integer version; - private final String etag; - - public static enum MemberType { - ALL_USERS("allUsers"), - ALL_AUTHENTICATED_USERS("allAuthenticatedUsers"), - USER("user:"), - SERVICE_ACCOUNT("serviceAccount:"), - GROUP("group:"), - DOMAIN("domain:"); - - private final String prefix; - - MemberType(String prefix) { - this.prefix = prefix; - } - - String prefix() { - return prefix; - } - } - - public enum RoleType { - OWNER, - EDITOR, - VIEWER; - } - - /** - * Represents a member belonging to an IAM policy binding - */ - public static final class Member implements Serializable { - - private static final long serialVersionUID = 6496912037577986137L; - private final MemberType memberType; - private final String emailOrDomain; - - Member(MemberType memberType, String emailOrDomain) { - this.memberType = memberType; - this.emailOrDomain = emailOrDomain; - } - - public static Member allUsers() { - throw new UnsupportedOperationException( - "Google Cloud Resource Manager does not support the \"all users\" member type yet."); - // return new Member(MemberType.ALL_USERS, null); - } - - public static Member allAuthenticatedUsers() { - throw new UnsupportedOperationException("Google Cloud Resource Manager does not support the " - + "\"all authenticated users\" member type yet."); - // return new Member(MemberType.ALL_AUTHENTICATED_USERS, null); - } - - public static Member user(String email) { - return new Member(MemberType.USER, email); - } - - public static Member serviceAccount(String email) { - return new Member(MemberType.SERVICE_ACCOUNT, email); - } - - public static Member group(String email) { - return new Member(MemberType.GROUP, email); - } - - public static Member domain(String domain) { - throw new UnsupportedOperationException( - "Google Cloud Resource Manager does not support domain members yet."); - // return new Member(MemberType.DOMAIN, domain); - } - - public MemberType type() { - return memberType; - } - - public String emailOrDomain() { - return emailOrDomain; - } - - @Override - public int hashCode() { - return Objects.hash(memberType, emailOrDomain); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof Member && Objects.equals(this.memberType, ((Member) obj).memberType) - && Objects.equals(this.emailOrDomain, ((Member) obj).emailOrDomain); - } - - String toPb() { - return emailOrDomain != null ? memberType.prefix() + emailOrDomain : memberType.prefix(); - } - - static Member fromPb(String memberPb) { - String[] memberInfo = memberPb.split(":", 2); - String memberStr = memberInfo[0]; - String emailOrDomain = (memberInfo.length > 1) ? memberInfo[1] : null; - switch (memberStr) { - case "allUsers": - return new Member(MemberType.ALL_USERS, null); - case "allAuthenticatedUsers": - return new Member(MemberType.ALL_AUTHENTICATED_USERS, null); - case "user": - return new Member(MemberType.USER, checkNotNull(emailOrDomain)); - case "serviceAccount": - return new Member(MemberType.SERVICE_ACCOUNT, checkNotNull(emailOrDomain)); - case "group": - return new Member(MemberType.GROUP, checkNotNull(emailOrDomain)); - case "domain": - return new Member(MemberType.DOMAIN, checkNotNull(emailOrDomain)); - default: - throw new UnsupportedOperationException("Unsupported member type: " + memberStr); - } - } - } - - /** - * Represents an IAM policy binding - */ - public static class Binding implements Serializable { - - private static final long serialVersionUID = -8493421092718338925L; - private final RoleType role; - private final List members; - - public static class Builder { - private RoleType role; - private List members; - - Builder() { - members = new ArrayList(); - } - - public Builder role(RoleType role) { - this.role = role; - return this; - } - - public Builder members(List members) { - this.members = Lists.newArrayList(checkNotNull(members)); - return this; - } - - public Builder clearMembers() { - this.members = new ArrayList<>(); - return this; - } - - public Builder addMember(Member member) { - this.members.add(member); - return this; - } - - public Builder removeMember(Member member) { - this.members.remove(member); - return this; - } - - public Binding build() { - return new Binding(role, members); - } - } - - private Binding(RoleType role, List members) { - this.role = checkNotNull(role); - this.members = ImmutableList.copyOf(members); - } - - public static Binding of(RoleType role, List members) { - return new Binding(role, members); - } - - public RoleType role() { - return role; - } - - public List members() { - return members; - } - - public static Builder builder() { - return new Builder(); - } - - public Builder toBuilder() { - return new Builder().role(role).members(members); - } - - com.google.api.services.cloudresourcemanager.model.Binding toPb() { - com.google.api.services.cloudresourcemanager.model.Binding bindingPb = - new com.google.api.services.cloudresourcemanager.model.Binding(); - bindingPb.setRole("roles/" + role.toString().toLowerCase()); - List membersPb = new ArrayList<>(members.size()); - for (Member member : members) { - membersPb.add(member.toPb()); - } - bindingPb.setMembers(membersPb); - return bindingPb; - } - - static Binding fromPb(com.google.api.services.cloudresourcemanager.model.Binding bindingPb) { - RoleType role = - (bindingPb.getRole() == null) - ? null : RoleType.valueOf(bindingPb.getRole().split("/")[1].toUpperCase()); - List members = new ArrayList<>(); - if (bindingPb.getMembers() != null) { - for (String memberPb : bindingPb.getMembers()) { - members.add(Member.fromPb(memberPb)); - } - } - return new Binding(role, members); - } - - @Override - public int hashCode() { - return Objects.hash(role, members); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof Binding && Objects.equals(toPb(), ((Binding) obj).toPb()); - } - } - - public static final class Builder { - private List bindings; - private String etag; - private Integer version; - - private Builder() { - bindings = new ArrayList<>(); - } - - public Builder addBinding(Binding binding) { - this.bindings.add(binding); - return this; - } - - public Builder removeBinding(Binding binding) { - this.bindings.remove(binding); - return this; - } - - public Builder clearBindings() { - this.bindings = new ArrayList<>(); - return this; - } - - public Builder bindings(List bindings) { - this.bindings = new ArrayList<>(checkNotNull(bindings)); - return this; - } - - public Builder etag(String etag) { - this.etag = etag; - return this; - } - - public Builder version(Integer version) { - this.version = version; - return this; - } - - public Policy build() { - return new Policy(this); - } - } - - Policy(Builder builder) { - bindings = ImmutableList.copyOf(builder.bindings); - version = builder.version; - etag = builder.etag; - } - - public List bindings() { - return bindings; - } - - public Integer version() { - return version; - } - - public String etag() { - return etag; - } - - public static Builder builder() { - return new Builder(); - } - - public Builder toBuilder() { - return new Builder().bindings(bindings).etag(etag).version(version); - } - - @Override - public int hashCode() { - return Objects.hash(bindings, etag, version); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof Policy && Objects.equals(toPb(), ((Policy) obj).toPb()); - } - - com.google.api.services.cloudresourcemanager.model.Policy toPb() { - com.google.api.services.cloudresourcemanager.model.Policy policyPb = - new com.google.api.services.cloudresourcemanager.model.Policy(); - List bindingsPb = new ArrayList<>(); - for (Binding binding : bindings) { - bindingsPb.add(binding.toPb()); - } - policyPb.setBindings(bindingsPb); - policyPb.setVersion(version); - policyPb.setEtag(etag); - return policyPb; - } - - static Policy fromPb(com.google.api.services.cloudresourcemanager.model.Policy policyPb) { - Builder policyBuilder = Policy.builder(); - if (policyPb.getBindings() != null) { - for (com.google.api.services.cloudresourcemanager.model.Binding bindingPb : - policyPb.getBindings()) { - policyBuilder.addBinding(Binding.fromPb(bindingPb)); - } - } - policyBuilder.version(policyPb.getVersion()); - policyBuilder.etag(policyPb.getEtag()); - return policyBuilder.build(); - } -} 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 index 74296a0be773..5f17bd48336e 100644 --- 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 @@ -18,10 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.gcloud.spi.ResourceManagerRpc.Permission; - -import java.util.List; - /** * A Google Cloud Resource Manager project object. * @@ -32,57 +28,70 @@ public class Project { private final ResourceManager resourceManager; private final ProjectInfo info; - private final Policy policy; /** - * Constructs a Project object that contains the ProjectInfo and Policy given. + * Constructs a Project object that contains the ProjectInfo given. */ - public Project(ResourceManager resourceManager, ProjectInfo projectInfo, Policy policy) { + public Project(ResourceManager resourceManager, ProjectInfo projectInfo) { this.resourceManager = checkNotNull(resourceManager); this.info = checkNotNull(projectInfo); - this.policy = checkNotNull(policy); } /** - * Constructs a Project object that contains project and policy information loaded from the - * server. + * Constructs a Project object that contains project information loaded from the server. * - * @return Project object containing the project's metadata and IAM policy + * @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); - Policy policy = resourceManager.getIamPolicy(projectId); - return new Project(resourceManager, projectInfo, policy); + return new Project(resourceManager, projectInfo); } + /** + * Returns the {@link ProjectInfo} object associated with this Project. + */ public ProjectInfo info() { return info; } - public Policy policy() { - return policy; - } - + /** + * Returns the {@link ResourceManager} service object associated with this Project. + */ public ResourceManager resourceManager() { return resourceManager; } /** - * Returns a Project object with updated project and policy information. + * Returns a Project object with updated project information. * - * @return Project object containing the project's updated metadata and IAM policy + * @return Project object containing the project's updated metadata * @throws ResourceManagerException upon failure */ public Project reload() { - return new Project( - resourceManager, resourceManager.get(info.id()), resourceManager.getIamPolicy(info.id())); + return new Project(resourceManager, resourceManager.get(info.id())); } /** - * Requests that this project be deleted. For an unspecified amount of time, this action can be - * undone by calling {@link #undelete}. + * 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 */ public void delete() { @@ -90,54 +99,34 @@ public void delete() { } /** - * Requests that a project's lifecycle status be changed from {@code DELETE_REQUESTED} to - * {@code ACTIVE}. + * Restores the project identified by the specified project ID. * - * @throws ResourceManagerException upon failure + * 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 */ public void undelete() { resourceManager.undelete(info.id()); } /** - * Replaces the project metadata (not including the IAM policy) using the given ProjectInfo. - * - * @return Project object containing the project's updated metadata - * @throws ResourceManagerException upon failure - */ - public Project replace(ProjectInfo projectInfo) { - return new Project(resourceManager, resourceManager.replace(checkNotNull(projectInfo)), policy); - } - - /** - * Replaces the project's IAM policy using the given policy. - * - * @return Project object containing the project's updated IAM policy - * @throws ResourceManagerException upon failure - */ - public Project replaceIamPolicy(Policy policy) { - return new Project( - resourceManager, info, resourceManager.replaceIamPolicy(info.id(), checkNotNull(policy))); - } - - /** - * Returns whether the caller has the permissions specified in the parameters. + * Replaces the attributes of the project. * - * @return List of booleans representing whether the user has the corresponding permission - * provided as a parameter - * @throws ResourceManagerException upon failure - */ - public List hasPermissions(Permission... permissions) { - return resourceManager.hasPermissions(info.id(), permissions); - } - - /** - * Returns whether the caller has all the permissions specified in the parameters. + * The caller must have modify permissions for this project. * - * @return true if the caller has all the permissions specified, otherwise false. + * @see + * + * Cloud Resource Manager update + * @return the ProjectInfo representing the new project metadata * @throws ResourceManagerException upon failure */ - public boolean hasAllPermissions(Permission... permissions) { - return !(hasPermissions(permissions).contains(false)); + 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/ResourceManager.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java index ca6a1cb24b27..2f999b7ca4e6 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,11 +16,13 @@ package com.google.gcloud.resourcemanager; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; import com.google.gcloud.Page; import com.google.gcloud.Service; -import com.google.gcloud.spi.ResourceManagerRpc.ListOptions; -import com.google.gcloud.spi.ResourceManagerRpc.Permission; +import java.util.Collections; import java.util.List; /** @@ -32,79 +34,134 @@ public interface ResourceManager extends Service { public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; + 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; + } + } + /** * 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 additional read-only information, namely project number, lifecycle state, and - * creation time. + * 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); /** - * Sends a request to delete a project. For an unspecified amount of time, this action can be - * undone using {@link #undelete}. + * 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); /** - * Return the requested project or {@code null} if not found. + * 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); /** - * List the projects viewable by the current user. Use {@link ListOptions} to filter this list, - * set page size, and set page tokens. Note that pagination is currently not implemented by the - * Cloud Resource Manager API. + * 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 ListOptions} to filter this list, set page size, and set page + * tokens. Note that pagination is currently not implemented by the Cloud Resource Manager API. * - * @return {@code Page}, a paginated list of projects. + * @see + * Cloud Resource Manager list + * @return {@code Page}, a page of projects. * @throws ResourceManagerException upon failure */ Page list(ListOptions listOptions); /** - * Replace project metadata. + * 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); /** - * Undo a delete request. This will only succeed if the server processes the undelete request - * while the project's state is {@code DELETE_REQUESTED}. + * 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); - - /** - * Get the IAM policy for the project specified. - * - * @return IAM Policy - * @throws ResourceManagerException upon failure - */ - Policy getIamPolicy(String projectId); - - /** - * Replace the IAM Policy for a project with the policy given. - * - * @return the new IAM Policy - * @throws ResourceManagerException upon failure - */ - Policy replaceIamPolicy(String projectId, Policy policy); - - /** - * Test whether the caller of this function has the permissions provided as arguments. - * - * @return List of booleans representing whether the caller has the corresponding permission in - * the given permissions array. - * @throws ResourceManagerException upon failure - */ - List hasPermissions(String projectId, Permission... permissions); } 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 9410891771c9..6f07b53d7df2 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,18 +16,44 @@ 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 { + enum Option { + FILTER("filter"), + PAGE_SIZE("maxResults"), + 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); + } + } + public enum Permission { CREATE("resourcemanager.projects.create"), DELETE("resourcemanager.projects.delete"), @@ -71,49 +97,13 @@ 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; - Tuple> list(ListOptions listOptions) throws ResourceManagerException; + Tuple> list(Map options) throws ResourceManagerException; void undelete(String projectId) throws ResourceManagerException; diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java deleted file mode 100644 index 6b52f20f3fae..000000000000 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 com.google.common.collect.ImmutableList; -import com.google.gcloud.resourcemanager.Policy.Binding; -import com.google.gcloud.resourcemanager.Policy.Member; -import com.google.gcloud.resourcemanager.Policy.RoleType; - -import org.junit.Test; - -import java.util.List; - -public class PolicyTest { - - private static final List OWNER_MEMBER_LIST = ImmutableList.of( - Member.user("first-owner@email.com"), Member.group("group-of-owners@email.com")); - private static final List EDITOR_MEMBER_LIST = - ImmutableList.of(Member.serviceAccount("editor@someemail.com")); - private static final List VIEWER_MEMBER_LIST = - ImmutableList.of(Member.serviceAccount("app@someemail.com"), Member.user("viewer@email.com")); - private static final Binding OWNER_BINDING = - Policy.Binding.builder().role(RoleType.OWNER).members(OWNER_MEMBER_LIST).build(); - private static final Binding EDITOR_BINDING = - Policy.Binding.builder().role(RoleType.EDITOR).members(EDITOR_MEMBER_LIST).build(); - private static final Binding VIEWER_BINDING = - Policy.Binding.builder().role(RoleType.VIEWER).members(VIEWER_MEMBER_LIST).build(); - private static final Policy EMPTY_POLICY = Policy.builder().build(); - private static final Integer VERSION = 1; - private static final String ETAG = "some-etag-value"; - private static final Policy FULL_POLICY = - Policy.builder() - .addBinding(OWNER_BINDING) - .addBinding(EDITOR_BINDING) - .addBinding(VIEWER_BINDING) - .version(VERSION) - .etag(ETAG) - .build(); - - @Test - public void testBindingBuilder() { - assertEquals(RoleType.OWNER, OWNER_BINDING.role()); - assertEquals(OWNER_MEMBER_LIST, OWNER_BINDING.members()); - } - - @Test - public void testBindingToBuilder() { - assertEquals(OWNER_BINDING, OWNER_BINDING.toBuilder().build()); - } - - @Test - public void testBindingToAndFromPb() { - assertEquals(OWNER_BINDING, Binding.fromPb(OWNER_BINDING.toPb())); - assertEquals(EDITOR_BINDING, Binding.fromPb(EDITOR_BINDING.toPb())); - assertEquals(VIEWER_BINDING, Binding.fromPb(VIEWER_BINDING.toPb())); - } - - @Test - public void testPolicyBuilder() { - assertEquals(OWNER_BINDING, FULL_POLICY.bindings().get(0)); - assertEquals(EDITOR_BINDING, FULL_POLICY.bindings().get(1)); - assertEquals(VIEWER_BINDING, FULL_POLICY.bindings().get(2)); - assertEquals(VERSION, FULL_POLICY.version()); - assertEquals(ETAG, FULL_POLICY.etag()); - } - - @Test - public void testPolicyToBuilder() { - comparePolicies(FULL_POLICY, FULL_POLICY.toBuilder().build()); - comparePolicies(EMPTY_POLICY, EMPTY_POLICY.toBuilder().build()); - } - - @Test - public void testPolicyToAndFromPb() { - comparePolicies(FULL_POLICY, Policy.fromPb(FULL_POLICY.toPb())); - comparePolicies(EMPTY_POLICY, Policy.fromPb(EMPTY_POLICY.toPb())); - } - - @Test - public void testEquals() { - comparePolicies( - FULL_POLICY, - Policy.builder() - .addBinding(OWNER_BINDING) - .addBinding(EDITOR_BINDING) - .addBinding(VIEWER_BINDING) - .version(VERSION) - .etag(ETAG) - .build()); - comparePolicies(EMPTY_POLICY, Policy.builder().build()); - assertNotEquals(FULL_POLICY, EMPTY_POLICY); - } - - private void comparePolicies(Policy expected, Policy value) { - assertEquals(expected, value); - assertEquals(expected.bindings(), value.bindings()); - assertEquals(expected.version(), value.version()); - assertEquals(expected.etag(), value.etag()); - } -} 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 index 99bc534d952c..9201448f60f8 100644 --- 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 @@ -22,22 +22,14 @@ import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.gcloud.resourcemanager.Policy.Binding; -import com.google.gcloud.resourcemanager.Policy.Member; -import com.google.gcloud.resourcemanager.Policy.RoleType; -import com.google.gcloud.spi.ResourceManagerRpc.Permission; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.util.List; import java.util.Map; public class ProjectTest { @@ -57,28 +49,6 @@ public class ProjectTest { .state(STATE) .parent(PARENT) .build(); - private static final List OWNER_MEMBER_LIST = ImmutableList.of( - Member.user("first-owner@email.com"), Member.group("group-of-owners@email.com")); - private static final List EDITOR_MEMBER_LIST = - ImmutableList.of(Member.serviceAccount("editor@someemail.com")); - private static final List VIEWER_MEMBER_LIST = - ImmutableList.of(Member.serviceAccount("app@someemail.com"), Member.user("viewer@email.com")); - private static final Binding OWNER_BINDING = - Policy.Binding.builder().role(RoleType.OWNER).members(OWNER_MEMBER_LIST).build(); - private static final Binding EDITOR_BINDING = - Policy.Binding.builder().role(RoleType.EDITOR).members(EDITOR_MEMBER_LIST).build(); - private static final Binding VIEWER_BINDING = - Policy.Binding.builder().role(RoleType.VIEWER).members(VIEWER_MEMBER_LIST).build(); - private static final Policy POLICY = - Policy.builder() - .addBinding(OWNER_BINDING) - .addBinding(EDITOR_BINDING) - .addBinding(VIEWER_BINDING) - .version(1) - .etag("some-etag-value") - .build(); - private static final Permission[] PERMISSIONS_REQUESTED = {Permission.REPLACE, Permission.GET}; - private static final List PERMISSIONS_OWNED = ImmutableList.of(false, true); private ResourceManager resourceManager; private Project project; @@ -86,7 +56,7 @@ public class ProjectTest { @Before public void setUp() throws Exception { resourceManager = createStrictMock(ResourceManager.class); - project = new Project(resourceManager, PROJECT_INFO, POLICY); + project = new Project(resourceManager, PROJECT_INFO); } @After @@ -97,30 +67,24 @@ public void tearDown() throws Exception { @Test public void testLoad() { expect(resourceManager.get(PROJECT_INFO.id())).andReturn(PROJECT_INFO); - expect(resourceManager.getIamPolicy(PROJECT_INFO.id())).andReturn(POLICY); replay(resourceManager); Project loadedProject = Project.load(resourceManager, PROJECT_INFO.id()); assertEquals(PROJECT_INFO, loadedProject.info()); - assertEquals(POLICY, loadedProject.policy()); } @Test public void testReload() { ProjectInfo newInfo = PROJECT_INFO.toBuilder().addLabel("k3", "v3").build(); - Policy newPolicy = POLICY.toBuilder().removeBinding(VIEWER_BINDING).build(); expect(resourceManager.get(PROJECT_INFO.id())).andReturn(newInfo); - expect(resourceManager.getIamPolicy(PROJECT_INFO.id())).andReturn(newPolicy); replay(resourceManager); Project newProject = project.reload(); assertSame(resourceManager, newProject.resourceManager()); assertEquals(newInfo, newProject.info()); - assertEquals(newPolicy, newProject.policy()); } @Test public void testPolicy() { replay(resourceManager); - assertEquals(POLICY, project.policy()); } @Test @@ -159,40 +123,5 @@ public void testReplace() { Project newProject = project.replace(newInfo); assertSame(resourceManager, newProject.resourceManager()); assertEquals(newInfo, newProject.info()); - assertEquals(POLICY, newProject.policy()); - } - - @Test - public void testReplaceIamPolicy() { - Policy newPolicy = POLICY.toBuilder().removeBinding(VIEWER_BINDING).build(); - expect(resourceManager.replaceIamPolicy(PROJECT_INFO.id(), newPolicy)).andReturn(newPolicy); - replay(resourceManager); - Project newProject = project.replaceIamPolicy(newPolicy); - assertSame(resourceManager, newProject.resourceManager()); - assertEquals(PROJECT_INFO, newProject.info()); - assertEquals(newPolicy, newProject.policy()); - } - - @Test - public void testHasPermissions() { - expect(resourceManager.hasPermissions(PROJECT_INFO.id(), PERMISSIONS_REQUESTED)) - .andReturn(PERMISSIONS_OWNED); - replay(resourceManager); - List response = - project.hasPermissions(PERMISSIONS_REQUESTED[0], PERMISSIONS_REQUESTED[1]); - assertEquals(PERMISSIONS_OWNED, response); - } - - @Test - public void testHasAllPermissions() { - expect(resourceManager.hasPermissions(PROJECT_INFO.id(), PERMISSIONS_REQUESTED)) - .andReturn(PERMISSIONS_OWNED); - Permission[] permissionsRequestAllOwned = {Permission.UNDELETE, Permission.DELETE}; - List permissionsResponseAllOwned = ImmutableList.of(true, true); - expect(resourceManager.hasPermissions(PROJECT_INFO.id(), permissionsRequestAllOwned)) - .andReturn(permissionsResponseAllOwned); - replay(resourceManager); - assertFalse(project.hasAllPermissions(PERMISSIONS_REQUESTED)); - assertTrue(project.hasAllPermissions(permissionsRequestAllOwned)); } } 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 index 0d035af52f6f..0929ea18c94a 100644 --- 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 @@ -19,14 +19,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.gcloud.AuthCredentials; import com.google.gcloud.PageImpl; import com.google.gcloud.RetryParams; -import com.google.gcloud.resourcemanager.Policy.Binding; -import com.google.gcloud.resourcemanager.Policy.Member; -import com.google.gcloud.resourcemanager.Policy.RoleType; import org.junit.Test; @@ -37,32 +33,10 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collections; -import java.util.List; public class SerializationTest { private static final ResourceId RESOURCE_ID = ResourceId.of("some id", "organization"); - private static final List OWNER_MEMBER_LIST = ImmutableList.of( - Member.user("first-owner@email.com"), Member.group("group-of-owners@email.com")); - private static final List EDITOR_MEMBER_LIST = - ImmutableList.of(Member.serviceAccount("editor@someemail.com")); - private static final List VIEWER_MEMBER_LIST = - ImmutableList.of(Member.serviceAccount("app@someemail.com"), Member.user("viewer@email.com")); - private static final Binding OWNER_BINDING = - Policy.Binding.builder().role(RoleType.OWNER).members(OWNER_MEMBER_LIST).build(); - private static final Binding EDITOR_BINDING = - Policy.Binding.builder().role(RoleType.EDITOR).members(EDITOR_MEMBER_LIST).build(); - private static final Binding VIEWER_BINDING = - Policy.Binding.builder().role(RoleType.VIEWER).members(VIEWER_MEMBER_LIST).build(); - private static final Policy EMPTY_POLICY = Policy.builder().build(); - private static final Policy FULL_POLICY = - Policy.builder() - .addBinding(OWNER_BINDING) - .addBinding(EDITOR_BINDING) - .addBinding(VIEWER_BINDING) - .version(1) - .etag("some-etag-value") - .build(); private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder("id1").build(); private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder("id") @@ -93,9 +67,7 @@ public void testServiceOptions() throws Exception { @Test public void testModelAndRequests() throws Exception { - Serializable[] objects = {RESOURCE_ID, OWNER_BINDING.members().get(0), OWNER_BINDING, - EDITOR_BINDING, VIEWER_BINDING, EMPTY_POLICY, FULL_POLICY, PARTIAL_PROJECT_INFO, - FULL_PROJECT_INFO, PAGE_RESULT}; + Serializable[] objects = {RESOURCE_ID, PARTIAL_PROJECT_INFO, FULL_PROJECT_INFO, PAGE_RESULT}; for (Serializable obj : objects) { Object copy = serializeAndDeserialize(obj); assertEquals(obj, obj); From d800d1fffb7745b46d8cad931f2eaa3bc45c9173 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Tue, 24 Nov 2015 17:21:09 -0800 Subject: [PATCH 11/35] Added docs, removed policy-related methods from spi layer, fixed list options --- .../google/gcloud/resourcemanager/Option.java | 73 +++++++++++++++ .../gcloud/resourcemanager/Project.java | 2 +- .../gcloud/resourcemanager/ProjectInfo.java | 40 ++++---- .../gcloud/resourcemanager/ResourceId.java | 22 ++++- .../resourcemanager/ResourceManager.java | 91 ++++++++++++------- .../google/gcloud/spi/ResourceManagerRpc.java | 30 ------ 6 files changed, 174 insertions(+), 84 deletions(-) create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Option.java 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..9900861b93cc --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Option.java @@ -0,0 +1,73 @@ +/* + * 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 + */ +public 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 index 5f17bd48336e..b9abdabc9f87 100644 --- 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 @@ -109,7 +109,7 @@ public void delete() { * @see * Cloud Resource Manager undelete - * @throws ResourceManagerException + * @throws ResourceManagerException upon failure (including when the project can't be restored) */ public void undelete() { resourceManager.undelete(info.id()); 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 index b5913b763ea0..8dbc41d691a4 100644 --- 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 @@ -19,7 +19,6 @@ 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; @@ -45,19 +44,32 @@ public class ProjectInfo implements Serializable { * The project lifecycle state. * *
        - *
      • LIFECYCLE_STATE_UNSPECIFIED: only used/useful for distinguishing unset values - *
      • ACTIVE: the normal and active state - *
      • DELETE_REQUESTED: 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_IN_PROGRESS: the process of deleting the project has begun. Reversing the deletion - * is no longer possible. + *
      • LIFECYCLE_STATE_UNSPECIFIED: + *
      • ACTIVE: + *
      • DELETE_REQUESTED: + *
      • DELETE_IN_PROGRESS: *
          */ 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 } @@ -289,19 +301,11 @@ com.google.api.services.cloudresourcemanager.model.Project toPb() { } static ProjectInfo fromPb(com.google.api.services.cloudresourcemanager.model.Project projectPb) { - ProjectInfo.Builder builder = - ProjectInfo.builder(projectPb.getProjectId()) - .name(projectPb.getName()) - .number(projectPb.getProjectNumber()); + ProjectInfo.Builder builder = + ProjectInfo.builder(projectPb.getProjectId()).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())); } diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java index f2907b9dca6a..77d0cfe58866 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java @@ -20,7 +20,11 @@ import java.util.Objects; /** - * Represents a Google Cloud Resource Manager Resource ID + * Represents a Google Cloud Resource Manager Resource ID. + * + * A 'resource' in Google Cloud Platform is a generic term for something you (a developer) may want + * to interact with through one of our API's. Some examples are an AppEngine app, a Compute Engine + * instance, and Cloud SQL database. */ public class ResourceId implements Serializable { @@ -33,10 +37,20 @@ private ResourceId(String id, String type) { this.type = checkNotNull(type); } + /** + * Required field for the type-specific ID. + * + * This should correspond to the ID used in the type-specific APIs. + */ public String id() { return id; } + /** + * Required field representing the resource type this ID is for. + * + * At present, the only valid type is "organization". + */ public String type() { return type; } @@ -51,6 +65,12 @@ public int hashCode() { return Objects.hash(id, type); } + /** + * Create a new Resource ID with the given ID and resource type. + * + * The ID should correspond to the ID used in the type-specific APIs. At present, the only valid + * type is "organization". + */ public static ResourceId of(String id, String type) { return new ResourceId(id, type); } 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 2f999b7ca4e6..a0405ef5117e 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,14 +16,9 @@ package com.google.gcloud.resourcemanager; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.collect.ImmutableList; import com.google.gcloud.Page; import com.google.gcloud.Service; - -import java.util.Collections; -import java.util.List; +import com.google.gcloud.spi.ResourceManagerRpc; /** * An interface for Google Cloud Resource Manager. @@ -34,39 +29,66 @@ public interface ResourceManager extends Service { public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; - 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; - } + /** + * Class for specifying project list options. + */ + public class ProjectListOption extends Option { - public static ListOptions getDefaultInstance() { - return DEFAULT_INSTANCE; - } + private static final long serialVersionUID = 7888768979702012328L; - public static ListOptions createListOption( - List filters, String pageToken, int pageSize) { - return new ListOptions(filters, pageToken, pageSize); + private ProjectListOption(ResourceManagerRpc.Option option, Object value) { + super(option, value); } - public String pageToken() { - return pageToken; + /** + * Returns an option to specify a page token. + * + * The page token (returned from a previous call to list) indicates from where listing should + * continue. Pagination is not yet supported; the server ignores this field. Optional. + */ + public static ProjectListOption pageToken(String pageToken) { + // return new ProjectListOption(ResourceManagerRpc.Option.PAGE_TOKEN, pageToken); + throw new UnsupportedOperationException("paging for project lists is not implemented yet."); } - public List filters() { - return filters; + /** + * Returns an option to specify a filter. + * + * Filter rules are case insensitive. The fields eligible for filtering are: + *
            + *
          • name + *
          • id + *
          • labels.key, where key is the name of a label + *
          + * + * Some examples of using labels as 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. + *
          + * + * Optional. + */ + public static ProjectListOption filter(String filter) { + return new ProjectListOption(ResourceManagerRpc.Option.FILTER, filter); } - public int pageSize() { - return pageSize; + /** + * The maximum number of projects to return in the response. + * + * The server can return fewer projects than requested. If unspecified, server picks an + * appropriate default. Note: pagination is not yet supported; the server ignores this field. + * Optional. + */ + public static ProjectListOption pageSize(int pageSize) { + // return new ProjectListOption(ResourceManagerRpc.Option.PAGE_SIZE, pageSize); + throw new UnsupportedOperationException("paging for project lists is not implemented yet."); } } @@ -127,15 +149,16 @@ public int pageSize() { * 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 ListOptions} to filter this list, set page size, and set page - * tokens. Note that pagination is currently not implemented by the Cloud Resource Manager API. + * 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(ListOptions listOptions); + Page list(ProjectListOption... options); /** * Replaces the attributes of the project. 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 6f07b53d7df2..854994109e3d 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,11 +16,9 @@ package com.google.gcloud.spi; -import com.google.api.services.cloudresourcemanager.model.Policy; import com.google.api.services.cloudresourcemanager.model.Project; import com.google.gcloud.resourcemanager.ResourceManagerException; -import java.util.List; import java.util.Map; public interface ResourceManagerRpc { @@ -54,27 +52,6 @@ Long getInt(Map options) { } } - public enum Permission { - CREATE("resourcemanager.projects.create"), - DELETE("resourcemanager.projects.delete"), - GET("resourcemanager.projects.get"), - LIST("resourcemanager.projects.list"), - REPLACE("resourcemanager.projects.replace"), - UNDELETE("resourcemanager.projects.undelete"), - GET_IAM_POLICY("resourcemanager.projects.getIamPolicy"), - REPLACE_IAM_POLICY("resourcemanager.projects.setIamPolicy"); - - String permissionPb; - - Permission(String permissionPb) { - this.permissionPb = permissionPb; - } - - String toPb() { - return permissionPb; - } - } - class Tuple { private final X x; private final Y y; @@ -109,12 +86,5 @@ public Y y() { Project replace(Project project) throws ResourceManagerException; - Policy getIamPolicy(String projectId) throws ResourceManagerException; - - Policy replaceIamPolicy(String projectId, Policy policy) throws ResourceManagerException; - - List hasPermissions(String projectId, List permissions) - throws ResourceManagerException; - // TODO(ajaykannan): implement "Organization" functionality when available (issue #319) } From a770e12e2d843f3ae2a417c951fcd98493003aa2 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Wed, 25 Nov 2015 15:42:06 -0800 Subject: [PATCH 12/35] Remove parent and resource ID, add fields options --- .../gcloud/resourcemanager/ProjectInfo.java | 37 +------- .../gcloud/resourcemanager/ResourceId.java | 90 ------------------- .../resourcemanager/ResourceManager.java | 87 ++++++++++++++---- .../google/gcloud/spi/ResourceManagerRpc.java | 3 +- .../resourcemanager/ProjectInfoTest.java | 6 -- .../gcloud/resourcemanager/ProjectTest.java | 2 - .../resourcemanager/ResourceIdTest.java | 51 ----------- .../resourcemanager/SerializationTest.java | 4 +- 8 files changed, 73 insertions(+), 207 deletions(-) delete mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java delete mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java 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 index 8dbc41d691a4..4e45e1274dbf 100644 --- 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 @@ -38,7 +38,6 @@ public class ProjectInfo implements Serializable { private final Long number; private final State state; private final Long createTimeMillis; - private final ResourceId parent; /** * The project lifecycle state. @@ -80,7 +79,6 @@ public static class Builder { private Long number; private State state; private Long createTimeMillis; - private ResourceId parent; Builder() { labels = new HashMap<>(); @@ -165,20 +163,6 @@ Builder createTimeMillis(Long createTimeMillis) { return this; } - /** - * Set the parent of the project. - * - * If this field is left unset in a project creation request, the server will set this field by - * default to the creator of the project. The parent cannot be changed after the server creates - * the project. When calling {@link ResourceManager#replace}, be sure to set the parent of the - * new ProjectInfo instance. Leaving the parent unset or setting it to null in a replace request - * will cause an error. - */ - public Builder parent(ResourceId parent) { - this.parent = parent; - return this; - } - public ProjectInfo build() { return new ProjectInfo(this); } @@ -191,7 +175,6 @@ public ProjectInfo build() { this.number = builder.number; this.state = builder.state; this.createTimeMillis = builder.createTimeMillis; - this.parent = builder.parent; } /** @@ -247,15 +230,6 @@ public Long createTimeMillis() { return createTimeMillis; } - /** - * Get the parent of the project. - * - * The parent cannot be changed after the server creates the project. - */ - public ResourceId parent() { - return parent; - } - @Override public boolean equals(Object obj) { return obj instanceof ProjectInfo && Objects.equals(toPb(), ((ProjectInfo) obj).toPb()); @@ -263,7 +237,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hash(name, id, labels, number, state, createTimeMillis, parent); + return Objects.hash(name, id, labels, number, state, createTimeMillis); } public static Builder builder(String id) { @@ -277,8 +251,7 @@ public Builder toBuilder() { .labels(labels) .number(number) .state(state) - .createTimeMillis(createTimeMillis) - .parent(parent); + .createTimeMillis(createTimeMillis); } com.google.api.services.cloudresourcemanager.model.Project toPb() { @@ -294,9 +267,6 @@ com.google.api.services.cloudresourcemanager.model.Project toPb() { if (createTimeMillis != null) { projectPb.setCreateTime(ISODateTimeFormat.dateTime().print(createTimeMillis)); } - if (parent != null) { - projectPb.setParent(parent.toPb()); - } return projectPb; } @@ -306,9 +276,6 @@ static ProjectInfo fromPb(com.google.api.services.cloudresourcemanager.model.Pro if (projectPb.getLabels() != null) { builder.labels(projectPb.getLabels()); } - 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/ResourceId.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java deleted file mode 100644 index 77d0cfe58866..000000000000 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceId.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 java.io.Serializable; -import java.util.Objects; - -/** - * Represents a Google Cloud Resource Manager Resource ID. - * - * A 'resource' in Google Cloud Platform is a generic term for something you (a developer) may want - * to interact with through one of our API's. Some examples are an AppEngine app, a Compute Engine - * instance, and Cloud SQL database. - */ -public class ResourceId implements Serializable { - - private static final long serialVersionUID = 7928469304338358885L; - private final String id; - private final String type; - - private ResourceId(String id, String type) { - this.id = checkNotNull(id); - this.type = checkNotNull(type); - } - - /** - * Required field for the type-specific ID. - * - * This should correspond to the ID used in the type-specific APIs. - */ - public String id() { - return id; - } - - /** - * Required field representing the resource type this ID is for. - * - * At present, the only valid type is "organization". - */ - public 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); - } - - /** - * Create a new Resource ID with the given ID and resource type. - * - * The ID should correspond to the ID used in the type-specific APIs. At present, the only valid - * type is "organization". - */ - public static ResourceId of(String id, String type) { - return new ResourceId(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()); - } -} 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 a0405ef5117e..46183bfc8188 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,10 +16,14 @@ 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. * @@ -30,25 +34,70 @@ public interface ResourceManager extends Service { public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; /** - * Class for specifying project list options. + * The fields of a project. + * + * These values can be used to specify the fields to include of in a partial response when calling + * {@link ResourceManager#get} or {@link ResourceManager#list}. Project ID is always returned, + * even if not specified. */ - public class ProjectListOption extends Option { + enum ProjectField { + ID("projectId"), + NAME("name"), + LABELS("labels"), + NUMBER("projectNumber"), + STATE("lifecycleState"), + CREATE_TIME("createTime"); + + private final String selector; + + ProjectField(String selector) { + this.selector = selector; + } - private static final long serialVersionUID = 7888768979702012328L; + public String selector() { + return selector; + } - private ProjectListOption(ResourceManagerRpc.Option option, Object value) { + static String selector(ProjectField... fields) { + HashSet fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1); + fieldStrings.add(ID.selector()); + for (ProjectField field : fields) { + fieldStrings.add(field.selector()); + } + return Joiner.on(',').join(fieldStrings); + } + } + + 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 a page token. + * Returns an option to specify the project's fields to be returned by the RPC call. * - * The page token (returned from a previous call to list) indicates from where listing should - * continue. Pagination is not yet supported; the server ignores this field. Optional. + * 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 pageToken(String pageToken) { - // return new ProjectListOption(ResourceManagerRpc.Option.PAGE_TOKEN, pageToken); - throw new UnsupportedOperationException("paging for project lists is not implemented yet."); + 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); } /** @@ -80,15 +129,17 @@ public static ProjectListOption filter(String filter) { } /** - * The maximum number of projects to return in the response. + * Returns an option to specify the project's fields to be returned by the RPC call. * - * The server can return fewer projects than requested. If unspecified, server picks an - * appropriate default. Note: pagination is not yet supported; the server ignores this field. - * Optional. + * 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 pageSize(int pageSize) { - // return new ProjectListOption(ResourceManagerRpc.Option.PAGE_SIZE, pageSize); - throw new UnsupportedOperationException("paging for project lists is not implemented yet."); + 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()); } } @@ -143,7 +194,7 @@ public static ProjectListOption pageSize(int pageSize) { * Cloud Resource Manager get * @throws ResourceManagerException upon failure */ - ProjectInfo get(String projectId); + ProjectInfo get(String projectId, ProjectGetOption... options); /** * Lists the projects visible to the current user. 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 854994109e3d..f7e330639399 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 @@ -25,8 +25,7 @@ public interface ResourceManagerRpc { enum Option { FILTER("filter"), - PAGE_SIZE("maxResults"), - PAGE_TOKEN("pageToken"); + FIELDS("fields"); private final String value; 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 index ef4d213721de..e11962d94273 100644 --- 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 @@ -34,7 +34,6 @@ public class ProjectInfoTest { private static final Long NUMBER = 123L; private static final Long CREATE_TIME_MILLIS = 123456789L; private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; - private static final ResourceId PARENT = ResourceId.of("owner-id", "organization"); private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder(ID) .name(NAME) @@ -42,7 +41,6 @@ public class ProjectInfoTest { .number(NUMBER) .createTimeMillis(CREATE_TIME_MILLIS) .state(STATE) - .parent(PARENT) .build(); private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder(ID).build(); @@ -54,7 +52,6 @@ public void testBuilder() { assertEquals(NUMBER, FULL_PROJECT_INFO.number()); assertEquals(CREATE_TIME_MILLIS, FULL_PROJECT_INFO.createTimeMillis()); assertEquals(STATE, FULL_PROJECT_INFO.state()); - assertEquals(PARENT, FULL_PROJECT_INFO.parent()); assertEquals(ID, PARTIAL_PROJECT_INFO.id()); assertEquals(null, PARTIAL_PROJECT_INFO.name()); @@ -62,7 +59,6 @@ public void testBuilder() { assertEquals(null, PARTIAL_PROJECT_INFO.number()); assertEquals(null, PARTIAL_PROJECT_INFO.createTimeMillis()); assertEquals(null, PARTIAL_PROJECT_INFO.state()); - assertEquals(null, PARTIAL_PROJECT_INFO.parent()); } @Test @@ -87,7 +83,6 @@ public void testEquals() { .number(NUMBER) .createTimeMillis(CREATE_TIME_MILLIS) .state(STATE) - .parent(PARENT) .build()); compareProjects(PARTIAL_PROJECT_INFO, ProjectInfo.builder(ID).build()); assertNotEquals(FULL_PROJECT_INFO, PARTIAL_PROJECT_INFO); @@ -101,7 +96,6 @@ private void compareProjects(ProjectInfo expected, ProjectInfo value) { assertEquals(expected.number(), value.number()); 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 index 9201448f60f8..ddb543752ac7 100644 --- 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 @@ -39,7 +39,6 @@ public class ProjectTest { private static final Long NUMBER = 123L; private static final Long CREATE_TIME_MILLIS = 123456789L; private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; - private static final ResourceId PARENT = ResourceId.of("owner-id", "organization"); private static final ProjectInfo PROJECT_INFO = ProjectInfo.builder(ID) .name(NAME) @@ -47,7 +46,6 @@ public class ProjectTest { .number(NUMBER) .createTimeMillis(CREATE_TIME_MILLIS) .state(STATE) - .parent(PARENT) .build(); private ResourceManager resourceManager; diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java deleted file mode 100644 index 9fed10759931..000000000000 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceIdTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 org.junit.Test; - -public class ResourceIdTest { - - private static final String ID = "id"; - private static final String TYPE = "organization"; - private static final ResourceId RESOURCE_ID = ResourceId.of(ID, TYPE); - - @Test - public void testOf() { - assertEquals(ID, RESOURCE_ID.id()); - assertEquals(TYPE, RESOURCE_ID.type()); - } - - @Test - public void testEquals() { - assertEquals(RESOURCE_ID, ResourceId.of(ID, TYPE)); - assertEquals(ID, RESOURCE_ID.id()); - assertEquals(TYPE, RESOURCE_ID.type()); - assertNotEquals(ResourceId.of("another-ID", TYPE), RESOURCE_ID); - } - - @Test - public void testToAndFromPb() { - ResourceId copy = ResourceId.fromPb(RESOURCE_ID.toPb()); - assertEquals(RESOURCE_ID, copy); - assertEquals(ID, copy.id()); - assertEquals(TYPE, copy.type()); - } -} 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 index 0929ea18c94a..5b981ce4c77a 100644 --- 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 @@ -36,7 +36,6 @@ public class SerializationTest { - private static final ResourceId RESOURCE_ID = ResourceId.of("some id", "organization"); private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder("id1").build(); private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder("id") @@ -45,7 +44,6 @@ public class SerializationTest { .number(123L) .state(ProjectInfo.State.ACTIVE) .createTimeMillis(1234L) - .parent(RESOURCE_ID) .build(); private static final PageImpl PAGE_RESULT = new PageImpl<>(null, "c", Collections.singletonList(PARTIAL_PROJECT_INFO)); @@ -67,7 +65,7 @@ public void testServiceOptions() throws Exception { @Test public void testModelAndRequests() throws Exception { - Serializable[] objects = {RESOURCE_ID, PARTIAL_PROJECT_INFO, FULL_PROJECT_INFO, PAGE_RESULT}; + Serializable[] objects = {PARTIAL_PROJECT_INFO, FULL_PROJECT_INFO, PAGE_RESULT}; for (Serializable obj : objects) { Object copy = serializeAndDeserialize(obj); assertEquals(obj, obj); From 24edea28fdede78e5a9850955907932518c9551b Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Fri, 27 Nov 2015 13:19:01 -0800 Subject: [PATCH 13/35] Add exception handling, add back resource ID, and other cleanup --- .../google/gcloud/resourcemanager/Option.java | 3 +- .../gcloud/resourcemanager/Project.java | 30 ++-- .../gcloud/resourcemanager/ProjectInfo.java | 152 +++++++++++++----- .../resourcemanager/ResourceManager.java | 55 ++++--- .../ResourceManagerException.java | 8 +- .../gcloud/spi/DefaultResourceManagerRpc.java | 89 ++++++++++ .../resourcemanager/ProjectInfoTest.java | 44 ++--- .../gcloud/resourcemanager/ProjectTest.java | 34 ++-- .../resourcemanager/SerializationTest.java | 26 ++- 9 files changed, 306 insertions(+), 135 deletions(-) create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java 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 index 9900861b93cc..853b678a2ebe 100644 --- 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 @@ -27,7 +27,7 @@ /** * Base class for Resource Manager operation options */ -public class Option implements Serializable { +class Option implements Serializable { private static final long serialVersionUID = 2655177550880762967L; @@ -70,4 +70,3 @@ public String toString() { .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 index b9abdabc9f87..a08d7e3714be 100644 --- 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 @@ -21,8 +21,10 @@ /** * A Google Cloud Resource Manager project object. * - * This class' member variables are immutable. Methods that change or update the underlying Project - * information return a new Project instance. + * 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 { @@ -69,7 +71,7 @@ public ResourceManager resourceManager() { * @throws ResourceManagerException upon failure */ public Project reload() { - return new Project(resourceManager, resourceManager.get(info.id())); + return Project.load(resourceManager, info.projectId()); } /** @@ -89,13 +91,13 @@ public Project reload() { * 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 + * @see Cloud + * Resource Manager delete * @throws ResourceManagerException upon failure */ public void delete() { - resourceManager.delete(info.id()); + resourceManager.delete(info.projectId()); } /** @@ -106,13 +108,13 @@ public void delete() { * 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 + * @see Cloud + * Resource Manager undelete * @throws ResourceManagerException upon failure (including when the project can't be restored) */ public void undelete() { - resourceManager.undelete(info.id()); + resourceManager.undelete(info.projectId()); } /** @@ -120,9 +122,9 @@ public void undelete() { * * The caller must have modify permissions for this project. * - * @see - * - * Cloud Resource Manager update + * @see Cloud + * Resource Manager update * @return the ProjectInfo representing the new project metadata * @throws ResourceManagerException upon failure */ 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 index 4e45e1274dbf..7b48e595ae0d 100644 --- 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 @@ -14,11 +14,14 @@ 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; @@ -28,26 +31,23 @@ /** * 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 id; + private final String projectId; private final Map labels; - private final Long number; + private final Long projectNumber; private final State state; private final Long createTimeMillis; + private final ResourceId parent; /** - * The project lifecycle state. - * - *
            - *
          • LIFECYCLE_STATE_UNSPECIFIED: - *
          • ACTIVE: - *
          • DELETE_REQUESTED: - *
          • DELETE_IN_PROGRESS: - *
              + * The project lifecycle states. */ public enum State { /** @@ -72,16 +72,71 @@ public enum State { 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 id; - private Map labels; - private Long number; + private String projectId; + private Map labels = new HashMap<>(); + private Long projectNumber; private State state; private Long createTimeMillis; + private ResourceId parent; - Builder() { - labels = new HashMap<>(); + private Builder() { + } + + Builder(ProjectInfo info) { + this.name = info.name; + this.projectId = info.projectId; + this.labels = Maps.newHashMap(checkNotNull(info.labels)); + this.projectNumber = info.projectNumber; + this.state = info.state; + this.createTimeMillis = info.createTimeMillis; + this.parent = info.parent; } /** @@ -92,7 +147,7 @@ public static class Builder { * field can be changed after project creation. */ public Builder name(String name) { - this.name = name; + this.name = firstNonNull(name, Data.nullOf(String.class)); return this; } @@ -103,8 +158,8 @@ public Builder name(String name) { * Trailing hyphens are prohibited. This field cannot be changed after the server creates the * project. */ - public Builder id(String id) { - this.id = id; + public Builder projectId(String projectId) { + this.projectId = projectId; return this; } @@ -148,8 +203,8 @@ public Builder labels(Map labels) { return this; } - Builder number(Long number) { - this.number = number; + Builder projectNumber(Long projectNumber) { + this.projectNumber = projectNumber; return this; } @@ -163,6 +218,11 @@ Builder createTimeMillis(Long createTimeMillis) { return this; } + Builder parent(ResourceId parent) { + this.parent = parent; + return this; + } + public ProjectInfo build() { return new ProjectInfo(this); } @@ -170,11 +230,12 @@ public ProjectInfo build() { ProjectInfo(Builder builder) { this.name = builder.name; - this.id = checkNotNull(builder.id); + this.projectId = checkNotNull(builder.projectId); this.labels = ImmutableMap.copyOf(builder.labels); - this.number = builder.number; + this.projectNumber = builder.projectNumber; this.state = builder.state; this.createTimeMillis = builder.createTimeMillis; + this.parent = builder.parent; } /** @@ -182,8 +243,8 @@ public ProjectInfo build() { * * This field cannot be changed after the server creates the project. */ - public String id() { - return id; + public String projectId() { + return projectId; } /** @@ -192,7 +253,7 @@ public String id() { * This field is optional, can remain unset, and can be changed after project creation. */ public String name() { - return name; + return Data.isNull(name) ? null : name; } /** @@ -200,8 +261,8 @@ public String name() { * * This field is set by the server and is read-only. */ - public Long number() { - return number; + public Long projectNumber() { + return projectNumber; } /** @@ -221,6 +282,10 @@ public State state() { return state; } + ResourceId parent() { + return parent; + } + /** * Get the project's creation time (in milliseconds). * @@ -237,45 +302,54 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hash(name, id, labels, number, state, createTimeMillis); + return Objects.hash(name, projectId, labels, projectNumber, state, createTimeMillis, parent); } public static Builder builder(String id) { - return new Builder().id(id); + return new Builder().projectId(id); } public Builder toBuilder() { - return new Builder() - .name(name) - .id(id) - .labels(labels) - .number(number) - .state(state) - .createTimeMillis(createTimeMillis); + 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(id); + projectPb.setProjectId(projectId); projectPb.setLabels(labels); - projectPb.setProjectNumber(number); + 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()).name(projectPb.getName()); + 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 46183bfc8188..b8631a9ae881 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 @@ -41,10 +41,10 @@ public interface ResourceManager extends Service { * even if not specified. */ enum ProjectField { - ID("projectId"), + PROJECT_ID("projectId"), NAME("name"), LABELS("labels"), - NUMBER("projectNumber"), + PROJECT_NUMBER("projectNumber"), STATE("lifecycleState"), CREATE_TIME("createTime"); @@ -60,7 +60,7 @@ public String selector() { static String selector(ProjectField... fields) { HashSet fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1); - fieldStrings.add(ID.selector()); + fieldStrings.add(PROJECT_ID.selector()); for (ProjectField field : fields) { fieldStrings.add(field.selector()); } @@ -68,6 +68,9 @@ static String selector(ProjectField... fields) { } } + /** + * Class for specifying project get options. + */ public class ProjectGetOption extends Option { private static final long serialVersionUID = 270185129961146874L; @@ -80,7 +83,7 @@ private ProjectGetOption(ResourceManagerRpc.Option option, Object 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 ProjectListOption.fields} can be used to specify only the fields of interest. Project + * {@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. */ @@ -106,11 +109,14 @@ private ProjectListOption(ResourceManagerRpc.Option option, Object value) { * Filter rules are case insensitive. The fields eligible for filtering are: *
                *
              • name - *
              • id + *
              • project ID *
              • labels.key, where key is the name of a label *
              * - * Some examples of using labels as filters: + * 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. @@ -121,8 +127,6 @@ private ProjectListOption(ResourceManagerRpc.Option option, Object value) { *
              • labels.color:red label.size:big The project's label color has the value red and its * label size has the value big. *
              - * - * Optional. */ public static ProjectListOption filter(String filter) { return new ProjectListOption(ResourceManagerRpc.Option.FILTER, filter); @@ -150,10 +154,9 @@ public static ProjectListOption fields(ProjectField... fields) { * 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 - * + * @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. @@ -178,9 +181,9 @@ public static ProjectListOption fields(ProjectField... fields) { * 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 + * @see Cloud + * Resource Manager delete * @throws ResourceManagerException upon failure */ void delete(String projectId); @@ -190,8 +193,9 @@ public static ProjectListOption fields(ProjectField... fields) { * * The caller must have read permissions for this project. * - * @see - * Cloud Resource Manager get + * @see Cloud + * Resource Manager get * @throws ResourceManagerException upon failure */ ProjectInfo get(String projectId, ProjectGetOption... options); @@ -204,8 +208,9 @@ public static ProjectListOption fields(ProjectField... fields) { * page tokens. Note that pagination is currently not implemented by the Cloud Resource Manager * API. * - * @see - * Cloud Resource Manager list + * @see Cloud + * Resource Manager list * @return {@code Page}, a page of projects. * @throws ResourceManagerException upon failure */ @@ -216,9 +221,9 @@ public static ProjectListOption fields(ProjectField... fields) { * * The caller must have modify permissions for this project. * - * @see - * - * Cloud Resource Manager update + * @see Cloud + * Resource Manager update * @return the ProjectInfo representing the new project metadata * @throws ResourceManagerException upon failure */ @@ -232,9 +237,9 @@ public static ProjectListOption fields(ProjectField... fields) { * 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 + * @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 8287ed167557..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 @@ -17,6 +17,7 @@ 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; @@ -44,7 +45,12 @@ public ResourceManagerException(int code, String message, 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/spi/DefaultResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java new file mode 100644 index 000000000000..45f8f9de75e9 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java @@ -0,0 +1,89 @@ +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 { + + 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()) + || "InternalError".equals(exception.getMessage()); + 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) 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/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java index e11962d94273..c066ccf39ca4 100644 --- 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 @@ -28,35 +28,37 @@ public class ProjectInfoTest { - private static final String ID = "project-id"; + 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 NUMBER = 123L; + 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 FULL_PROJECT_INFO = - ProjectInfo.builder(ID) - .name(NAME) - .labels(LABELS) - .number(NUMBER) - .createTimeMillis(CREATE_TIME_MILLIS) - .state(STATE) - .build(); - private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder(ID).build(); + 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(ID, FULL_PROJECT_INFO.id()); + assertEquals(PROJECT_ID, FULL_PROJECT_INFO.projectId()); assertEquals(NAME, FULL_PROJECT_INFO.name()); assertEquals(LABELS, FULL_PROJECT_INFO.labels()); - assertEquals(NUMBER, FULL_PROJECT_INFO.number()); + assertEquals(PROJECT_NUMBER, FULL_PROJECT_INFO.projectNumber()); assertEquals(CREATE_TIME_MILLIS, FULL_PROJECT_INFO.createTimeMillis()); assertEquals(STATE, FULL_PROJECT_INFO.state()); - assertEquals(ID, PARTIAL_PROJECT_INFO.id()); + assertEquals(PROJECT_ID, PARTIAL_PROJECT_INFO.projectId()); assertEquals(null, PARTIAL_PROJECT_INFO.name()); assertTrue(PARTIAL_PROJECT_INFO.labels().isEmpty()); - assertEquals(null, PARTIAL_PROJECT_INFO.number()); + assertEquals(null, PARTIAL_PROJECT_INFO.projectNumber()); assertEquals(null, PARTIAL_PROJECT_INFO.createTimeMillis()); assertEquals(null, PARTIAL_PROJECT_INFO.state()); } @@ -77,25 +79,27 @@ public void testToAndFromPb() { public void testEquals() { compareProjects( FULL_PROJECT_INFO, - ProjectInfo.builder(ID) + ProjectInfo.builder(PROJECT_ID) .name(NAME) .labels(LABELS) - .number(NUMBER) + .projectNumber(PROJECT_NUMBER) .createTimeMillis(CREATE_TIME_MILLIS) .state(STATE) + .parent(PARENT) .build()); - compareProjects(PARTIAL_PROJECT_INFO, ProjectInfo.builder(ID).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.id(), value.id()); + assertEquals(expected.projectId(), value.projectId()); assertEquals(expected.name(), value.name()); assertEquals(expected.labels(), value.labels()); - assertEquals(expected.number(), value.number()); + 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 index ddb543752ac7..223dd5b14289 100644 --- 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 @@ -33,20 +33,19 @@ import java.util.Map; public class ProjectTest { - private static final String ID = "project-id"; + 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 NUMBER = 123L; + 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(ID) - .name(NAME) - .labels(LABELS) - .number(NUMBER) - .createTimeMillis(CREATE_TIME_MILLIS) - .state(STATE) - .build(); + 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; @@ -64,27 +63,22 @@ public void tearDown() throws Exception { @Test public void testLoad() { - expect(resourceManager.get(PROJECT_INFO.id())).andReturn(PROJECT_INFO); + expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(PROJECT_INFO); replay(resourceManager); - Project loadedProject = Project.load(resourceManager, PROJECT_INFO.id()); + 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.id())).andReturn(newInfo); + 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 testPolicy() { - replay(resourceManager); - } - @Test public void testInfo() { replay(resourceManager); @@ -99,7 +93,7 @@ public void testResourceManager() { @Test public void testDelete() { - resourceManager.delete(PROJECT_INFO.id()); + resourceManager.delete(PROJECT_INFO.projectId()); expectLastCall(); replay(resourceManager); project.delete(); @@ -107,7 +101,7 @@ public void testDelete() { @Test public void testUndelete() { - resourceManager.undelete(PROJECT_INFO.id()); + resourceManager.undelete(PROJECT_INFO.projectId()); expectLastCall(); replay(resourceManager); project.undelete(); 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 index 5b981ce4c77a..dd8fb8014bf0 100644 --- 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 @@ -37,14 +37,13 @@ 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")) - .number(123L) - .state(ProjectInfo.State.ACTIVE) - .createTimeMillis(1234L) - .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)); @@ -53,12 +52,11 @@ 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(); + options = options.toBuilder() + .projectId("some-unnecessary-project-ID") + .retryParams(RetryParams.defaultInstance()) + .authCredentials(AuthCredentials.noCredentials()) + .build(); serializedCopy = serializeAndDeserialize(options); assertEquals(options, serializedCopy); } From 940aa92c009b9bdd4fad316fa32f2bc9857d758e Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 30 Nov 2015 09:27:35 -0800 Subject: [PATCH 14/35] Add list/get options serialization tests + other small fixes --- .../com/google/gcloud/resourcemanager/ProjectInfo.java | 6 +++--- .../com/google/gcloud/resourcemanager/ResourceManager.java | 2 +- .../com/google/gcloud/spi/DefaultResourceManagerRpc.java | 7 +++---- .../java/com/google/gcloud/spi/ResourceManagerRpc.java | 2 +- .../com/google/gcloud/resourcemanager/ProjectTest.java | 3 --- .../google/gcloud/resourcemanager/SerializationTest.java | 7 ++++++- 6 files changed, 14 insertions(+), 13 deletions(-) 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 index 7b48e595ae0d..5d490e662df0 100644 --- 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 @@ -132,7 +132,7 @@ private Builder() { Builder(ProjectInfo info) { this.name = info.name; this.projectId = info.projectId; - this.labels = Maps.newHashMap(checkNotNull(info.labels)); + this.labels.putAll(info.labels); this.projectNumber = info.projectNumber; this.state = info.state; this.createTimeMillis = info.createTimeMillis; @@ -159,7 +159,7 @@ public Builder name(String name) { * project. */ public Builder projectId(String projectId) { - this.projectId = projectId; + this.projectId = checkNotNull(projectId); return this; } @@ -230,7 +230,7 @@ public ProjectInfo build() { ProjectInfo(Builder builder) { this.name = builder.name; - this.projectId = checkNotNull(builder.projectId); + this.projectId = builder.projectId; this.labels = ImmutableMap.copyOf(builder.labels); this.projectNumber = builder.projectNumber; this.state = builder.state; 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 b8631a9ae881..9aabd640b018 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 @@ -36,7 +36,7 @@ public interface ResourceManager extends Service { /** * The fields of a project. * - * These values can be used to specify the fields to include of in a partial response when calling + * 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. */ 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 index 45f8f9de75e9..d73c6ae4015c 100644 --- 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 @@ -17,6 +17,7 @@ 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; @@ -45,9 +46,7 @@ private static ResourceManagerException translate(IOException exception) { } private static ResourceManagerException translate(GoogleJsonError exception) { - boolean retryable = - RETRYABLE_CODES.contains(exception.getCode()) - || "InternalError".equals(exception.getMessage()); + boolean retryable = RETRYABLE_CODES.contains(exception.getCode()); return new ResourceManagerException(exception.getCode(), exception.getMessage(), retryable); } @@ -63,7 +62,7 @@ public void delete(String projectId) throws ResourceManagerException { } @Override - public Project get(String projectId) throws ResourceManagerException { + public Project get(String projectId, Map options) 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 f7e330639399..ec96e26e0be5 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 @@ -77,7 +77,7 @@ public Y y() { void delete(String projectId) throws ResourceManagerException; - Project get(String projectId) throws ResourceManagerException; + Project get(String projectId, Map options) throws ResourceManagerException; Tuple> list(Map options) throws ResourceManagerException; 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 index 223dd5b14289..65bb37dbccf9 100644 --- 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 @@ -18,7 +18,6 @@ import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; @@ -94,7 +93,6 @@ public void testResourceManager() { @Test public void testDelete() { resourceManager.delete(PROJECT_INFO.projectId()); - expectLastCall(); replay(resourceManager); project.delete(); } @@ -102,7 +100,6 @@ public void testDelete() { @Test public void testUndelete() { resourceManager.undelete(PROJECT_INFO.projectId()); - expectLastCall(); replay(resourceManager); project.undelete(); } 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 index dd8fb8014bf0..8b61519fc106 100644 --- 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 @@ -46,6 +46,10 @@ public class SerializationTest { .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 { @@ -63,7 +67,8 @@ public void testServiceOptions() throws Exception { @Test public void testModelAndRequests() throws Exception { - Serializable[] objects = {PARTIAL_PROJECT_INFO, FULL_PROJECT_INFO, PAGE_RESULT}; + 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); From 57de95ab08944e567a3f083bc7168d7ec6239d74 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 30 Nov 2015 12:55:02 -0800 Subject: [PATCH 15/35] Add page size and page token options --- .../resourcemanager/ResourceManager.java | 20 +++++++++++++++++++ .../google/gcloud/spi/ResourceManagerRpc.java | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) 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 9aabd640b018..8f27ff182b48 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 @@ -132,6 +132,26 @@ 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. Pagination is not yet supported; the server ignores this field. + */ + public static ProjectListOption pageToken(String pageToken) { + return new ProjectListOption(ResourceManagerRpc.Option.PAGE_TOKEN, pageToken); + } + + /** + * The maximum number of projects to return in the response. + * + * The server can return fewer projects than requested. If unspecified, server picks an + * appropriate default. Note: pagination is not yet supported; the server ignores this field. + */ + 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. * 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 ec96e26e0be5..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 @@ -25,7 +25,9 @@ public interface ResourceManagerRpc { enum Option { FILTER("filter"), - FIELDS("fields"); + FIELDS("fields"), + PAGE_SIZE("pageSize"), + PAGE_TOKEN("pageToken"); private final String value; From e9359a0e5a69fccf224bcaf7002fc851755ab0f3 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 30 Nov 2015 13:57:54 -0800 Subject: [PATCH 16/35] fix paging docs --- .../google/gcloud/resourcemanager/ResourceManager.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 8f27ff182b48..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 @@ -136,17 +136,19 @@ public static ProjectListOption filter(String 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. Pagination is not yet supported; the server ignores this field. + * continue. */ public static ProjectListOption pageToken(String pageToken) { return new ProjectListOption(ResourceManagerRpc.Option.PAGE_TOKEN, pageToken); } /** - * The maximum number of projects to return in the response. + * The maximum number of projects to return per RPC. * - * The server can return fewer projects than requested. If unspecified, server picks an - * appropriate default. Note: pagination is not yet supported; the server ignores this field. + * 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); From e6954ae90e5f7e0e8955a7525f2b6702458053b9 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 30 Nov 2015 15:15:25 -0800 Subject: [PATCH 17/35] sync pom version to parent project --- gcloud-java-resourcemanager/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcloud-java-resourcemanager/pom.xml b/gcloud-java-resourcemanager/pom.xml index 4a4899231177..515a5c50e04f 100644 --- a/gcloud-java-resourcemanager/pom.xml +++ b/gcloud-java-resourcemanager/pom.xml @@ -11,7 +11,7 @@ com.google.gcloud gcloud-java-pom - 0.0.11-SNAPSHOT + 0.0.13-SNAPSHOT gcloud-java-resourcemanager From a50d770e002a6035529bfd9bbc27c9e83e35e6e3 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 30 Nov 2015 15:32:12 -0800 Subject: [PATCH 18/35] Default spi layer implementation --- .../gcloud/spi/DefaultResourceManagerRpc.java | 59 +++++++++++++++---- .../google/gcloud/spi/ResourceManagerRpc.java | 2 +- 2 files changed, 48 insertions(+), 13 deletions(-) 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 index d73c6ae4015c..6fbd038a3f8c 100644 --- 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 @@ -1,11 +1,17 @@ package com.google.gcloud.spi; +import static com.google.gcloud.spi.ResourceManagerRpc.Option.FIELDS; +import static com.google.gcloud.spi.ResourceManagerRpc.Option.FILTER; +import static com.google.gcloud.spi.ResourceManagerRpc.Option.PAGE_SIZE; +import static com.google.gcloud.spi.ResourceManagerRpc.Option.PAGE_TOKEN; + 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.ListProjectsResponse; import com.google.api.services.cloudresourcemanager.model.Project; import com.google.common.collect.ImmutableSet; import com.google.gcloud.resourcemanager.ResourceManagerException; @@ -20,13 +26,11 @@ 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()) @@ -52,37 +56,68 @@ private static ResourceManagerException translate(GoogleJsonError exception) { @Override public Project create(Project project) throws ResourceManagerException { - // TODO(ajaykannan): fix me! - return null; + try { + return resourceManager.projects().create(project).execute(); + } catch (IOException ex) { + throw translate(ex); + } } @Override public void delete(String projectId) throws ResourceManagerException { - // TODO(ajaykannan): fix me! + try { + resourceManager.projects().delete(projectId).execute(); + } catch (IOException ex) { + throw translate(ex); + } } @Override public Project get(String projectId, Map options) throws ResourceManagerException { - // TODO(ajaykannan): fix me! - return null; + try { + return resourceManager.projects() + .get(projectId) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + throw translate(ex); + } } @Override public Tuple> list(Map options) throws ResourceManagerException { - // TODO(ajaykannan): fix me! - return null; + try { + ListProjectsResponse response = resourceManager.projects() + .list() + .setFilter(FIELDS.getString(options)) + .setFilter(FILTER.getString(options)) + .setPageSize(PAGE_SIZE.getInt(options)) + .setPageToken(PAGE_TOKEN.getString(options)) + .execute(); + return Tuple.>of( + response.getNextPageToken(), response.getProjects()); + } catch (IOException ex) { + throw translate(ex); + } } @Override public void undelete(String projectId) throws ResourceManagerException { - // TODO(ajaykannan): fix me! + try { + resourceManager.projects().undelete(projectId).execute(); + } catch (IOException ex) { + throw translate(ex); + } } @Override public Project replace(Project project) throws ResourceManagerException { - // TODO(ajaykannan): fix me! - return null; + try { + return resourceManager.projects().update(project.getProjectId(), project).execute(); + } catch (IOException ex) { + throw translate(ex); + } } } 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 3302b3571efa..52dfc2d2368e 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 @@ -48,7 +48,7 @@ String getString(Map options) { return get(options); } - Long getInt(Map options) { + Integer getInt(Map options) { return get(options); } } From 918ab7b5f58f4eb3a7f5f69ea7fe8a22832d54e3 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Mon, 30 Nov 2015 16:12:17 -0800 Subject: [PATCH 19/35] Fix typo and remove non-retryable error code --- .../java/com/google/gcloud/spi/DefaultResourceManagerRpc.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 6fbd038a3f8c..44893ade2e95 100644 --- 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 @@ -24,7 +24,7 @@ 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 static final Set RETRYABLE_CODES = ImmutableSet.of(503, 500, 429); private final Cloudresourcemanager resourceManager; @@ -90,7 +90,7 @@ public Tuple> list(Map options) try { ListProjectsResponse response = resourceManager.projects() .list() - .setFilter(FIELDS.getString(options)) + .setFields(FIELDS.getString(options)) .setFilter(FILTER.getString(options)) .setPageSize(PAGE_SIZE.getInt(options)) .setPageToken(PAGE_TOKEN.getString(options)) From 3de4f8f3372ba1f70ce0ef96f55ceba8dc081858 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Sun, 6 Dec 2015 13:15:00 -0800 Subject: [PATCH 20/35] Initial commit of LocalResourceManagerHelper and tests --- .../testing/LocalResourceManagerHelper.java | 483 ++++++++++++++++++ .../resourcemanager/testing/package-info.java | 35 ++ .../gcloud/spi/DefaultResourceManagerRpc.java | 10 +- .../LocalResourceManagerHelperTest.java | 382 ++++++++++++++ 4 files changed, 905 insertions(+), 5 deletions(-) create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java create mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java new file mode 100644 index 000000000000..aa9031b6b7dd --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java @@ -0,0 +1,483 @@ +package com.google.gcloud.resourcemanager.testing; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.json.JsonFactory; +import com.google.api.services.cloudresourcemanager.model.Project; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import org.joda.time.format.ISODateTimeFormat; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; + +/** + * Utility to create a local Resource Manager mock for testing. + * + * The mock runs in a separate thread, listening to port 8080 for HTTP requests. + */ +@SuppressWarnings("restriction") +public class LocalResourceManagerHelper { + private static final Logger log = Logger.getLogger(LocalResourceManagerHelper.class.getName()); + private static final JsonFactory jsonFactory = + new com.google.api.client.json.jackson.JacksonFactory(); + private static final int HTTP_STATUS_OK = 200; + private static final Random PROJECT_NUMBER_GENERATOR = new Random(); + + private HttpServer server; + private final Map projects = new HashMap<>(); + + static class Response { + private final int code; + private final String body; + + Response(int code, String body) { + this.code = code; + this.body = body; + } + + int code() { + return code; + } + + String body() { + return body; + } + } + + enum Error { + ALREADY_EXISTS( + 409, "global", "Requested entity already exists.", "alreadyExists", "ALREADY_EXISTS"), + PERMISSION_DENIED( + 403, "global", "The caller does not have permission.", "forbidden", "PERMISSION_DENIED"), + FAILED_PRECONDITION( // change this error code to 412 when #440 is fixed + 400, "global", "Precondition check failed.", "failedPrecondition", "FAILED_PRECONDITION"), + INVALID_ARGUMENT( // change this error code to 412 when #440 is fixed + 400, "global", "Request contains an invalid argument.", "badRequest", + "INVALID_ARGUMENT"); + + private final Response response; + + Error(int code, String domain, String message, String reason, String status) { + this.response = new Response(code, toJson(code, domain, message, reason, status)); + } + + private static String toJson( + int code, String domain, String message, String reason, String status) { + Map args = new HashMap<>(); + Map nestedArgs = new HashMap<>(); + nestedArgs.put("domain", domain); + nestedArgs.put("message", message); + nestedArgs.put("reason", reason); + List> errors = ImmutableList.of(nestedArgs); + args.put("errors", errors); + args.put("code", code); + args.put("message", message); + args.put("status", status); + try { + return jsonFactory.toString(args); + } catch (IOException e) { + throw new RuntimeException("Error when generating JSON error response."); + } + } + } + + private class RequestHandler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) throws IOException { + // see https://cloud.google.com/resource-manager/reference/rest/ + String path = exchange.getRequestURI().getPath(); + String requestMethod = exchange.getRequestMethod(); + Response response = null; + if (requestMethod.equals("POST") && path.startsWith("/v1beta1/projects")) { + if (path.contains("undelete")) { + response = undelete(projectIdFromURI(path)); + } else { + String requestBody = + decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); + response = create(jsonFactory.fromString(requestBody, Project.class)); + } + } else if (requestMethod.equals("DELETE")) { + response = delete(projectIdFromURI(path)); + } else if (requestMethod.equals("GET") && path.startsWith("/v1beta1/projects/")) { + response = get(projectIdFromURI(path), parseFields(exchange.getRequestURI().getQuery())); + } else if (requestMethod.equals("GET")) { + response = list(parseListOptions(exchange.getRequestURI().getQuery())); + } else if (requestMethod.equals("PUT")) { + String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); + response = replace(jsonFactory.fromString(requestBody, Project.class)); + } + if (response == null) { + throw new UnsupportedOperationException("Request not recognized."); + } + exchange.sendResponseHeaders(response.code(), response.body().length()); + OutputStream outputStream = exchange.getResponseBody(); + outputStream.write(response.body().getBytes()); + outputStream.close(); + } + } + + private static String decodeContent(Headers headers, InputStream inputStream) throws IOException { + List contentEncoding = headers.get("Content-encoding"); + byte[] bytes; + if (contentEncoding != null && contentEncoding.size() > 0 + && contentEncoding.get(0).contains("gzip")) { + bytes = ByteStreams.toByteArray(new GZIPInputStream(inputStream)); + log.fine("Content-encoding is in gzip format. Decoded successfully."); + } else { + bytes = ByteStreams.toByteArray(inputStream); + } + return new String(bytes, StandardCharsets.UTF_8); + } + + private static String projectIdFromURI(String path) { + String[] pathSplit = path.split("/"); + if (pathSplit.length < 4) { + throw new IllegalArgumentException("This path doesn't have a project ID"); + } + return path.split("/")[3].split(":")[0]; + } + + private static String[] parseFields(String query) { + return query != null ? query.split("=")[1].split(",") : null; + } + + private static Map parseListOptions(String query) { + Map options = new HashMap<>(); + if (query != null) { + String[] args = query.split("&"); + for (String arg : args) { + String[] argEntry = arg.split("="); + switch (argEntry[0]) { + case "fields": + options.put("fields", argEntry[1].split(",")); + break; + case "pageToken": + // support pageToken when Cloud Resource Manager supports this (#421) + break; + case "pageSize": + // support pageSize when Cloud Resource Manager supports this (#421) + break; + } + } + } + return options; + } + + Response create(Project project) throws IOException { + project.setLifecycleState("ACTIVE"); + project.setProjectNumber(Math.abs(PROJECT_NUMBER_GENERATOR.nextLong())); + project.setCreateTime(ISODateTimeFormat.dateTime().print(new Date().getTime())); + Response response; + if (projects.containsKey(checkNotNull(project.getProjectId()))) { + response = Error.ALREADY_EXISTS.response; + log.info( + "A project with the same project ID (" + project.getProjectId() + ") already exists."); + } else { + projects.put(project.getProjectId(), project); + String createdProjectStr = jsonFactory.toString(project); + log.info("Created the following project: " + createdProjectStr); + response = new Response(HTTP_STATUS_OK, createdProjectStr); + } + return response; + } + + Response delete(String projectId) { + Project project = projects.get(checkNotNull(projectId)); + Response response; + if (project == null) { + response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) + log.info("Error when deleting " + projectId + " because the project was not found."); + } else if (!project.getLifecycleState().equals("ACTIVE")) { + response = Error.FAILED_PRECONDITION.response; + log.info("Error when deleting " + projectId + " because the lifecycle state was not ACTIVE."); + } else { + project.setLifecycleState("DELETE_REQUESTED"); + response = new Response(HTTP_STATUS_OK, "{}"); + log.info("Successfully requested delete for the following project: " + projectId); + } + return response; + } + + Response get(String projectId, String[] fields) throws IOException { + Response response; + if (!projects.containsKey(checkNotNull(projectId))) { + response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) + log.info("Project not found."); + } else { + response = new Response( + HTTP_STATUS_OK, jsonFactory.toString(extractFields(projects.get(projectId), fields))); + } + return response; + } + + Response list(Map options) throws IOException { + // Use pageSize and pageToken options when Cloud Resource Manager does so (#421) + List projectsSerialized = new ArrayList<>(); + for (Project p : projects.values()) { + projectsSerialized.add( + jsonFactory.toString(extractFields(p, (String[]) options.get("fields")))); + } + StringBuilder responseBody = new StringBuilder(); + responseBody.append("{\"projects\": ["); + responseBody.append(Joiner.on(",").join(projectsSerialized)); + responseBody.append("]}"); + return new Response(HTTP_STATUS_OK, responseBody.toString()); + } + + private static Project extractFields(Project fullProject, String[] fields) { + if (fields == null) { + return fullProject; + } + Project project = new Project(); + for (String field : fields) { + switch (field) { + case "createTime": + project.setCreateTime(fullProject.getCreateTime()); + break; + case "labels": + project.setLabels(fullProject.getLabels()); + break; + case "lifecycleState": + project.setLifecycleState(fullProject.getLifecycleState()); + break; + case "name": + project.setName(fullProject.getName()); + break; + case "parent": + project.setParent(fullProject.getParent()); + break; + case "projectId": + project.setProjectId(fullProject.getProjectId()); + break; + case "projectNumber": + project.setProjectNumber(fullProject.getProjectNumber()); + break; + } + } + return project; + } + + Response replace(Project project) throws IOException { + Response response; + Project oldProject = projects.get(checkNotNull(project.getProjectId())); + if (oldProject == null) { + response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) + log.info( + "Error when replacing " + project.getProjectId() + " because the project was not found."); + } else if (!oldProject.getLifecycleState().equals("ACTIVE")) { + response = Error.FAILED_PRECONDITION.response; + log.info("Error when replacing " + project.getProjectId() + + " because the lifecycle state was not ACTIVE."); + } else if (!Objects.equal(oldProject.getParent(), project.getParent())) { + response = Error.INVALID_ARGUMENT.response; + log.info("The server currently only supports setting the parent once " + + "and does not allow unsetting it."); + } else { + project.setLifecycleState("ACTIVE"); + project.setProjectNumber(oldProject.getProjectNumber()); + project.setCreateTime(oldProject.getCreateTime()); + project.setParent(oldProject.getParent()); + projects.put(project.getProjectId(), project); + String updatedProjectStr = jsonFactory.toString(project); + log.info("Successfully updated the project to be: " + updatedProjectStr); + response = new Response(HTTP_STATUS_OK, updatedProjectStr); + } + return response; + } + + Response undelete(String projectId) { + Project project = projects.get(checkNotNull(projectId)); + Response response; + if (project == null) { + response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) + log.info("Error when undeleting " + projectId + " because the project was not found."); + } else if (!project.getLifecycleState().equals("DELETE_REQUESTED")) { + response = Error.FAILED_PRECONDITION.response; + log.info("Error when undeleting " + projectId + + " because the lifecycle state was not DELETE_REQUESTED."); + } else { + project.setLifecycleState("ACTIVE"); + response = new Response(HTTP_STATUS_OK, "{}"); + log.info("Successfully undeleted " + projectId + "."); + } + return response; + } + + private LocalResourceManagerHelper(int port) { + try { + this.server = HttpServer.create(new InetSocketAddress(port), 0); + this.server.createContext("/", new RequestHandler()); + } catch (IOException e) { + log.severe("Could not create the mock Resource Manager."); + } + } + + /** + * Creates a LocalResourceManagerHelper object that listens to requests on the local machine at + * the port specified. + */ + public static LocalResourceManagerHelper create(int port) { + return new LocalResourceManagerHelper(port); + } + + /** + * Returns an available port on the local machine. + * + * This port can be used to set the host in ResourceManagerOptions and to specify the port to + * which the Resource Manager mock should listen. + */ + public static int findAvailablePort(int defaultPort) { + try (ServerSocket tempSocket = new ServerSocket(0)) { + return tempSocket.getLocalPort(); + } catch (IOException e) { + return defaultPort; + } + } + + /** + * Starts the thread that runs the Resource Manager server. + */ + public void start() { + server.start(); + } + + /** + * Stops the thread that runs the mock Resource Manager server. + */ + public void stop() { + server.stop(1); + } + + /** + * Utility method to add a project. + * + * Will not overwrite an existing project with the same ID. + * + * @return true if the project was successfully added, false otherwise + */ + public boolean addProject(Project project) { + if (projects.containsKey(checkNotNull(project.getProjectId()))) { + return false; + } + projects.put(project.getProjectId(), clone(project)); + return true; + } + + /** + * Utility method to get a project. + * + * @return Project (if it exists) or null (if it doesn't exist) + */ + public Project getProject(String projectId) { + com.google.api.services.cloudresourcemanager.model.Project original = projects.get(projectId); + return original != null ? clone(projects.get(projectId)) : null; + } + + private static com.google.api.services.cloudresourcemanager.model.Project clone( + com.google.api.services.cloudresourcemanager.model.Project original) { + com.google.api.services.cloudresourcemanager.model.Project clone = + new com.google.api.services.cloudresourcemanager.model.Project(); + clone.setProjectId(original.getProjectId()); + clone.setName(original.getName()); + clone.setLabels(original.getLabels()); + clone.setProjectNumber(original.getProjectNumber()); + clone.setCreateTime(original.getCreateTime()); + clone.setLifecycleState(original.getLifecycleState()); + clone.setParent(original.getParent()); + return clone; + } + + /** + * Utility method to remove the specified project. + * + * This method can be used to fully remove a project (to mimic when the server completely + * deletes a project). + * + * @return true if the project was successfully deleted, false otherwise. + */ + public boolean removeProject(String projectId) { + if (projects.containsKey(projectId)) { + projects.remove(checkNotNull(projectId)); + return true; + } + return false; + } + + /** + * Utility method to change the project number of a project. + * + * @return true if the project number was successfully changed, false otherwise. + */ + public boolean changeProjectNumber(String projectId, long projectNumber) { + com.google.api.services.cloudresourcemanager.model.Project project = + projects.get(checkNotNull(projectId)); + if (project != null) { + project.setProjectNumber(projectNumber); + return true; + } + return false; + } + + /** + * Utility method to change the lifecycle state of the specified project. + * + * @return true if the lifecycle state was successfully updated, false otherwise. + */ + public boolean changeLifecycleState(String projectId, String lifecycleState) { + checkArgument( + "ACTIVE".equals(lifecycleState) || "DELETE_REQUESTED".equals(lifecycleState) + || "DELETE_IN_PROGRESS".equals(lifecycleState), + "Lifecycle state must be ACTIVE, DELETE_REQUESTED, or DELETE_IN_PROGRESS"); + com.google.api.services.cloudresourcemanager.model.Project project = + projects.get(checkNotNull(projectId)); + if (project != null) { + project.setLifecycleState(lifecycleState); + return true; + } + return false; + } + + /** + * Utility method to change the create time of a project. + * + * @return true if the project create time was successfully changed, false otherwise. + */ + public boolean changeCreateTime(String projectId, String createTime) { + com.google.api.services.cloudresourcemanager.model.Project project = + projects.get(checkNotNull(projectId)); + if (project != null) { + project.setCreateTime(createTime); + return true; + } + return false; + } + + /** + * Utility method to clear all the projects. + */ + public void clearProjects() { + projects.clear(); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java new file mode 100644 index 000000000000..6116cac5cbaf --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +/** + * A testing helper for Google Cloud Resource Manager. + * + *

              A simple usage example: + *

              Before the test: + *

               {@code
              + * LocalResourceManagerHelper resourceManagerHelper = LocalResourceManagerHelper.create();
              + * // TODO(ajaykannan): implement the following line when ResourceManagerImpl is checked in.
              + * ResourceManager resourceManager = resourceManagerHelper.options().service();
              + * implement this in the next PR
              + * resourceManager.list();
              + * } 
              + * + *

              After the test: + *

               {@code
              + * resourceManagerHelper.stop();
              + * } 
              + */ +package com.google.gcloud.resourcemanager.testing; 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 index 44893ade2e95..b9db67cdd69f 100644 --- 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 @@ -5,7 +5,6 @@ import static com.google.gcloud.spi.ResourceManagerRpc.Option.PAGE_SIZE; import static com.google.gcloud.spi.ResourceManagerRpc.Option.PAGE_TOKEN; -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; @@ -41,7 +40,7 @@ public DefaultResourceManagerRpc(ResourceManagerOptions options) { private static ResourceManagerException translate(IOException exception) { ResourceManagerException translated; if (exception instanceof GoogleJsonResponseException) { - translated = translate(((GoogleJsonResponseException) exception).getDetails()); + translated = translate((GoogleJsonResponseException) exception); } else { translated = new ResourceManagerException(0, exception.getMessage(), false); } @@ -49,9 +48,10 @@ private static ResourceManagerException translate(IOException exception) { return translated; } - private static ResourceManagerException translate(GoogleJsonError exception) { - boolean retryable = RETRYABLE_CODES.contains(exception.getCode()); - return new ResourceManagerException(exception.getCode(), exception.getMessage(), retryable); + private static ResourceManagerException translate(GoogleJsonResponseException exception) { + boolean retryable = RETRYABLE_CODES.contains(exception.getStatusCode()); + return new ResourceManagerException( + exception.getStatusCode(), exception.getMessage(), retryable); } @Override diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java new file mode 100644 index 000000000000..72d063f5c99f --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java @@ -0,0 +1,382 @@ +package com.google.gcloud.resourcemanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.resourcemanager.testing.LocalResourceManagerHelper; +import com.google.gcloud.spi.DefaultResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpc.Tuple; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class LocalResourceManagerHelperTest { + + private static final int PORT = 8080; + private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); + private static final LocalResourceManagerHelper RESOURCE_MANAGER_HELPER = + LocalResourceManagerHelper.create(PORT); + private static final ResourceManagerRpc rpc = new DefaultResourceManagerRpc( + ResourceManagerOptions.builder() + .host("http://localhost:" + PORT) + .build()); + private static final com.google.api.services.cloudresourcemanager.model.Project PARTIAL_PROJECT = + new com.google.api.services.cloudresourcemanager.model.Project(); + private static final com.google.api.services.cloudresourcemanager.model.Project COMPLETE_PROJECT = + new com.google.api.services.cloudresourcemanager.model.Project(); + private static final com.google.api.services.cloudresourcemanager.model.Project + DELETE_REQUESTED_PROJECT = new com.google.api.services.cloudresourcemanager.model.Project(); + private static final com.google.api.services.cloudresourcemanager.model.Project + DELETE_IN_PROGRESS_PROJECT = new com.google.api.services.cloudresourcemanager.model.Project(); + private static final com.google.api.services.cloudresourcemanager.model.Project + PROJECT_WITH_PARENT = new com.google.api.services.cloudresourcemanager.model.Project(); + private static final com.google.api.services.cloudresourcemanager.model.ResourceId PARENT = + new com.google.api.services.cloudresourcemanager.model.ResourceId(); + private static final long DEFAULT_PROJECT_NUMBER = 123456789L; + private static final String DEFAULT_CREATE_TIME = "2011-11-11T01:23:45.678Z"; + private static final String DEFAULT_PARENT_ID = "12345"; + private static final String DEFAULT_PARENT_TYPE = "organization"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @BeforeClass + public static void beforeClass() { + PARTIAL_PROJECT.setProjectId("partialProject"); + COMPLETE_PROJECT.setProjectId("completeProject"); + COMPLETE_PROJECT.setName("full project"); + COMPLETE_PROJECT.setLabels(ImmutableMap.of("k1", "v1", "k2", "v2")); + COMPLETE_PROJECT.setProjectNumber(DEFAULT_PROJECT_NUMBER); + COMPLETE_PROJECT.setCreateTime(DEFAULT_CREATE_TIME); + COMPLETE_PROJECT.setLifecycleState("ACTIVE"); + copyProperties(COMPLETE_PROJECT, PROJECT_WITH_PARENT); + PARENT.setId(DEFAULT_PARENT_ID); + PARENT.setType(DEFAULT_PARENT_TYPE); + PROJECT_WITH_PARENT.setProjectId("projectWithParentId"); + PROJECT_WITH_PARENT.setParent(PARENT); + copyProperties(COMPLETE_PROJECT, DELETE_REQUESTED_PROJECT); + DELETE_REQUESTED_PROJECT.setProjectId("deleteRequestedProjectId"); + DELETE_REQUESTED_PROJECT.setLifecycleState("DELETE_REQUESTED"); + copyProperties(COMPLETE_PROJECT, DELETE_IN_PROGRESS_PROJECT); + DELETE_IN_PROGRESS_PROJECT.setProjectId("deleteInProgressProjectId"); + DELETE_IN_PROGRESS_PROJECT.setLifecycleState("DELETE_IN_PROGRESS"); + RESOURCE_MANAGER_HELPER.start(); + } + + private static void copyProperties( + com.google.api.services.cloudresourcemanager.model.Project from, + com.google.api.services.cloudresourcemanager.model.Project to) { + to.setProjectId(from.getProjectId()); + to.setName(from.getName()); + to.setLabels(from.getLabels()); + to.setProjectNumber(from.getProjectNumber()); + to.setCreateTime(from.getCreateTime()); + to.setLifecycleState(from.getLifecycleState()); + to.setParent(from.getParent()); + } + + @Before + public void setUp() { + RESOURCE_MANAGER_HELPER.clearProjects(); + } + + @AfterClass + public static void afterClass() { + RESOURCE_MANAGER_HELPER.stop(); + } + + @Test + public void testCreate() { + com.google.api.services.cloudresourcemanager.model.Project returnedProject = + rpc.create(PARTIAL_PROJECT); + assertEquals(PARTIAL_PROJECT.getProjectId(), returnedProject.getProjectId()); + assertEquals("ACTIVE", returnedProject.getLifecycleState()); + assertNull(returnedProject.getLabels()); + assertNull(returnedProject.getName()); + assertNull(returnedProject.getParent()); + assertNotNull(returnedProject.getProjectNumber()); + assertNotNull(returnedProject.getCreateTime()); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Requested entity already exists."); + rpc.create(PARTIAL_PROJECT); + returnedProject = rpc.create(PROJECT_WITH_PARENT); + assertEquals(PROJECT_WITH_PARENT.getProjectId(), returnedProject.getProjectId()); + assertEquals("ACTIVE", returnedProject.getLifecycleState()); + assertEquals(PARENT, returnedProject.getParent()); + assertEquals(PROJECT_WITH_PARENT.getLabels(), returnedProject.getLabels()); + assertEquals(PROJECT_WITH_PARENT.getName(), returnedProject.getName()); + assertNotNull(returnedProject.getProjectNumber()); + assertFalse(PROJECT_WITH_PARENT.getCreateTime().equals(returnedProject.getCreateTime())); + } + + @Test + public void testDelete() { + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.delete(COMPLETE_PROJECT.getProjectId()); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("The caller does not have permission."); + rpc.delete("some-nonexistant-project-id"); + } + + @Test + public void testDeleteWhenDeleteInProgress() { + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS"); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Precondition check failed."); + rpc.delete(COMPLETE_PROJECT.getProjectId()); + } + + @Test + public void testDeleteWhenDeleteRequested() { + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_REQUESTED"); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Precondition check failed."); + rpc.delete(COMPLETE_PROJECT.getProjectId()); + } + + @Test + public void testGet() { + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + com.google.api.services.cloudresourcemanager.model.Project returnedProject = + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); + assertEquals(COMPLETE_PROJECT, returnedProject); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("The caller does not have permission."); + RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId()); + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); + } + + @Test + public void testGetWithOptions() { + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + Map rpcOptions = new HashMap<>(); + rpcOptions.put(ResourceManagerRpc.Option.FIELDS, "projectId,name,createTime"); + com.google.api.services.cloudresourcemanager.model.Project returnedProject = + rpc.get(COMPLETE_PROJECT.getProjectId(), rpcOptions); + assertFalse(COMPLETE_PROJECT.equals(returnedProject)); + assertEquals(COMPLETE_PROJECT.getProjectId(), returnedProject.getProjectId()); + assertEquals(COMPLETE_PROJECT.getName(), returnedProject.getName()); + assertEquals(COMPLETE_PROJECT.getCreateTime(), returnedProject.getCreateTime()); + assertNull(returnedProject.getParent()); + assertNull(returnedProject.getProjectNumber()); + assertNull(returnedProject.getLifecycleState()); + assertNull(returnedProject.getLabels()); + } + + @Test + public void testList() { + Tuple> projects = + rpc.list(EMPTY_RPC_OPTIONS); + assertNull(projects.x()); // change this when #421 is resolved + assertFalse(projects.y().iterator().hasNext()); + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.addProject(PROJECT_WITH_PARENT); + projects = rpc.list(EMPTY_RPC_OPTIONS); + Iterator it = + projects.y().iterator(); + assertEquals(COMPLETE_PROJECT, it.next()); + assertEquals(PROJECT_WITH_PARENT, it.next()); + } + + @Test + public void testListWithOptions() { + Map rpcOptions = new HashMap<>(); + rpcOptions.put(ResourceManagerRpc.Option.FIELDS, "projectId,name,labels"); + rpcOptions.put(ResourceManagerRpc.Option.PAGE_TOKEN, "somePageToken"); + rpcOptions.put(ResourceManagerRpc.Option.PAGE_SIZE, 1); + RESOURCE_MANAGER_HELPER.addProject(PROJECT_WITH_PARENT); + Tuple> projects = + rpc.list(rpcOptions); + com.google.api.services.cloudresourcemanager.model.Project returnedProject = + projects.y().iterator().next(); + assertFalse(PROJECT_WITH_PARENT.equals(returnedProject)); + assertEquals(PROJECT_WITH_PARENT.getProjectId(), returnedProject.getProjectId()); + assertEquals(PROJECT_WITH_PARENT.getName(), returnedProject.getName()); + assertEquals(PROJECT_WITH_PARENT.getLabels(), returnedProject.getLabels()); + assertNull(returnedProject.getParent()); + assertNull(returnedProject.getProjectNumber()); + assertNull(returnedProject.getLifecycleState()); + assertNull(returnedProject.getCreateTime()); + } + + @Test + public void testReplace() { + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + com.google.api.services.cloudresourcemanager.model.Project anotherCompleteProject = + new com.google.api.services.cloudresourcemanager.model.Project(); + anotherCompleteProject.setProjectId(COMPLETE_PROJECT.getProjectId()); + String newName = "new name"; + anotherCompleteProject.setName(newName); + Map newLabels = ImmutableMap.of("new k1", "new v1"); + anotherCompleteProject.setLabels(newLabels); + anotherCompleteProject.setProjectNumber(987654321L); + anotherCompleteProject.setCreateTime("2000-01-01T00:00:00.001Z"); + anotherCompleteProject.setLifecycleState("DELETE_REQUESTED"); + rpc.replace(anotherCompleteProject); + com.google.api.services.cloudresourcemanager.model.Project returnedProject = + RESOURCE_MANAGER_HELPER.getProject(COMPLETE_PROJECT.getProjectId()); + assertEquals(COMPLETE_PROJECT.getProjectId(), returnedProject.getProjectId()); + assertEquals(newName, returnedProject.getName()); + assertEquals(newLabels, returnedProject.getLabels()); + assertEquals(COMPLETE_PROJECT.getProjectNumber(), returnedProject.getProjectNumber()); + assertEquals(COMPLETE_PROJECT.getCreateTime(), returnedProject.getCreateTime()); + assertEquals(COMPLETE_PROJECT.getLifecycleState(), returnedProject.getLifecycleState()); + assertNull(returnedProject.getParent()); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("The caller does not have permission."); + com.google.api.services.cloudresourcemanager.model.Project nonexistantProject = + new com.google.api.services.cloudresourcemanager.model.Project(); + nonexistantProject.setProjectId("some-project-id-that-does-not-exist"); + rpc.replace(nonexistantProject); + } + + @Test + public void testReplaceWhenDeleteRequested() { + RESOURCE_MANAGER_HELPER.addProject(DELETE_REQUESTED_PROJECT); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Precondition check failed."); + com.google.api.services.cloudresourcemanager.model.Project anotherProject = + new com.google.api.services.cloudresourcemanager.model.Project(); + anotherProject.setProjectId(DELETE_REQUESTED_PROJECT.getProjectId()); + rpc.replace(anotherProject); + } + + @Test + public void testReplaceWhenDeleteInProgress() { + RESOURCE_MANAGER_HELPER.addProject(DELETE_IN_PROGRESS_PROJECT); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Precondition check failed."); + com.google.api.services.cloudresourcemanager.model.Project anotherProject = + new com.google.api.services.cloudresourcemanager.model.Project(); + anotherProject.setProjectId(DELETE_IN_PROGRESS_PROJECT.getProjectId()); + rpc.replace(anotherProject); + } + + @Test + public void testReplaceAddingParent() { + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + com.google.api.services.cloudresourcemanager.model.Project anotherProject = + new com.google.api.services.cloudresourcemanager.model.Project(); + anotherProject.setProjectId(COMPLETE_PROJECT.getProjectId()); + anotherProject.setParent(PARENT); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Request contains an invalid argument."); + rpc.replace(anotherProject); + } + + @Test + public void testReplaceRemovingParent() { + RESOURCE_MANAGER_HELPER.addProject(PROJECT_WITH_PARENT); + com.google.api.services.cloudresourcemanager.model.Project anotherProject = + new com.google.api.services.cloudresourcemanager.model.Project(); + anotherProject.setProjectId(PROJECT_WITH_PARENT.getProjectId()); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Request contains an invalid argument."); + rpc.replace(anotherProject); + } + + @Test + public void testUndelete() { + RESOURCE_MANAGER_HELPER.addProject(DELETE_REQUESTED_PROJECT); + rpc.undelete(DELETE_REQUESTED_PROJECT.getProjectId()); + com.google.api.services.cloudresourcemanager.model.Project returnedProject = + rpc.get(DELETE_REQUESTED_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); + assertEquals("ACTIVE", returnedProject.getLifecycleState()); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("The caller does not have permission."); + rpc.undelete("invalid-project-id"); + } + + @Test + public void testUndeleteWhenActive() { + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Precondition check failed."); + rpc.undelete(COMPLETE_PROJECT.getProjectId()); + } + + @Test + public void testUndeleteWhenDeleteInProgress() { + RESOURCE_MANAGER_HELPER.addProject(DELETE_IN_PROGRESS_PROJECT); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Precondition check failed."); + rpc.undelete(DELETE_IN_PROGRESS_PROJECT.getProjectId()); + } + + @Test + public void testChangeLifecycleStatus() { + assertFalse(RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS")); + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + assertTrue(RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS")); + thrown.expect(IllegalArgumentException.class); + assertFalse(RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "INVALID_STATE")); + } + + @Test + public void testAddProject() { + assertTrue(RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT)); + com.google.api.services.cloudresourcemanager.model.Project project = + new com.google.api.services.cloudresourcemanager.model.Project(); + project.setProjectId(COMPLETE_PROJECT.getProjectId()); + assertFalse(RESOURCE_MANAGER_HELPER.addProject(project)); + assertFalse( + project.equals(RESOURCE_MANAGER_HELPER.getProject(COMPLETE_PROJECT.getProjectId()))); + } + + @Test + public void testGetProject() { + assertNull(RESOURCE_MANAGER_HELPER.getProject("some-invalid-project-id")); + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + assertEquals( + COMPLETE_PROJECT, RESOURCE_MANAGER_HELPER.getProject(COMPLETE_PROJECT.getProjectId())); + } + + @Test + public void testRemoveProject() { + assertFalse(RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId())); + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + assertTrue(RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId())); + } + + @Test + public void testChangeProjectNumber() { + assertFalse(RESOURCE_MANAGER_HELPER.changeProjectNumber(COMPLETE_PROJECT.getProjectId(), 123L)); + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + assertTrue(RESOURCE_MANAGER_HELPER.changeProjectNumber(COMPLETE_PROJECT.getProjectId(), 123L)); + } + + @Test + public void testChangeCreateTime() { + assertFalse(RESOURCE_MANAGER_HELPER.changeCreateTime( + COMPLETE_PROJECT.getProjectId(), "2015-01-01T01:01:01.001Z")); + RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + assertTrue(RESOURCE_MANAGER_HELPER.changeCreateTime( + COMPLETE_PROJECT.getProjectId(), "2015-01-01T01:01:01.001Z")); + } + + @Test + public void testClearProjects() { + RESOURCE_MANAGER_HELPER.clearProjects(); + assertFalse(rpc.list(EMPTY_RPC_OPTIONS).y().iterator().hasNext()); + } +} From 978fe27693fdf0caa6627b150ac185d4b9a10366 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Tue, 8 Dec 2015 09:03:51 -0800 Subject: [PATCH 21/35] Add filtering, make projects map a ConcurrentHashMap, and fix style issues. --- .../testing/LocalResourceManagerHelper.java | 199 ++++++++++---- .../gcloud/spi/DefaultResourceManagerRpc.java | 10 +- .../LocalResourceManagerHelperTest.java | 247 +++++++++++++----- 3 files changed, 345 insertions(+), 111 deletions(-) diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java index aa9031b6b7dd..57666b88cd37 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java @@ -5,9 +5,15 @@ import com.google.api.client.json.JsonFactory; import com.google.api.services.cloudresourcemanager.model.Project; +import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Objects; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import com.sun.net.httpserver.Headers; @@ -20,15 +26,17 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; @@ -42,11 +50,14 @@ public class LocalResourceManagerHelper { private static final Logger log = Logger.getLogger(LocalResourceManagerHelper.class.getName()); private static final JsonFactory jsonFactory = new com.google.api.client.json.jackson.JacksonFactory(); - private static final int HTTP_STATUS_OK = 200; private static final Random PROJECT_NUMBER_GENERATOR = new Random(); + // see https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects + private static final Set PERMISSIBLE_PROJECT_NAME_PUNCTUATION = + ImmutableSet.of('-', '\'', '"', ' ', '!'); + private HttpServer server; - private final Map projects = new HashMap<>(); + private final ConcurrentHashMap projects = new ConcurrentHashMap<>(); static class Response { private final int code; @@ -96,7 +107,7 @@ private static String toJson( args.put("message", message); args.put("status", status); try { - return jsonFactory.toString(args); + return jsonFactory.toString(ImmutableMap.of("error", args)); } catch (IOException e) { throw new RuntimeException("Error when generating JSON error response."); } @@ -131,6 +142,7 @@ public void handle(HttpExchange exchange) throws IOException { if (response == null) { throw new UnsupportedOperationException("Request not recognized."); } + exchange.getResponseHeaders().set("Content-type", "application/json; charset=UTF-8"); exchange.sendResponseHeaders(response.code(), response.body().length()); OutputStream outputStream = exchange.getResponseBody(); outputStream.write(response.body().getBytes()); @@ -173,6 +185,9 @@ private static Map parseListOptions(String query) { case "fields": options.put("fields", argEntry[1].split(",")); break; + case "filter": + options.put("filter", argEntry[1].split(" ")); + break; case "pageToken": // support pageToken when Cloud Resource Manager supports this (#421) break; @@ -185,12 +200,66 @@ private static Map parseListOptions(String query) { return options; } + private static final boolean isValidProject(Project project) { + if (project.getProjectId() == null) { + log.info("Project ID cannot be empty."); + return false; + } + if (!isValidIdOrLabel(project.getProjectId(), 6, 30)) { + log.info("Project " + project.getProjectId() + " has an invalid ID." + + " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects" + + " for more information."); + return false; + } + if (project.getName() != null) { + for (char c : project.getName().toCharArray()) { + if (!PERMISSIBLE_PROJECT_NAME_PUNCTUATION.contains(c) && !Character.isLetterOrDigit(c)) { + log.info("Project " + project.getProjectId() + " has an invalid name." + + " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects" + + " for more information."); + return false; + } + } + } + if (project.getLabels() != null) { + if (project.getLabels().size() > 256) { + log.info("Project " + project.getProjectId() + " exceeds the limit of 256 labels."); + return false; + } + for (Map.Entry entry : project.getLabels().entrySet()) { + if (!isValidIdOrLabel(entry.getKey(), 1, 63) + || !isValidIdOrLabel(entry.getValue(), 0, 63)) { + log.info("Project " + project.getProjectId() + " has an invalid label entry." + + " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects" + + " for more information."); + return false; + } + } + } + return true; + } + + private static final boolean isValidIdOrLabel(String value, int minLength, int maxLength) { + for (char c : value.toCharArray()) { + if (c != '-' && !Character.isDigit(c) + && (!Character.isLetter(c) || !Character.isLowerCase(c))) { + return false; + } + } + if (value.length() > 0 && (!Character.isLetter(value.charAt(0)) || value.endsWith("-"))) { + return false; + } + return value.length() >= minLength && value.length() <= maxLength; + } + Response create(Project project) throws IOException { project.setLifecycleState("ACTIVE"); project.setProjectNumber(Math.abs(PROJECT_NUMBER_GENERATOR.nextLong())); project.setCreateTime(ISODateTimeFormat.dateTime().print(new Date().getTime())); Response response; - if (projects.containsKey(checkNotNull(project.getProjectId()))) { + if (!isValidProject(project)) { + response = Error.INVALID_ARGUMENT.response; + } else if (projects.containsKey(project.getProjectId())) { response = Error.ALREADY_EXISTS.response; log.info( "A project with the same project ID (" + project.getProjectId() + ") already exists."); @@ -198,7 +267,7 @@ Response create(Project project) throws IOException { projects.put(project.getProjectId(), project); String createdProjectStr = jsonFactory.toString(project); log.info("Created the following project: " + createdProjectStr); - response = new Response(HTTP_STATUS_OK, createdProjectStr); + response = new Response(HttpURLConnection.HTTP_OK, createdProjectStr); } return response; } @@ -214,7 +283,7 @@ Response delete(String projectId) { log.info("Error when deleting " + projectId + " because the lifecycle state was not ACTIVE."); } else { project.setLifecycleState("DELETE_REQUESTED"); - response = new Response(HTTP_STATUS_OK, "{}"); + response = new Response(HttpURLConnection.HTTP_OK, "{}"); log.info("Successfully requested delete for the following project: " + projectId); } return response; @@ -227,23 +296,69 @@ Response get(String projectId, String[] fields) throws IOException { log.info("Project not found."); } else { response = new Response( - HTTP_STATUS_OK, jsonFactory.toString(extractFields(projects.get(projectId), fields))); + HttpURLConnection.HTTP_OK, + jsonFactory.toString(extractFields(projects.get(projectId), fields))); } return response; } - Response list(Map options) throws IOException { + Response list(final Map options) { // Use pageSize and pageToken options when Cloud Resource Manager does so (#421) - List projectsSerialized = new ArrayList<>(); - for (Project p : projects.values()) { - projectsSerialized.add( - jsonFactory.toString(extractFields(p, (String[]) options.get("fields")))); - } + List projectsSerialized = Lists.newArrayList(Iterables.filter( + Iterables.transform(projects.values(), new Function() { + @Override + public String apply(Project p) { + try { + return includeProject(p, (String[]) options.get("filter")) + ? jsonFactory.toString(extractFields(p, (String[]) options.get("fields"))) : null; + } catch (IOException e) { + log.info("Error when serializing project " + p.getProjectId()); + return null; + } + } + }), + Predicates.notNull())); StringBuilder responseBody = new StringBuilder(); responseBody.append("{\"projects\": ["); responseBody.append(Joiner.on(",").join(projectsSerialized)); responseBody.append("]}"); - return new Response(HTTP_STATUS_OK, responseBody.toString()); + return new Response(HttpURLConnection.HTTP_OK, responseBody.toString()); + } + + private static boolean includeProject(Project project, String[] filters) { + if (filters == null) { + return true; + } + for (String filter : filters) { + String[] filterEntry = filter.toLowerCase().split(":"); + if ("id".equals(filterEntry[0])) { + if (!satisfiesFilter(project.getProjectId(), filterEntry[1])) { + return false; + } + } else if ("name".equals(filterEntry[0])) { + if (!satisfiesFilter(project.getName(), filterEntry[1])) { + return false; + } + } else if (filterEntry[0].startsWith("labels")) { + String labelKey = filterEntry[0].split("\\.")[1]; + if (project.getLabels() != null) { + String labelValue = project.getLabels().get(labelKey); + if (!satisfiesFilter(labelValue, filterEntry[1])) { + return false; + } + } + } else { + log.info("Could not parse the following filter: " + filter); + } + } + return true; + } + + private static boolean satisfiesFilter(String projectValue, String filterValue) { + if (projectValue == null) { + return false; + } + return "*".equals(filterValue) ? true : filterValue.equals(projectValue.toLowerCase()); } private static Project extractFields(Project fullProject, String[] fields) { @@ -281,7 +396,7 @@ private static Project extractFields(Project fullProject, String[] fields) { Response replace(Project project) throws IOException { Response response; - Project oldProject = projects.get(checkNotNull(project.getProjectId())); + Project oldProject = projects.get(project.getProjectId()); if (oldProject == null) { response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) log.info( @@ -302,7 +417,7 @@ Response replace(Project project) throws IOException { projects.put(project.getProjectId(), project); String updatedProjectStr = jsonFactory.toString(project); log.info("Successfully updated the project to be: " + updatedProjectStr); - response = new Response(HTTP_STATUS_OK, updatedProjectStr); + response = new Response(HttpURLConnection.HTTP_OK, updatedProjectStr); } return response; } @@ -319,7 +434,7 @@ Response undelete(String projectId) { + " because the lifecycle state was not DELETE_REQUESTED."); } else { project.setLifecycleState("ACTIVE"); - response = new Response(HTTP_STATUS_OK, "{}"); + response = new Response(HttpURLConnection.HTTP_OK, "{}"); log.info("Successfully undeleted " + projectId + "."); } return response; @@ -378,11 +493,10 @@ public void stop() { * @return true if the project was successfully added, false otherwise */ public boolean addProject(Project project) { - if (projects.containsKey(checkNotNull(project.getProjectId()))) { - return false; + if (isValidProject(project)) { + return projects.putIfAbsent(project.getProjectId(), clone(project)) == null ? true : false; } - projects.put(project.getProjectId(), clone(project)); - return true; + return false; } /** @@ -391,22 +505,20 @@ public boolean addProject(Project project) { * @return Project (if it exists) or null (if it doesn't exist) */ public Project getProject(String projectId) { - com.google.api.services.cloudresourcemanager.model.Project original = projects.get(projectId); + Project original = projects.get(projectId); return original != null ? clone(projects.get(projectId)) : null; } - private static com.google.api.services.cloudresourcemanager.model.Project clone( - com.google.api.services.cloudresourcemanager.model.Project original) { - com.google.api.services.cloudresourcemanager.model.Project clone = - new com.google.api.services.cloudresourcemanager.model.Project(); - clone.setProjectId(original.getProjectId()); - clone.setName(original.getName()); - clone.setLabels(original.getLabels()); - clone.setProjectNumber(original.getProjectNumber()); - clone.setCreateTime(original.getCreateTime()); - clone.setLifecycleState(original.getLifecycleState()); - clone.setParent(original.getParent()); - return clone; + private static Project clone(Project original) { + return new Project() + .setProjectId(original.getProjectId()) + .setName(original.getName()) + .setLabels(original.getLabels() != null ? ImmutableMap.copyOf(original.getLabels()) : null) + .setProjectNumber( + original.getProjectNumber() != null ? original.getProjectNumber().longValue() : null) + .setCreateTime(original.getCreateTime()) + .setLifecycleState(original.getLifecycleState()) + .setParent(original.getParent() != null ? original.getParent().clone() : null); } /** @@ -418,11 +530,7 @@ private static com.google.api.services.cloudresourcemanager.model.Project clone( * @return true if the project was successfully deleted, false otherwise. */ public boolean removeProject(String projectId) { - if (projects.containsKey(projectId)) { - projects.remove(checkNotNull(projectId)); - return true; - } - return false; + return projects.remove(checkNotNull(projectId)) != null ? true : false; } /** @@ -431,8 +539,7 @@ public boolean removeProject(String projectId) { * @return true if the project number was successfully changed, false otherwise. */ public boolean changeProjectNumber(String projectId, long projectNumber) { - com.google.api.services.cloudresourcemanager.model.Project project = - projects.get(checkNotNull(projectId)); + Project project = projects.get(checkNotNull(projectId)); if (project != null) { project.setProjectNumber(projectNumber); return true; @@ -450,8 +557,7 @@ public boolean changeLifecycleState(String projectId, String lifecycleState) { "ACTIVE".equals(lifecycleState) || "DELETE_REQUESTED".equals(lifecycleState) || "DELETE_IN_PROGRESS".equals(lifecycleState), "Lifecycle state must be ACTIVE, DELETE_REQUESTED, or DELETE_IN_PROGRESS"); - com.google.api.services.cloudresourcemanager.model.Project project = - projects.get(checkNotNull(projectId)); + Project project = projects.get(checkNotNull(projectId)); if (project != null) { project.setLifecycleState(lifecycleState); return true; @@ -465,10 +571,9 @@ public boolean changeLifecycleState(String projectId, String lifecycleState) { * @return true if the project create time was successfully changed, false otherwise. */ public boolean changeCreateTime(String projectId, String createTime) { - com.google.api.services.cloudresourcemanager.model.Project project = - projects.get(checkNotNull(projectId)); + Project project = projects.get(checkNotNull(projectId)); if (project != null) { - project.setCreateTime(createTime); + project.setCreateTime(checkNotNull(createTime)); return true; } return false; 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 index b9db67cdd69f..44893ade2e95 100644 --- 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 @@ -5,6 +5,7 @@ import static com.google.gcloud.spi.ResourceManagerRpc.Option.PAGE_SIZE; import static com.google.gcloud.spi.ResourceManagerRpc.Option.PAGE_TOKEN; +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; @@ -40,7 +41,7 @@ public DefaultResourceManagerRpc(ResourceManagerOptions options) { private static ResourceManagerException translate(IOException exception) { ResourceManagerException translated; if (exception instanceof GoogleJsonResponseException) { - translated = translate((GoogleJsonResponseException) exception); + translated = translate(((GoogleJsonResponseException) exception).getDetails()); } else { translated = new ResourceManagerException(0, exception.getMessage(), false); } @@ -48,10 +49,9 @@ private static ResourceManagerException translate(IOException exception) { return translated; } - private static ResourceManagerException translate(GoogleJsonResponseException exception) { - boolean retryable = RETRYABLE_CODES.contains(exception.getStatusCode()); - return new ResourceManagerException( - exception.getStatusCode(), exception.getMessage(), retryable); + private static ResourceManagerException translate(GoogleJsonError exception) { + boolean retryable = RETRYABLE_CODES.contains(exception.getCode()); + return new ResourceManagerException(exception.getCode(), exception.getMessage(), retryable); } @Override diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java index 72d063f5c99f..f1abc46f79ca 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.google.common.collect.ImmutableMap; import com.google.gcloud.resourcemanager.testing.LocalResourceManagerHelper; @@ -26,6 +27,14 @@ public class LocalResourceManagerHelperTest { private static final int PORT = 8080; + private static final long DEFAULT_PROJECT_NUMBER = 123456789L; + private static final String DEFAULT_CREATE_TIME = "2011-11-11T01:23:45.678Z"; + private static final String DEFAULT_PARENT_ID = "12345"; + private static final String DEFAULT_PARENT_TYPE = "organization"; + private static final com.google.api.services.cloudresourcemanager.model.ResourceId PARENT = + new com.google.api.services.cloudresourcemanager.model.ResourceId() + .setId(DEFAULT_PARENT_ID) + .setType(DEFAULT_PARENT_TYPE); private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); private static final LocalResourceManagerHelper RESOURCE_MANAGER_HELPER = LocalResourceManagerHelper.create(PORT); @@ -34,58 +43,47 @@ public class LocalResourceManagerHelperTest { .host("http://localhost:" + PORT) .build()); private static final com.google.api.services.cloudresourcemanager.model.Project PARTIAL_PROJECT = - new com.google.api.services.cloudresourcemanager.model.Project(); + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + "partial-project"); private static final com.google.api.services.cloudresourcemanager.model.Project COMPLETE_PROJECT = - new com.google.api.services.cloudresourcemanager.model.Project(); + new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId("complete-project") + .setName("full project") + .setLabels(ImmutableMap.of("k1", "v1", "k2", "v2")) + .setProjectNumber(DEFAULT_PROJECT_NUMBER) + .setCreateTime(DEFAULT_CREATE_TIME) + .setLifecycleState("ACTIVE"); private static final com.google.api.services.cloudresourcemanager.model.Project - DELETE_REQUESTED_PROJECT = new com.google.api.services.cloudresourcemanager.model.Project(); + DELETE_REQUESTED_PROJECT = copyFrom(COMPLETE_PROJECT) + .setProjectId("delete-requested-project-id") + .setLifecycleState("DELETE_REQUESTED"); private static final com.google.api.services.cloudresourcemanager.model.Project - DELETE_IN_PROGRESS_PROJECT = new com.google.api.services.cloudresourcemanager.model.Project(); + DELETE_IN_PROGRESS_PROJECT = copyFrom(COMPLETE_PROJECT) + .setProjectId("delete-in-progress-project-id") + .setLifecycleState("DELETE_IN_PROGRESS"); private static final com.google.api.services.cloudresourcemanager.model.Project - PROJECT_WITH_PARENT = new com.google.api.services.cloudresourcemanager.model.Project(); - private static final com.google.api.services.cloudresourcemanager.model.ResourceId PARENT = - new com.google.api.services.cloudresourcemanager.model.ResourceId(); - private static final long DEFAULT_PROJECT_NUMBER = 123456789L; - private static final String DEFAULT_CREATE_TIME = "2011-11-11T01:23:45.678Z"; - private static final String DEFAULT_PARENT_ID = "12345"; - private static final String DEFAULT_PARENT_TYPE = "organization"; + PROJECT_WITH_PARENT = + copyFrom(COMPLETE_PROJECT).setProjectId("project-with-parent-id").setParent(PARENT); @Rule public ExpectedException thrown = ExpectedException.none(); @BeforeClass public static void beforeClass() { - PARTIAL_PROJECT.setProjectId("partialProject"); - COMPLETE_PROJECT.setProjectId("completeProject"); - COMPLETE_PROJECT.setName("full project"); - COMPLETE_PROJECT.setLabels(ImmutableMap.of("k1", "v1", "k2", "v2")); - COMPLETE_PROJECT.setProjectNumber(DEFAULT_PROJECT_NUMBER); - COMPLETE_PROJECT.setCreateTime(DEFAULT_CREATE_TIME); - COMPLETE_PROJECT.setLifecycleState("ACTIVE"); - copyProperties(COMPLETE_PROJECT, PROJECT_WITH_PARENT); - PARENT.setId(DEFAULT_PARENT_ID); - PARENT.setType(DEFAULT_PARENT_TYPE); - PROJECT_WITH_PARENT.setProjectId("projectWithParentId"); - PROJECT_WITH_PARENT.setParent(PARENT); - copyProperties(COMPLETE_PROJECT, DELETE_REQUESTED_PROJECT); - DELETE_REQUESTED_PROJECT.setProjectId("deleteRequestedProjectId"); - DELETE_REQUESTED_PROJECT.setLifecycleState("DELETE_REQUESTED"); - copyProperties(COMPLETE_PROJECT, DELETE_IN_PROGRESS_PROJECT); - DELETE_IN_PROGRESS_PROJECT.setProjectId("deleteInProgressProjectId"); - DELETE_IN_PROGRESS_PROJECT.setLifecycleState("DELETE_IN_PROGRESS"); RESOURCE_MANAGER_HELPER.start(); } - private static void copyProperties( - com.google.api.services.cloudresourcemanager.model.Project from, - com.google.api.services.cloudresourcemanager.model.Project to) { - to.setProjectId(from.getProjectId()); - to.setName(from.getName()); - to.setLabels(from.getLabels()); - to.setProjectNumber(from.getProjectNumber()); - to.setCreateTime(from.getCreateTime()); - to.setLifecycleState(from.getLifecycleState()); - to.setParent(from.getParent()); + private static com.google.api.services.cloudresourcemanager.model.Project copyFrom( + com.google.api.services.cloudresourcemanager.model.Project from) { + return new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId(from.getProjectId()) + .setName(from.getName()) + .setLabels(from.getLabels() != null ? ImmutableMap.copyOf(from.getLabels()) : null) + .setProjectNumber( + from.getProjectNumber() != null ? from.getProjectNumber().longValue() : null) + .setCreateTime(from.getCreateTime()) + .setLifecycleState(from.getLifecycleState()) + .setParent(from.getParent() != null ? from.getParent().clone() : null); } @Before @@ -122,6 +120,96 @@ public void testCreate() { assertFalse(PROJECT_WITH_PARENT.getCreateTime().equals(returnedProject.getCreateTime())); } + @Test + public void testIsInvalidProjectId() { + com.google.api.services.cloudresourcemanager.model.Project project = + new com.google.api.services.cloudresourcemanager.model.Project(); + expectInvalidArgumentException(project); + project.setProjectId("abcde"); + expectInvalidArgumentException(project); + project.setProjectId("this-project-id-is-more-than-thirty-characters-long"); + expectInvalidArgumentException(project); + project.setProjectId("project-id-with-invalid-character-?"); + expectInvalidArgumentException(project); + project.setProjectId("-invalid-start-character"); + expectInvalidArgumentException(project); + project.setProjectId("invalid-ending-character-"); + expectInvalidArgumentException(project); + project.setProjectId("some-valid-project-id-12345"); + rpc.create(project); + assertNotNull(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId())); + } + + private void expectInvalidArgumentException( + com.google.api.services.cloudresourcemanager.model.Project project) { + try { + rpc.create(project); + fail("Should fail because of an invalid argument."); + } catch (ResourceManagerException e) { + assertEquals("Request contains an invalid argument.", e.getMessage()); + } + } + + @Test + public void testIsInvalidProjectName() { + com.google.api.services.cloudresourcemanager.model.Project project = + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + "some-project-id"); + rpc.create(project); + assertNull(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId()).getName()); + RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); + project.setName("This is a valid name-'\"!"); + rpc.create(project); + assertEquals( + project.getName(), RESOURCE_MANAGER_HELPER.getProject(project.getProjectId()).getName()); + RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); + project.setName("invalid-character-,"); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Request contains an invalid argument."); + rpc.create(project); + } + + @Test + public void testIsInvalidProjectLabels() { + com.google.api.services.cloudresourcemanager.model.Project project = + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + "some-valid-project-id"); + rpc.create(project); + RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); + project.setLabels(ImmutableMap.of("", "v1")); + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of( + "this-project-label-is-more-than-sixty-three-characters-long-so-it-should-fail", "v1")); + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of( + "k1", "this-project-label-is-more-than-sixty-three-characters-long-so-it-should-fail")); + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of("k1?", "v1")); + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of("k1", "v1*")); + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of("-k1", "v1")); + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of("k1", "-v1")); + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of("k1-", "v1")); + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of("k1", "v1-")); + expectInvalidArgumentException(project); + Map tooManyLabels = new HashMap<>(); + for (int i = 0; i < 257; i++) { + tooManyLabels.put("k" + Integer.toString(i), "v" + Integer.toString(i)); + } + expectInvalidArgumentException(project); + project.setLabels(ImmutableMap.of("k-1", "")); + rpc.create(project); + assertNotNull(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId())); + assertTrue(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId()) + .getLabels() + .get("k-1") + .isEmpty()); + } + @Test public void testDelete() { RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); @@ -196,7 +284,7 @@ public void testList() { } @Test - public void testListWithOptions() { + public void testListFieldOptions() { Map rpcOptions = new HashMap<>(); rpcOptions.put(ResourceManagerRpc.Option.FIELDS, "projectId,name,labels"); rpcOptions.put(ResourceManagerRpc.Option.PAGE_TOKEN, "somePageToken"); @@ -216,19 +304,58 @@ public void testListWithOptions() { assertNull(returnedProject.getCreateTime()); } + @Test + public void testListFilterOptions() { + Map rpcFilterOptions = new HashMap<>(); + rpcFilterOptions.put( + ResourceManagerRpc.Option.FILTER, "id:* name:myProject labels.color:blue LABELS.SIZE:*"); + com.google.api.services.cloudresourcemanager.model.Project matchingProject = + new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId("matching-project") + .setName("MyProject") + .setProjectNumber(DEFAULT_PROJECT_NUMBER) + .setLabels(ImmutableMap.of("Color", "blue", "size", "Big")); + com.google.api.services.cloudresourcemanager.model.Project nonMatchingProject1 = + new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId("non-matching-project1") + .setName("myProject") + .setProjectNumber(DEFAULT_PROJECT_NUMBER); + nonMatchingProject1.setLabels(ImmutableMap.of("color", "blue")); + com.google.api.services.cloudresourcemanager.model.Project nonMatchingProject2 = + new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId("non-matching-project2") + .setName("myProj") + .setProjectNumber(DEFAULT_PROJECT_NUMBER) + .setLabels(ImmutableMap.of("color", "blue", "size", "big")); + com.google.api.services.cloudresourcemanager.model.Project nonMatchingProject3 = + new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId("non-matching-project3") + .setProjectNumber(DEFAULT_PROJECT_NUMBER); + RESOURCE_MANAGER_HELPER.addProject(matchingProject); + RESOURCE_MANAGER_HELPER.addProject(nonMatchingProject1); + RESOURCE_MANAGER_HELPER.addProject(nonMatchingProject2); + RESOURCE_MANAGER_HELPER.addProject(nonMatchingProject3); + for (com.google.api.services.cloudresourcemanager.model.Project p : + rpc.list(rpcFilterOptions).y()) { + assertFalse(p.equals(nonMatchingProject1)); + assertFalse(p.equals(nonMatchingProject2)); + assertTrue(p.equals(matchingProject)); + } + } + @Test public void testReplace() { RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); - com.google.api.services.cloudresourcemanager.model.Project anotherCompleteProject = - new com.google.api.services.cloudresourcemanager.model.Project(); - anotherCompleteProject.setProjectId(COMPLETE_PROJECT.getProjectId()); String newName = "new name"; - anotherCompleteProject.setName(newName); Map newLabels = ImmutableMap.of("new k1", "new v1"); - anotherCompleteProject.setLabels(newLabels); - anotherCompleteProject.setProjectNumber(987654321L); - anotherCompleteProject.setCreateTime("2000-01-01T00:00:00.001Z"); - anotherCompleteProject.setLifecycleState("DELETE_REQUESTED"); + com.google.api.services.cloudresourcemanager.model.Project anotherCompleteProject = + new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId(COMPLETE_PROJECT.getProjectId()) + .setName(newName) + .setLabels(newLabels) + .setProjectNumber(987654321L) + .setCreateTime("2000-01-01T00:00:00.001Z") + .setLifecycleState("DELETE_REQUESTED"); rpc.replace(anotherCompleteProject); com.google.api.services.cloudresourcemanager.model.Project returnedProject = RESOURCE_MANAGER_HELPER.getProject(COMPLETE_PROJECT.getProjectId()); @@ -253,8 +380,8 @@ public void testReplaceWhenDeleteRequested() { thrown.expect(ResourceManagerException.class); thrown.expectMessage("Precondition check failed."); com.google.api.services.cloudresourcemanager.model.Project anotherProject = - new com.google.api.services.cloudresourcemanager.model.Project(); - anotherProject.setProjectId(DELETE_REQUESTED_PROJECT.getProjectId()); + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + DELETE_REQUESTED_PROJECT.getProjectId()); rpc.replace(anotherProject); } @@ -264,8 +391,8 @@ public void testReplaceWhenDeleteInProgress() { thrown.expect(ResourceManagerException.class); thrown.expectMessage("Precondition check failed."); com.google.api.services.cloudresourcemanager.model.Project anotherProject = - new com.google.api.services.cloudresourcemanager.model.Project(); - anotherProject.setProjectId(DELETE_IN_PROGRESS_PROJECT.getProjectId()); + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + DELETE_IN_PROGRESS_PROJECT.getProjectId()); rpc.replace(anotherProject); } @@ -273,9 +400,9 @@ public void testReplaceWhenDeleteInProgress() { public void testReplaceAddingParent() { RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); com.google.api.services.cloudresourcemanager.model.Project anotherProject = - new com.google.api.services.cloudresourcemanager.model.Project(); - anotherProject.setProjectId(COMPLETE_PROJECT.getProjectId()); - anotherProject.setParent(PARENT); + new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId(COMPLETE_PROJECT.getProjectId()) + .setParent(PARENT); thrown.expect(ResourceManagerException.class); thrown.expectMessage("Request contains an invalid argument."); rpc.replace(anotherProject); @@ -285,8 +412,8 @@ public void testReplaceAddingParent() { public void testReplaceRemovingParent() { RESOURCE_MANAGER_HELPER.addProject(PROJECT_WITH_PARENT); com.google.api.services.cloudresourcemanager.model.Project anotherProject = - new com.google.api.services.cloudresourcemanager.model.Project(); - anotherProject.setProjectId(PROJECT_WITH_PARENT.getProjectId()); + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + PROJECT_WITH_PARENT.getProjectId()); thrown.expect(ResourceManagerException.class); thrown.expectMessage("Request contains an invalid argument."); rpc.replace(anotherProject); @@ -336,11 +463,13 @@ public void testChangeLifecycleStatus() { public void testAddProject() { assertTrue(RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT)); com.google.api.services.cloudresourcemanager.model.Project project = - new com.google.api.services.cloudresourcemanager.model.Project(); - project.setProjectId(COMPLETE_PROJECT.getProjectId()); + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + COMPLETE_PROJECT.getProjectId()); assertFalse(RESOURCE_MANAGER_HELPER.addProject(project)); assertFalse( project.equals(RESOURCE_MANAGER_HELPER.getProject(COMPLETE_PROJECT.getProjectId()))); + assertFalse(RESOURCE_MANAGER_HELPER.addProject( + new com.google.api.services.cloudresourcemanager.model.Project())); } @Test From a670d21b0df095e118399b9a4a9b812795c40f3a Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Wed, 9 Dec 2015 16:05:29 -0800 Subject: [PATCH 22/35] Make error messages more informative, propagate all server exceptions to the user, automatically allocate ephemeral port --- .../testing/LocalResourceManagerHelper.java | 319 +++++++++--------- .../resourcemanager/testing/package-info.java | 3 - .../LocalResourceManagerHelperTest.java | 209 ++++++++---- 3 files changed, 299 insertions(+), 232 deletions(-) diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java index 57666b88cd37..d7934fef72c8 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java @@ -1,19 +1,16 @@ package com.google.gcloud.resourcemanager.testing; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.client.json.JsonFactory; import com.google.api.services.cloudresourcemanager.model.Project; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Objects; -import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import com.sun.net.httpserver.Headers; @@ -28,29 +25,28 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; -import java.net.ServerSocket; import java.nio.charset.StandardCharsets; -import java.util.Date; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; import java.util.zip.GZIPInputStream; /** * Utility to create a local Resource Manager mock for testing. * - * The mock runs in a separate thread, listening to port 8080 for HTTP requests. + *

              The mock runs in a separate thread, listening for HTTP requests on the local machine at an + * ephemeral port. */ @SuppressWarnings("restriction") public class LocalResourceManagerHelper { - private static final Logger log = Logger.getLogger(LocalResourceManagerHelper.class.getName()); private static final JsonFactory jsonFactory = new com.google.api.client.json.jackson.JacksonFactory(); private static final Random PROJECT_NUMBER_GENERATOR = new Random(); + private static final String VERSION = "v1beta1"; // see https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects private static final Set PERMISSIBLE_PROJECT_NAME_PUNCTUATION = @@ -58,6 +54,7 @@ public class LocalResourceManagerHelper { private HttpServer server; private final ConcurrentHashMap projects = new ConcurrentHashMap<>(); + private final int port; static class Response { private final int code; @@ -78,39 +75,46 @@ String body() { } enum Error { - ALREADY_EXISTS( - 409, "global", "Requested entity already exists.", "alreadyExists", "ALREADY_EXISTS"), - PERMISSION_DENIED( - 403, "global", "The caller does not have permission.", "forbidden", "PERMISSION_DENIED"), - FAILED_PRECONDITION( // change this error code to 412 when #440 is fixed - 400, "global", "Precondition check failed.", "failedPrecondition", "FAILED_PRECONDITION"), - INVALID_ARGUMENT( // change this error code to 412 when #440 is fixed - 400, "global", "Request contains an invalid argument.", "badRequest", - "INVALID_ARGUMENT"); + ALREADY_EXISTS(409, "global", "alreadyExists", "ALREADY_EXISTS"), + PERMISSION_DENIED(403, "global", "forbidden", "PERMISSION_DENIED"), + // change failed precondition error code to 412 when #440 is fixed + FAILED_PRECONDITION(400, "global", "failedPrecondition", "FAILED_PRECONDITION"), + // change invalid argument error code to 412 when #440 is fixed + INVALID_ARGUMENT(400, "global", "badRequest", "INVALID_ARGUMENT"), + BAD_REQUEST(400, "global", "badRequest", "BAD_REQUEST"), + INTERNAL_ERROR(500, "global", "internalError", "INTERNAL_ERROR"); - private final Response response; + private final int code; + private final String domain; + private final String reason; + private final String status; + + Error(int code, String domain, String reason, String status) { + this.code = code; + this.domain = domain; + this.reason = reason; + this.status = status; + } - Error(int code, String domain, String message, String reason, String status) { - this.response = new Response(code, toJson(code, domain, message, reason, status)); + Response response(String message) { + try { + return new Response(code, toJson(message)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response("Error when generating JSON error response"); + } } - private static String toJson( - int code, String domain, String message, String reason, String status) { + private String toJson(String message) throws IOException { Map args = new HashMap<>(); - Map nestedArgs = new HashMap<>(); - nestedArgs.put("domain", domain); - nestedArgs.put("message", message); - nestedArgs.put("reason", reason); - List> errors = ImmutableList.of(nestedArgs); - args.put("errors", errors); + Map errors = new HashMap<>(); + errors.put("domain", domain); + errors.put("message", message); + errors.put("reason", reason); + args.put("errors", ImmutableList.of(errors)); args.put("code", code); args.put("message", message); args.put("status", status); - try { - return jsonFactory.toString(ImmutableMap.of("error", args)); - } catch (IOException e) { - throw new RuntimeException("Error when generating JSON error response."); - } + return jsonFactory.toString(ImmutableMap.of("error", args)); } } @@ -121,9 +125,13 @@ public void handle(HttpExchange exchange) throws IOException { String path = exchange.getRequestURI().getPath(); String requestMethod = exchange.getRequestMethod(); Response response = null; - if (requestMethod.equals("POST") && path.startsWith("/v1beta1/projects")) { + if (requestMethod.equals("POST") && path.startsWith("/" + VERSION + "/projects")) { if (path.contains("undelete")) { - response = undelete(projectIdFromURI(path)); + try { + response = undelete(projectIdFromURI(path)); + } catch (IOException e) { + response = Error.BAD_REQUEST.response(e.getMessage()); + } } else { String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); @@ -131,48 +139,50 @@ public void handle(HttpExchange exchange) throws IOException { } } else if (requestMethod.equals("DELETE")) { response = delete(projectIdFromURI(path)); - } else if (requestMethod.equals("GET") && path.startsWith("/v1beta1/projects/")) { - response = get(projectIdFromURI(path), parseFields(exchange.getRequestURI().getQuery())); } else if (requestMethod.equals("GET")) { - response = list(parseListOptions(exchange.getRequestURI().getQuery())); + if (path.startsWith("/" + VERSION + "/projects/")) { + response = get(projectIdFromURI(path), parseFields(exchange.getRequestURI().getQuery())); + } else { + response = list(parseListOptions(exchange.getRequestURI().getQuery())); + } } else if (requestMethod.equals("PUT")) { String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); response = replace(jsonFactory.fromString(requestBody, Project.class)); } - if (response == null) { - throw new UnsupportedOperationException("Request not recognized."); - } + response = firstNonNull( + response, Error.BAD_REQUEST.response("The server could not understand the request.")); exchange.getResponseHeaders().set("Content-type", "application/json; charset=UTF-8"); exchange.sendResponseHeaders(response.code(), response.body().length()); OutputStream outputStream = exchange.getResponseBody(); - outputStream.write(response.body().getBytes()); + outputStream.write(response.body().getBytes(StandardCharsets.UTF_8)); outputStream.close(); } } private static String decodeContent(Headers headers, InputStream inputStream) throws IOException { List contentEncoding = headers.get("Content-encoding"); - byte[] bytes; + InputStream input = inputStream; if (contentEncoding != null && contentEncoding.size() > 0 && contentEncoding.get(0).contains("gzip")) { - bytes = ByteStreams.toByteArray(new GZIPInputStream(inputStream)); - log.fine("Content-encoding is in gzip format. Decoded successfully."); - } else { - bytes = ByteStreams.toByteArray(inputStream); + input = new GZIPInputStream(inputStream); } - return new String(bytes, StandardCharsets.UTF_8); + return new String(ByteStreams.toByteArray(input), StandardCharsets.UTF_8); } - private static String projectIdFromURI(String path) { + private static String projectIdFromURI(String path) throws IOException { String[] pathSplit = path.split("/"); if (pathSplit.length < 4) { - throw new IllegalArgumentException("This path doesn't have a project ID"); + throw new IOException("The path '" + path + "' doesn't have a project ID"); } - return path.split("/")[3].split(":")[0]; + return pathSplit[3].split(":")[0]; } private static String[] parseFields(String query) { - return query != null ? query.split("=")[1].split(",") : null; + if (query != null && !query.isEmpty()) { + String[] querySplit = query.split("="); + return querySplit.length > 1 ? querySplit[1].split(",") : null; + } + return null; } private static Map parseListOptions(String query) { @@ -200,43 +210,38 @@ private static Map parseListOptions(String query) { return options; } - private static final boolean isValidProject(Project project) { + private static final String checkForProjectErrors(Project project) { if (project.getProjectId() == null) { - log.info("Project ID cannot be empty."); - return false; + return "Project ID cannot be empty."; } if (!isValidIdOrLabel(project.getProjectId(), 6, 30)) { - log.info("Project " + project.getProjectId() + " has an invalid ID." - + " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects" - + " for more information."); - return false; + return "Project " + project.getProjectId() + " has an invalid ID." + + " See https://cloud.google.com/resource-manager/reference/rest/" + VERSION + "/projects" + + " for more information."; } if (project.getName() != null) { for (char c : project.getName().toCharArray()) { if (!PERMISSIBLE_PROJECT_NAME_PUNCTUATION.contains(c) && !Character.isLetterOrDigit(c)) { - log.info("Project " + project.getProjectId() + " has an invalid name." - + " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects" - + " for more information."); - return false; + return "Project " + project.getProjectId() + " has an invalid name." + + " See https://cloud.google.com/resource-manager/reference/rest/" + VERSION + + "/projects for more information."; } } } if (project.getLabels() != null) { if (project.getLabels().size() > 256) { - log.info("Project " + project.getProjectId() + " exceeds the limit of 256 labels."); - return false; + return "Project " + project.getProjectId() + " exceeds the limit of 256 labels."; } for (Map.Entry entry : project.getLabels().entrySet()) { if (!isValidIdOrLabel(entry.getKey(), 1, 63) || !isValidIdOrLabel(entry.getValue(), 0, 63)) { - log.info("Project " + project.getProjectId() + " has an invalid label entry." - + " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects" - + " for more information."); - return false; + return "Project " + project.getProjectId() + " has an invalid label entry." + + " See https://cloud.google.com/resource-manager/reference/rest/" + VERSION + + "/projects for more information."; } } } - return true; + return null; } private static final boolean isValidIdOrLabel(String value, int minLength, int maxLength) { @@ -252,22 +257,26 @@ private static final boolean isValidIdOrLabel(String value, int minLength, int m return value.length() >= minLength && value.length() <= maxLength; } - Response create(Project project) throws IOException { + Response create(Project project) { project.setLifecycleState("ACTIVE"); - project.setProjectNumber(Math.abs(PROJECT_NUMBER_GENERATOR.nextLong())); - project.setCreateTime(ISODateTimeFormat.dateTime().print(new Date().getTime())); + project.setProjectNumber(Math.abs(PROJECT_NUMBER_GENERATOR.nextLong() % Long.MAX_VALUE)); + project.setCreateTime(ISODateTimeFormat.dateTime().print(System.currentTimeMillis())); Response response; - if (!isValidProject(project)) { - response = Error.INVALID_ARGUMENT.response; + String customErrorMessage = checkForProjectErrors(project); + if (customErrorMessage != null) { + response = Error.INVALID_ARGUMENT.response(customErrorMessage); } else if (projects.containsKey(project.getProjectId())) { - response = Error.ALREADY_EXISTS.response; - log.info( + response = Error.ALREADY_EXISTS.response( "A project with the same project ID (" + project.getProjectId() + ") already exists."); } else { projects.put(project.getProjectId(), project); - String createdProjectStr = jsonFactory.toString(project); - log.info("Created the following project: " + createdProjectStr); - response = new Response(HttpURLConnection.HTTP_OK, createdProjectStr); + try { + String createdProjectStr = jsonFactory.toString(project); + response = new Response(HttpURLConnection.HTTP_OK, createdProjectStr); + } catch (IOException e) { + response = + Error.INTERNAL_ERROR.response("Error serializing project " + project.getProjectId()); + } } return response; } @@ -276,48 +285,51 @@ Response delete(String projectId) { Project project = projects.get(checkNotNull(projectId)); Response response; if (project == null) { - response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) - log.info("Error when deleting " + projectId + " because the project was not found."); + // when possible, change this to 404 (#440) + response = Error.PERMISSION_DENIED.response( + "Error when deleting " + projectId + " because the project was not found."); } else if (!project.getLifecycleState().equals("ACTIVE")) { - response = Error.FAILED_PRECONDITION.response; - log.info("Error when deleting " + projectId + " because the lifecycle state was not ACTIVE."); + response = Error.FAILED_PRECONDITION.response( + "Error when deleting " + projectId + " because the lifecycle state was not ACTIVE."); } else { project.setLifecycleState("DELETE_REQUESTED"); response = new Response(HttpURLConnection.HTTP_OK, "{}"); - log.info("Successfully requested delete for the following project: " + projectId); } return response; } - Response get(String projectId, String[] fields) throws IOException { - Response response; + Response get(String projectId, String[] fields) { if (!projects.containsKey(checkNotNull(projectId))) { - response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) - log.info("Project not found."); - } else { - response = new Response( - HttpURLConnection.HTTP_OK, - jsonFactory.toString(extractFields(projects.get(projectId), fields))); + // when possible, change this to 404 (#440) + return Error.PERMISSION_DENIED.response("Project " + projectId + " not found."); + } + Project project = projects.get(projectId); + try { + return new Response( + HttpURLConnection.HTTP_OK, jsonFactory.toString(extractFields(project, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response( + "Error when serializing project " + project.getProjectId()); } - return response; } Response list(final Map options) { // Use pageSize and pageToken options when Cloud Resource Manager does so (#421) - List projectsSerialized = Lists.newArrayList(Iterables.filter( - Iterables.transform(projects.values(), new Function() { - @Override - public String apply(Project p) { - try { - return includeProject(p, (String[]) options.get("filter")) - ? jsonFactory.toString(extractFields(p, (String[]) options.get("fields"))) : null; - } catch (IOException e) { - log.info("Error when serializing project " + p.getProjectId()); - return null; - } - } - }), - Predicates.notNull())); + List projectsSerialized = new ArrayList<>(); + for (Project p : projects.values()) { + Boolean includeProject = includeProject(p, (String[]) options.get("filter")); + if (includeProject) { + try { + projectsSerialized.add( + jsonFactory.toString(extractFields(p, (String[]) options.get("fields")))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response( + "Error when serializing project " + p.getProjectId()); + } + } else if (includeProject == null) { + return Error.INVALID_ARGUMENT.response("Could not parse the filter."); + } + } StringBuilder responseBody = new StringBuilder(); responseBody.append("{\"projects\": ["); responseBody.append(Joiner.on(",").join(projectsSerialized)); @@ -325,7 +337,7 @@ public String apply(Project p) { return new Response(HttpURLConnection.HTTP_OK, responseBody.toString()); } - private static boolean includeProject(Project project, String[] filters) { + private static Boolean includeProject(Project project, String[] filters) { if (filters == null) { return true; } @@ -348,7 +360,7 @@ private static boolean includeProject(Project project, String[] filters) { } } } else { - log.info("Could not parse the following filter: " + filter); + return null; } } return true; @@ -389,86 +401,78 @@ private static Project extractFields(Project fullProject, String[] fields) { case "projectNumber": project.setProjectNumber(fullProject.getProjectNumber()); break; - } } + } return project; } - Response replace(Project project) throws IOException { - Response response; + Response replace(Project project) { Project oldProject = projects.get(project.getProjectId()); if (oldProject == null) { - response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) - log.info( + // when possible, change this to 404 (#440) + return Error.PERMISSION_DENIED.response( "Error when replacing " + project.getProjectId() + " because the project was not found."); } else if (!oldProject.getLifecycleState().equals("ACTIVE")) { - response = Error.FAILED_PRECONDITION.response; - log.info("Error when replacing " + project.getProjectId() + return Error.FAILED_PRECONDITION.response("Error when replacing " + project.getProjectId() + " because the lifecycle state was not ACTIVE."); } else if (!Objects.equal(oldProject.getParent(), project.getParent())) { - response = Error.INVALID_ARGUMENT.response; - log.info("The server currently only supports setting the parent once " + return Error.INVALID_ARGUMENT.response( + "The server currently only supports setting the parent once " + "and does not allow unsetting it."); - } else { - project.setLifecycleState("ACTIVE"); - project.setProjectNumber(oldProject.getProjectNumber()); - project.setCreateTime(oldProject.getCreateTime()); - project.setParent(oldProject.getParent()); - projects.put(project.getProjectId(), project); - String updatedProjectStr = jsonFactory.toString(project); - log.info("Successfully updated the project to be: " + updatedProjectStr); - response = new Response(HttpURLConnection.HTTP_OK, updatedProjectStr); } - return response; + project.setLifecycleState("ACTIVE"); + project.setProjectNumber(oldProject.getProjectNumber()); + project.setCreateTime(oldProject.getCreateTime()); + project.setParent(oldProject.getParent()); + projects.put(project.getProjectId(), project); + try { + return new Response(HttpURLConnection.HTTP_OK, jsonFactory.toString(project)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response( + "Error when serializing project " + project.getProjectId()); + } } Response undelete(String projectId) { Project project = projects.get(checkNotNull(projectId)); Response response; if (project == null) { - response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440) - log.info("Error when undeleting " + projectId + " because the project was not found."); + // when possible, change this to 404 (#440) + response = Error.PERMISSION_DENIED.response( + "Error when undeleting " + projectId + " because the project was not found."); } else if (!project.getLifecycleState().equals("DELETE_REQUESTED")) { - response = Error.FAILED_PRECONDITION.response; - log.info("Error when undeleting " + projectId + response = Error.FAILED_PRECONDITION.response("Error when undeleting " + projectId + " because the lifecycle state was not DELETE_REQUESTED."); } else { project.setLifecycleState("ACTIVE"); response = new Response(HttpURLConnection.HTTP_OK, "{}"); - log.info("Successfully undeleted " + projectId + "."); } return response; } - private LocalResourceManagerHelper(int port) { + private LocalResourceManagerHelper() { + InetSocketAddress addr = new InetSocketAddress(0); try { - this.server = HttpServer.create(new InetSocketAddress(port), 0); - this.server.createContext("/", new RequestHandler()); + server = HttpServer.create(addr, 0); + port = server.getAddress().getPort(); + server.createContext("/", new RequestHandler()); } catch (IOException e) { - log.severe("Could not create the mock Resource Manager."); + throw new RuntimeException("Could not bind the mock Resource Manager server.", e); } } /** - * Creates a LocalResourceManagerHelper object that listens to requests on the local machine at - * the port specified. + * Creates a LocalResourceManagerHelper object that listens to requests on the local machine. */ - public static LocalResourceManagerHelper create(int port) { - return new LocalResourceManagerHelper(port); + public static LocalResourceManagerHelper create() { + return new LocalResourceManagerHelper(); } /** - * Returns an available port on the local machine. - * - * This port can be used to set the host in ResourceManagerOptions and to specify the port to - * which the Resource Manager mock should listen. + * Returns the port that the LocalResourceManagerHelper listens to for requests. */ - public static int findAvailablePort(int defaultPort) { - try (ServerSocket tempSocket = new ServerSocket(0)) { - return tempSocket.getLocalPort(); - } catch (IOException e) { - return defaultPort; - } + public int port() { + return port; } /** @@ -488,12 +492,13 @@ public void stop() { /** * Utility method to add a project. * - * Will not overwrite an existing project with the same ID. + *

              Will not overwrite an existing project with the same ID. * - * @return true if the project was successfully added, false otherwise + * @return true if the project was successfully added, false if the project already exists or is + * invalid */ public boolean addProject(Project project) { - if (isValidProject(project)) { + if (checkForProjectErrors(project) == null) { return projects.putIfAbsent(project.getProjectId(), clone(project)) == null ? true : false; } return false; @@ -524,7 +529,7 @@ private static Project clone(Project original) { /** * Utility method to remove the specified project. * - * This method can be used to fully remove a project (to mimic when the server completely + *

              This method can be used to fully remove a project (to mimic when the server completely * deletes a project). * * @return true if the project was successfully deleted, false otherwise. diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java index 6116cac5cbaf..a2c07904ddbd 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java @@ -21,10 +21,7 @@ *

              Before the test: *

               {@code
                * LocalResourceManagerHelper resourceManagerHelper = LocalResourceManagerHelper.create();
              - * // TODO(ajaykannan): implement the following line when ResourceManagerImpl is checked in.
                * ResourceManager resourceManager = resourceManagerHelper.options().service();
              - * implement this in the next PR
              - * resourceManager.list();
                * } 
              * *

              After the test: diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java index f1abc46f79ca..ac329e9aa6d0 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java @@ -16,9 +16,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.util.HashMap; import java.util.Iterator; @@ -26,7 +24,6 @@ public class LocalResourceManagerHelperTest { - private static final int PORT = 8080; private static final long DEFAULT_PROJECT_NUMBER = 123456789L; private static final String DEFAULT_CREATE_TIME = "2011-11-11T01:23:45.678Z"; private static final String DEFAULT_PARENT_ID = "12345"; @@ -37,10 +34,10 @@ public class LocalResourceManagerHelperTest { .setType(DEFAULT_PARENT_TYPE); private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); private static final LocalResourceManagerHelper RESOURCE_MANAGER_HELPER = - LocalResourceManagerHelper.create(PORT); + LocalResourceManagerHelper.create(); private static final ResourceManagerRpc rpc = new DefaultResourceManagerRpc( ResourceManagerOptions.builder() - .host("http://localhost:" + PORT) + .host("http://localhost:" + RESOURCE_MANAGER_HELPER.port()) .build()); private static final com.google.api.services.cloudresourcemanager.model.Project PARTIAL_PROJECT = new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( @@ -65,9 +62,6 @@ public class LocalResourceManagerHelperTest { PROJECT_WITH_PARENT = copyFrom(COMPLETE_PROJECT).setProjectId("project-with-parent-id").setParent(PARENT); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @BeforeClass public static void beforeClass() { RESOURCE_MANAGER_HELPER.start(); @@ -95,7 +89,7 @@ public void setUp() { public static void afterClass() { RESOURCE_MANAGER_HELPER.stop(); } - + @Test public void testCreate() { com.google.api.services.cloudresourcemanager.model.Project returnedProject = @@ -107,9 +101,14 @@ public void testCreate() { assertNull(returnedProject.getParent()); assertNotNull(returnedProject.getProjectNumber()); assertNotNull(returnedProject.getCreateTime()); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Requested entity already exists."); - rpc.create(PARTIAL_PROJECT); + try { + rpc.create(PARTIAL_PROJECT); + fail("Should fail, project already exists."); + } catch (ResourceManagerException e) { + assertEquals(409, e.code()); + assertTrue(e.getMessage().startsWith("A project with the same project ID") + && e.getMessage().endsWith("already exists.")); + } returnedProject = rpc.create(PROJECT_WITH_PARENT); assertEquals(PROJECT_WITH_PARENT.getProjectId(), returnedProject.getProjectId()); assertEquals("ACTIVE", returnedProject.getLifecycleState()); @@ -124,29 +123,32 @@ public void testCreate() { public void testIsInvalidProjectId() { com.google.api.services.cloudresourcemanager.model.Project project = new com.google.api.services.cloudresourcemanager.model.Project(); - expectInvalidArgumentException(project); + String invalidIDMessageSubstring = "invalid ID"; + expectInvalidArgumentException(project, "Project ID cannot be empty."); project.setProjectId("abcde"); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidIDMessageSubstring); project.setProjectId("this-project-id-is-more-than-thirty-characters-long"); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidIDMessageSubstring); project.setProjectId("project-id-with-invalid-character-?"); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidIDMessageSubstring); project.setProjectId("-invalid-start-character"); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidIDMessageSubstring); project.setProjectId("invalid-ending-character-"); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidIDMessageSubstring); project.setProjectId("some-valid-project-id-12345"); rpc.create(project); assertNotNull(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId())); } private void expectInvalidArgumentException( - com.google.api.services.cloudresourcemanager.model.Project project) { + com.google.api.services.cloudresourcemanager.model.Project project, + String errorMessageSubstring) { try { rpc.create(project); fail("Should fail because of an invalid argument."); } catch (ResourceManagerException e) { - assertEquals("Request contains an invalid argument.", e.getMessage()); + assertEquals(400, e.code()); + assertTrue(e.getMessage().contains(errorMessageSubstring)); } } @@ -164,9 +166,13 @@ public void testIsInvalidProjectName() { project.getName(), RESOURCE_MANAGER_HELPER.getProject(project.getProjectId()).getName()); RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); project.setName("invalid-character-,"); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Request contains an invalid argument."); - rpc.create(project); + try { + rpc.create(project); + fail("Should fail because of invalid project name."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertTrue(e.getMessage().contains("invalid name")); + } } @Test @@ -175,32 +181,33 @@ public void testIsInvalidProjectLabels() { new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( "some-valid-project-id"); rpc.create(project); + String invalidLabelMessageSubstring = "invalid label entry"; RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); project.setLabels(ImmutableMap.of("", "v1")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of( "this-project-label-is-more-than-sixty-three-characters-long-so-it-should-fail", "v1")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of( "k1", "this-project-label-is-more-than-sixty-three-characters-long-so-it-should-fail")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of("k1?", "v1")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of("k1", "v1*")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of("-k1", "v1")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of("k1", "-v1")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of("k1-", "v1")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of("k1", "v1-")); - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); Map tooManyLabels = new HashMap<>(); for (int i = 0; i < 257; i++) { tooManyLabels.put("k" + Integer.toString(i), "v" + Integer.toString(i)); } - expectInvalidArgumentException(project); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of("k-1", "")); rpc.create(project); assertNotNull(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId())); @@ -214,9 +221,13 @@ public void testIsInvalidProjectLabels() { public void testDelete() { RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); rpc.delete(COMPLETE_PROJECT.getProjectId()); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("The caller does not have permission."); - rpc.delete("some-nonexistant-project-id"); + try { + rpc.delete("some-nonexistant-project-id"); + fail("Should fail because the project was already deleted."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("the project was not found")); + } } @Test @@ -224,9 +235,13 @@ public void testDeleteWhenDeleteInProgress() { RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); RESOURCE_MANAGER_HELPER.changeLifecycleState( COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS"); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Precondition check failed."); - rpc.delete(COMPLETE_PROJECT.getProjectId()); + try { + rpc.delete(COMPLETE_PROJECT.getProjectId()); + fail("Should fail because the project is not ACTIVE."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertTrue(e.getMessage().contains("the lifecycle state was not ACTIVE")); + } } @Test @@ -234,9 +249,13 @@ public void testDeleteWhenDeleteRequested() { RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); RESOURCE_MANAGER_HELPER.changeLifecycleState( COMPLETE_PROJECT.getProjectId(), "DELETE_REQUESTED"); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Precondition check failed."); - rpc.delete(COMPLETE_PROJECT.getProjectId()); + try { + rpc.delete(COMPLETE_PROJECT.getProjectId()); + fail("Should fail because the project is not ACTIVE."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertTrue(e.getMessage().contains("the lifecycle state was not ACTIVE")); + } } @Test @@ -245,10 +264,14 @@ public void testGet() { com.google.api.services.cloudresourcemanager.model.Project returnedProject = rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); assertEquals(COMPLETE_PROJECT, returnedProject); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("The caller does not have permission."); RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId()); - rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); + try { + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("not found")); + } } @Test @@ -366,34 +389,46 @@ public void testReplace() { assertEquals(COMPLETE_PROJECT.getCreateTime(), returnedProject.getCreateTime()); assertEquals(COMPLETE_PROJECT.getLifecycleState(), returnedProject.getLifecycleState()); assertNull(returnedProject.getParent()); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("The caller does not have permission."); com.google.api.services.cloudresourcemanager.model.Project nonexistantProject = new com.google.api.services.cloudresourcemanager.model.Project(); nonexistantProject.setProjectId("some-project-id-that-does-not-exist"); - rpc.replace(nonexistantProject); + try { + rpc.replace(nonexistantProject); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("the project was not found")); + } } @Test public void testReplaceWhenDeleteRequested() { RESOURCE_MANAGER_HELPER.addProject(DELETE_REQUESTED_PROJECT); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Precondition check failed."); com.google.api.services.cloudresourcemanager.model.Project anotherProject = new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( DELETE_REQUESTED_PROJECT.getProjectId()); - rpc.replace(anotherProject); + try { + rpc.replace(anotherProject); + fail("Should fail because the project is not ACTIVE."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertTrue(e.getMessage().contains("the lifecycle state was not ACTIVE")); + } } @Test public void testReplaceWhenDeleteInProgress() { RESOURCE_MANAGER_HELPER.addProject(DELETE_IN_PROGRESS_PROJECT); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Precondition check failed."); com.google.api.services.cloudresourcemanager.model.Project anotherProject = new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( DELETE_IN_PROGRESS_PROJECT.getProjectId()); - rpc.replace(anotherProject); + try { + rpc.replace(anotherProject); + fail("Should fail because the project is not ACTIVE."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertTrue(e.getMessage().contains("the lifecycle state was not ACTIVE")); + } } @Test @@ -403,9 +438,16 @@ public void testReplaceAddingParent() { new com.google.api.services.cloudresourcemanager.model.Project() .setProjectId(COMPLETE_PROJECT.getProjectId()) .setParent(PARENT); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Request contains an invalid argument."); - rpc.replace(anotherProject); + try { + rpc.replace(anotherProject); + fail("Should fail because the project's parent was modified after creation."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertEquals( + "The server currently only supports setting the parent once " + + "and does not allow unsetting it.", + e.getMessage()); + } } @Test @@ -414,9 +456,16 @@ public void testReplaceRemovingParent() { com.google.api.services.cloudresourcemanager.model.Project anotherProject = new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( PROJECT_WITH_PARENT.getProjectId()); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Request contains an invalid argument."); - rpc.replace(anotherProject); + try { + rpc.replace(anotherProject); + fail("Should fail because the project's parent was unset."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertEquals( + "The server currently only supports setting the parent once " + + "and does not allow unsetting it.", + e.getMessage()); + } } @Test @@ -426,25 +475,37 @@ public void testUndelete() { com.google.api.services.cloudresourcemanager.model.Project returnedProject = rpc.get(DELETE_REQUESTED_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); assertEquals("ACTIVE", returnedProject.getLifecycleState()); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("The caller does not have permission."); - rpc.undelete("invalid-project-id"); + try { + rpc.undelete("invalid-project-id"); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("the project was not found")); + } } @Test public void testUndeleteWhenActive() { RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Precondition check failed."); - rpc.undelete(COMPLETE_PROJECT.getProjectId()); + try { + rpc.undelete(COMPLETE_PROJECT.getProjectId()); + fail("Should fail because the project is not deleted."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertTrue(e.getMessage().contains("lifecycle state was not DELETE_REQUESTED")); + } } @Test public void testUndeleteWhenDeleteInProgress() { RESOURCE_MANAGER_HELPER.addProject(DELETE_IN_PROGRESS_PROJECT); - thrown.expect(ResourceManagerException.class); - thrown.expectMessage("Precondition check failed."); - rpc.undelete(DELETE_IN_PROGRESS_PROJECT.getProjectId()); + try { + rpc.undelete(DELETE_IN_PROGRESS_PROJECT.getProjectId()); + fail("Should fail because the project is not deleted."); + } catch (ResourceManagerException e) { + assertEquals(400, e.code()); + assertTrue(e.getMessage().contains("lifecycle state was not DELETE_REQUESTED")); + } } @Test @@ -454,9 +515,13 @@ public void testChangeLifecycleStatus() { RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); assertTrue(RESOURCE_MANAGER_HELPER.changeLifecycleState( COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS")); - thrown.expect(IllegalArgumentException.class); - assertFalse(RESOURCE_MANAGER_HELPER.changeLifecycleState( - COMPLETE_PROJECT.getProjectId(), "INVALID_STATE")); + try { + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "INVALID_STATE"); + fail("Should fail because of an invalid lifecycle state"); + } catch (IllegalArgumentException e) { + // ignore + } } @Test From 19aa650e3a452f312c106e06ba247afb05cae29b Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Thu, 10 Dec 2015 09:44:09 -0800 Subject: [PATCH 23/35] remove checkNotNull calls and minor fixes --- .../testing/LocalResourceManagerHelper.java | 313 ++++++++---------- .../LocalResourceManagerHelperTest.java | 222 ++++++------- 2 files changed, 237 insertions(+), 298 deletions(-) diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java index d7934fef72c8..00475922c5b3 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java @@ -1,8 +1,8 @@ package com.google.gcloud.resourcemanager.testing; -import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.net.HttpURLConnection.HTTP_OK; import com.google.api.client.json.JsonFactory; import com.google.api.services.cloudresourcemanager.model.Project; @@ -23,8 +23,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.HttpURLConnection; import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -33,6 +34,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; import java.util.zip.GZIPInputStream; /** @@ -43,10 +45,12 @@ */ @SuppressWarnings("restriction") public class LocalResourceManagerHelper { + private static final Logger log = Logger.getLogger(LocalResourceManagerHelper.class.getName()); private static final JsonFactory jsonFactory = new com.google.api.client.json.jackson.JacksonFactory(); private static final Random PROJECT_NUMBER_GENERATOR = new Random(); private static final String VERSION = "v1beta1"; + private static final String CONTEXT = "/" + VERSION + "/projects"; // see https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects private static final Set PERMISSIBLE_PROJECT_NAME_PUNCTUATION = @@ -105,11 +109,11 @@ Response response(String message) { } private String toJson(String message) throws IOException { - Map args = new HashMap<>(); Map errors = new HashMap<>(); errors.put("domain", domain); errors.put("message", message); errors.put("reason", reason); + Map args = new HashMap<>(); args.put("errors", ImmutableList.of(errors)); args.put("code", code); args.put("message", message); @@ -120,61 +124,93 @@ private String toJson(String message) throws IOException { private class RequestHandler implements HttpHandler { @Override - public void handle(HttpExchange exchange) throws IOException { + public void handle(HttpExchange exchange) { // see https://cloud.google.com/resource-manager/reference/rest/ - String path = exchange.getRequestURI().getPath(); - String requestMethod = exchange.getRequestMethod(); Response response = null; - if (requestMethod.equals("POST") && path.startsWith("/" + VERSION + "/projects")) { - if (path.contains("undelete")) { - try { - response = undelete(projectIdFromURI(path)); - } catch (IOException e) { - response = Error.BAD_REQUEST.response(e.getMessage()); - } - } else { - String requestBody = - decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); - response = create(jsonFactory.fromString(requestBody, Project.class)); - } - } else if (requestMethod.equals("DELETE")) { - response = delete(projectIdFromURI(path)); - } else if (requestMethod.equals("GET")) { - if (path.startsWith("/" + VERSION + "/projects/")) { - response = get(projectIdFromURI(path), parseFields(exchange.getRequestURI().getQuery())); - } else { - response = list(parseListOptions(exchange.getRequestURI().getQuery())); + URI baseContext = null; + try { + baseContext = new URI(CONTEXT); + } catch (URISyntaxException e) { + writeResponse( + exchange, + Error.INTERNAL_ERROR.response( + "URI syntax exception when constructing URI from the path '" + CONTEXT + "'")); + return; + } + String path = baseContext.relativize(exchange.getRequestURI()).getPath(); + String requestMethod = exchange.getRequestMethod(); + try { + switch (requestMethod) { + case "POST": + if (path.contains(":undelete")) { + response = undelete(projectIdFromURI(path)); + } else { + String requestBody = + decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); + response = create(jsonFactory.fromString(requestBody, Project.class)); + } + break; + case "DELETE": + response = delete(projectIdFromURI(path)); + break; + case "GET": + if (!path.isEmpty()) { + response = + get(projectIdFromURI(path), parseFields(exchange.getRequestURI().getQuery())); + } else { + response = list(parseListOptions(exchange.getRequestURI().getQuery())); + } + break; + case "PUT": + String requestBody = + decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); + response = + replace(projectIdFromURI(path), jsonFactory.fromString(requestBody, Project.class)); + break; + default: + response = Error.BAD_REQUEST.response("The server could not understand the request."); } - } else if (requestMethod.equals("PUT")) { - String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); - response = replace(jsonFactory.fromString(requestBody, Project.class)); + } catch (IOException e) { + response = Error.BAD_REQUEST.response(e.getMessage()); } - response = firstNonNull( - response, Error.BAD_REQUEST.response("The server could not understand the request.")); - exchange.getResponseHeaders().set("Content-type", "application/json; charset=UTF-8"); + writeResponse(exchange, response); + } + } + + private static void writeResponse(HttpExchange exchange, Response response) { + exchange.getResponseHeaders().set("Content-type", "application/json; charset=UTF-8"); + OutputStream outputStream = exchange.getResponseBody(); + try { exchange.sendResponseHeaders(response.code(), response.body().length()); - OutputStream outputStream = exchange.getResponseBody(); outputStream.write(response.body().getBytes(StandardCharsets.UTF_8)); outputStream.close(); + } catch (IOException e) { + log.info("IOException encountered when sending response."); } } private static String decodeContent(Headers headers, InputStream inputStream) throws IOException { List contentEncoding = headers.get("Content-encoding"); InputStream input = inputStream; - if (contentEncoding != null && contentEncoding.size() > 0 - && contentEncoding.get(0).contains("gzip")) { - input = new GZIPInputStream(inputStream); + try { + if (contentEncoding != null && !contentEncoding.isEmpty()) { + if (contentEncoding.get(0).equals("gzip") || contentEncoding.get(0).equals("x-gzip")) { + input = new GZIPInputStream(inputStream); + } else if (!contentEncoding.equals("identity")) { + throw new IOException("The request has an unsupported HTTP content encoding."); + } + } + return new String(ByteStreams.toByteArray(input), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IOException("Exception encountered when decoding request content.", e); } - return new String(ByteStreams.toByteArray(input), StandardCharsets.UTF_8); } private static String projectIdFromURI(String path) throws IOException { - String[] pathSplit = path.split("/"); - if (pathSplit.length < 4) { - throw new IOException("The path '" + path + "' doesn't have a project ID"); + if (path.isEmpty()) { + throw new IOException("The URI path '" + path + "' doesn't have a project ID."); } - return pathSplit[3].split(":")[0]; + return path.split(":")[0]; } private static String[] parseFields(String query) { @@ -246,8 +282,7 @@ private static final String checkForProjectErrors(Project project) { private static final boolean isValidIdOrLabel(String value, int minLength, int maxLength) { for (char c : value.toCharArray()) { - if (c != '-' && !Character.isDigit(c) - && (!Character.isLetter(c) || !Character.isLowerCase(c))) { + if (c != '-' && !Character.isDigit(c) && (!Character.isLowerCase(c))) { return false; } } @@ -258,86 +293,93 @@ private static final boolean isValidIdOrLabel(String value, int minLength, int m } Response create(Project project) { - project.setLifecycleState("ACTIVE"); - project.setProjectNumber(Math.abs(PROJECT_NUMBER_GENERATOR.nextLong() % Long.MAX_VALUE)); - project.setCreateTime(ISODateTimeFormat.dateTime().print(System.currentTimeMillis())); - Response response; String customErrorMessage = checkForProjectErrors(project); if (customErrorMessage != null) { - response = Error.INVALID_ARGUMENT.response(customErrorMessage); - } else if (projects.containsKey(project.getProjectId())) { - response = Error.ALREADY_EXISTS.response( - "A project with the same project ID (" + project.getProjectId() + ") already exists."); + return Error.INVALID_ARGUMENT.response(customErrorMessage); } else { - projects.put(project.getProjectId(), project); + project.setLifecycleState("ACTIVE"); + project.setProjectNumber(Math.abs(PROJECT_NUMBER_GENERATOR.nextLong() % Long.MAX_VALUE)); + project.setCreateTime(ISODateTimeFormat.dateTime().print(System.currentTimeMillis())); + if (projects.putIfAbsent(project.getProjectId(), project) != null) { + return Error.ALREADY_EXISTS.response( + "A project with the same project ID (" + project.getProjectId() + ") already exists."); + } try { String createdProjectStr = jsonFactory.toString(project); - response = new Response(HttpURLConnection.HTTP_OK, createdProjectStr); + return new Response(HTTP_OK, createdProjectStr); } catch (IOException e) { - response = - Error.INTERNAL_ERROR.response("Error serializing project " + project.getProjectId()); + return Error.INTERNAL_ERROR.response("Error serializing project " + project.getProjectId()); } } - return response; } Response delete(String projectId) { - Project project = projects.get(checkNotNull(projectId)); - Response response; + Project project = projects.get(projectId); if (project == null) { // when possible, change this to 404 (#440) - response = Error.PERMISSION_DENIED.response( + return Error.PERMISSION_DENIED.response( "Error when deleting " + projectId + " because the project was not found."); - } else if (!project.getLifecycleState().equals("ACTIVE")) { - response = Error.FAILED_PRECONDITION.response( + } + if (!project.getLifecycleState().equals("ACTIVE")) { + return Error.FAILED_PRECONDITION.response( "Error when deleting " + projectId + " because the lifecycle state was not ACTIVE."); } else { project.setLifecycleState("DELETE_REQUESTED"); - response = new Response(HttpURLConnection.HTTP_OK, "{}"); + return new Response(HTTP_OK, "{}"); } - return response; } Response get(String projectId, String[] fields) { - if (!projects.containsKey(checkNotNull(projectId))) { + if (!projects.containsKey(projectId)) { // when possible, change this to 404 (#440) return Error.PERMISSION_DENIED.response("Project " + projectId + " not found."); } Project project = projects.get(projectId); try { - return new Response( - HttpURLConnection.HTTP_OK, jsonFactory.toString(extractFields(project, fields))); + return new Response(HTTP_OK, jsonFactory.toString(extractFields(project, fields))); } catch (IOException e) { return Error.INTERNAL_ERROR.response( "Error when serializing project " + project.getProjectId()); } } - Response list(final Map options) { + Response list(Map options) { // Use pageSize and pageToken options when Cloud Resource Manager does so (#421) List projectsSerialized = new ArrayList<>(); + String[] filters = (String[]) options.get("filter"); + if (filters != null && !isValidFilter(filters)) { + return Error.INVALID_ARGUMENT.response("Could not parse the filter."); + } + String[] fields = (String[]) options.get("fields"); for (Project p : projects.values()) { - Boolean includeProject = includeProject(p, (String[]) options.get("filter")); + Boolean includeProject = includeProject(p, filters); if (includeProject) { try { - projectsSerialized.add( - jsonFactory.toString(extractFields(p, (String[]) options.get("fields")))); + projectsSerialized.add(jsonFactory.toString(extractFields(p, fields))); } catch (IOException e) { return Error.INTERNAL_ERROR.response( "Error when serializing project " + p.getProjectId()); } - } else if (includeProject == null) { - return Error.INVALID_ARGUMENT.response("Could not parse the filter."); } } StringBuilder responseBody = new StringBuilder(); responseBody.append("{\"projects\": ["); - responseBody.append(Joiner.on(",").join(projectsSerialized)); + Joiner.on(",").appendTo(responseBody, projectsSerialized); responseBody.append("]}"); - return new Response(HttpURLConnection.HTTP_OK, responseBody.toString()); + return new Response(HTTP_OK, responseBody.toString()); } - private static Boolean includeProject(Project project, String[] filters) { + private static boolean isValidFilter(String[] filters) { + for (String filter : filters) { + String field = filter.toLowerCase().split(":")[0]; + if (!("id".equals(field) || "name".equals(field) || field.startsWith("labels."))) { + return false; + } + } + return true; + } + + private static boolean includeProject(Project project, String[] filters) { if (filters == null) { return true; } @@ -359,8 +401,6 @@ private static Boolean includeProject(Project project, String[] filters) { return false; } } - } else { - return null; } } return true; @@ -406,35 +446,32 @@ private static Project extractFields(Project fullProject, String[] fields) { return project; } - Response replace(Project project) { - Project oldProject = projects.get(project.getProjectId()); - if (oldProject == null) { + Response replace(String projectId, Project project) { + Project originalProject = projects.get(projectId); + if (originalProject == null) { // when possible, change this to 404 (#440) return Error.PERMISSION_DENIED.response( - "Error when replacing " + project.getProjectId() + " because the project was not found."); - } else if (!oldProject.getLifecycleState().equals("ACTIVE")) { - return Error.FAILED_PRECONDITION.response("Error when replacing " + project.getProjectId() - + " because the lifecycle state was not ACTIVE."); - } else if (!Objects.equal(oldProject.getParent(), project.getParent())) { + "Error when replacing " + projectId + " because the project was not found."); + } else if (!originalProject.getLifecycleState().equals("ACTIVE")) { + return Error.FAILED_PRECONDITION.response( + "Error when replacing " + projectId + " because the lifecycle state was not ACTIVE."); + } else if (!Objects.equal(originalProject.getParent(), project.getParent())) { return Error.INVALID_ARGUMENT.response( "The server currently only supports setting the parent once " + "and does not allow unsetting it."); } - project.setLifecycleState("ACTIVE"); - project.setProjectNumber(oldProject.getProjectNumber()); - project.setCreateTime(oldProject.getCreateTime()); - project.setParent(oldProject.getParent()); - projects.put(project.getProjectId(), project); + originalProject.setName(project.getName()); + originalProject.setLabels(project.getLabels()); + originalProject.setParent(project.getParent()); try { - return new Response(HttpURLConnection.HTTP_OK, jsonFactory.toString(project)); + return new Response(HTTP_OK, jsonFactory.toString(originalProject)); } catch (IOException e) { - return Error.INTERNAL_ERROR.response( - "Error when serializing project " + project.getProjectId()); + return Error.INTERNAL_ERROR.response("Error when serializing project " + projectId); } } Response undelete(String projectId) { - Project project = projects.get(checkNotNull(projectId)); + Project project = projects.get(projectId); Response response; if (project == null) { // when possible, change this to 404 (#440) @@ -445,7 +482,7 @@ Response undelete(String projectId) { + " because the lifecycle state was not DELETE_REQUESTED."); } else { project.setLifecycleState("ACTIVE"); - response = new Response(HttpURLConnection.HTTP_OK, "{}"); + response = new Response(HTTP_OK, "{}"); } return response; } @@ -455,7 +492,7 @@ private LocalResourceManagerHelper() { try { server = HttpServer.create(addr, 0); port = server.getAddress().getPort(); - server.createContext("/", new RequestHandler()); + server.createContext(CONTEXT, new RequestHandler()); } catch (IOException e) { throw new RuntimeException("Could not bind the mock Resource Manager server.", e); } @@ -489,69 +526,6 @@ public void stop() { server.stop(1); } - /** - * Utility method to add a project. - * - *

              Will not overwrite an existing project with the same ID. - * - * @return true if the project was successfully added, false if the project already exists or is - * invalid - */ - public boolean addProject(Project project) { - if (checkForProjectErrors(project) == null) { - return projects.putIfAbsent(project.getProjectId(), clone(project)) == null ? true : false; - } - return false; - } - - /** - * Utility method to get a project. - * - * @return Project (if it exists) or null (if it doesn't exist) - */ - public Project getProject(String projectId) { - Project original = projects.get(projectId); - return original != null ? clone(projects.get(projectId)) : null; - } - - private static Project clone(Project original) { - return new Project() - .setProjectId(original.getProjectId()) - .setName(original.getName()) - .setLabels(original.getLabels() != null ? ImmutableMap.copyOf(original.getLabels()) : null) - .setProjectNumber( - original.getProjectNumber() != null ? original.getProjectNumber().longValue() : null) - .setCreateTime(original.getCreateTime()) - .setLifecycleState(original.getLifecycleState()) - .setParent(original.getParent() != null ? original.getParent().clone() : null); - } - - /** - * Utility method to remove the specified project. - * - *

              This method can be used to fully remove a project (to mimic when the server completely - * deletes a project). - * - * @return true if the project was successfully deleted, false otherwise. - */ - public boolean removeProject(String projectId) { - return projects.remove(checkNotNull(projectId)) != null ? true : false; - } - - /** - * Utility method to change the project number of a project. - * - * @return true if the project number was successfully changed, false otherwise. - */ - public boolean changeProjectNumber(String projectId, long projectNumber) { - Project project = projects.get(checkNotNull(projectId)); - if (project != null) { - project.setProjectNumber(projectNumber); - return true; - } - return false; - } - /** * Utility method to change the lifecycle state of the specified project. * @@ -571,23 +545,14 @@ public boolean changeLifecycleState(String projectId, String lifecycleState) { } /** - * Utility method to change the create time of a project. + * Utility method to remove the specified project. * - * @return true if the project create time was successfully changed, false otherwise. - */ - public boolean changeCreateTime(String projectId, String createTime) { - Project project = projects.get(checkNotNull(projectId)); - if (project != null) { - project.setCreateTime(checkNotNull(createTime)); - return true; - } - return false; - } - - /** - * Utility method to clear all the projects. + *

              This method can be used to fully remove a project (to mimic when the server completely + * deletes a project). + * + * @return true if the project was successfully deleted, false otherwise. */ - public void clearProjects() { - projects.clear(); + public boolean removeProject(String projectId) { + return projects.remove(checkNotNull(projectId)) != null ? true : false; } } diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java index ac329e9aa6d0..5bd87d1d4335 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java @@ -24,8 +24,6 @@ public class LocalResourceManagerHelperTest { - private static final long DEFAULT_PROJECT_NUMBER = 123456789L; - private static final String DEFAULT_CREATE_TIME = "2011-11-11T01:23:45.678Z"; private static final String DEFAULT_PARENT_ID = "12345"; private static final String DEFAULT_PARENT_TYPE = "organization"; private static final com.google.api.services.cloudresourcemanager.model.ResourceId PARENT = @@ -46,18 +44,7 @@ public class LocalResourceManagerHelperTest { new com.google.api.services.cloudresourcemanager.model.Project() .setProjectId("complete-project") .setName("full project") - .setLabels(ImmutableMap.of("k1", "v1", "k2", "v2")) - .setProjectNumber(DEFAULT_PROJECT_NUMBER) - .setCreateTime(DEFAULT_CREATE_TIME) - .setLifecycleState("ACTIVE"); - private static final com.google.api.services.cloudresourcemanager.model.Project - DELETE_REQUESTED_PROJECT = copyFrom(COMPLETE_PROJECT) - .setProjectId("delete-requested-project-id") - .setLifecycleState("DELETE_REQUESTED"); - private static final com.google.api.services.cloudresourcemanager.model.Project - DELETE_IN_PROGRESS_PROJECT = copyFrom(COMPLETE_PROJECT) - .setProjectId("delete-in-progress-project-id") - .setLifecycleState("DELETE_IN_PROGRESS"); + .setLabels(ImmutableMap.of("k1", "v1", "k2", "v2")); private static final com.google.api.services.cloudresourcemanager.model.Project PROJECT_WITH_PARENT = copyFrom(COMPLETE_PROJECT).setProjectId("project-with-parent-id").setParent(PARENT); @@ -80,9 +67,17 @@ private static com.google.api.services.cloudresourcemanager.model.Project copyFr .setParent(from.getParent() != null ? from.getParent().clone() : null); } + private void clearProjects() { + Iterator it = + rpc.list(EMPTY_RPC_OPTIONS).y().iterator(); + while (it.hasNext()) { + RESOURCE_MANAGER_HELPER.removeProject(it.next().getProjectId()); + } + } + @Before public void setUp() { - RESOURCE_MANAGER_HELPER.clearProjects(); + clearProjects(); } @AfterClass @@ -94,7 +89,7 @@ public static void afterClass() { public void testCreate() { com.google.api.services.cloudresourcemanager.model.Project returnedProject = rpc.create(PARTIAL_PROJECT); - assertEquals(PARTIAL_PROJECT.getProjectId(), returnedProject.getProjectId()); + compareReadWriteFields(PARTIAL_PROJECT, returnedProject); assertEquals("ACTIVE", returnedProject.getLifecycleState()); assertNull(returnedProject.getLabels()); assertNull(returnedProject.getName()); @@ -110,13 +105,10 @@ public void testCreate() { && e.getMessage().endsWith("already exists.")); } returnedProject = rpc.create(PROJECT_WITH_PARENT); - assertEquals(PROJECT_WITH_PARENT.getProjectId(), returnedProject.getProjectId()); + compareReadWriteFields(PROJECT_WITH_PARENT, returnedProject); assertEquals("ACTIVE", returnedProject.getLifecycleState()); - assertEquals(PARENT, returnedProject.getParent()); - assertEquals(PROJECT_WITH_PARENT.getLabels(), returnedProject.getLabels()); - assertEquals(PROJECT_WITH_PARENT.getName(), returnedProject.getName()); assertNotNull(returnedProject.getProjectNumber()); - assertFalse(PROJECT_WITH_PARENT.getCreateTime().equals(returnedProject.getCreateTime())); + assertNotNull(returnedProject.getCreateTime()); } @Test @@ -137,7 +129,7 @@ public void testIsInvalidProjectId() { expectInvalidArgumentException(project, invalidIDMessageSubstring); project.setProjectId("some-valid-project-id-12345"); rpc.create(project); - assertNotNull(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId())); + assertNotNull(rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS)); } private void expectInvalidArgumentException( @@ -158,12 +150,11 @@ public void testIsInvalidProjectName() { new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( "some-project-id"); rpc.create(project); - assertNull(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId()).getName()); + assertNull(rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS).getName()); RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); project.setName("This is a valid name-'\"!"); rpc.create(project); - assertEquals( - project.getName(), RESOURCE_MANAGER_HELPER.getProject(project.getProjectId()).getName()); + assertEquals(project.getName(), rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS).getName()); RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); project.setName("invalid-character-,"); try { @@ -180,9 +171,7 @@ public void testIsInvalidProjectLabels() { com.google.api.services.cloudresourcemanager.model.Project project = new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( "some-valid-project-id"); - rpc.create(project); String invalidLabelMessageSubstring = "invalid label entry"; - RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); project.setLabels(ImmutableMap.of("", "v1")); expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of( @@ -210,8 +199,8 @@ public void testIsInvalidProjectLabels() { expectInvalidArgumentException(project, invalidLabelMessageSubstring); project.setLabels(ImmutableMap.of("k-1", "")); rpc.create(project); - assertNotNull(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId())); - assertTrue(RESOURCE_MANAGER_HELPER.getProject(project.getProjectId()) + assertNotNull(rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS)); + assertTrue(rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS) .getLabels() .get("k-1") .isEmpty()); @@ -219,20 +208,23 @@ public void testIsInvalidProjectLabels() { @Test public void testDelete() { - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.create(COMPLETE_PROJECT); rpc.delete(COMPLETE_PROJECT.getProjectId()); + assertEquals( + "DELETE_REQUESTED", + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS).getLifecycleState()); try { rpc.delete("some-nonexistant-project-id"); - fail("Should fail because the project was already deleted."); + fail("Should fail because the project doesn't exist."); } catch (ResourceManagerException e) { assertEquals(403, e.code()); - assertTrue(e.getMessage().contains("the project was not found")); + assertTrue(e.getMessage().contains("not found.")); } } @Test public void testDeleteWhenDeleteInProgress() { - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.create(COMPLETE_PROJECT); RESOURCE_MANAGER_HELPER.changeLifecycleState( COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS"); try { @@ -246,7 +238,7 @@ public void testDeleteWhenDeleteInProgress() { @Test public void testDeleteWhenDeleteRequested() { - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.create(COMPLETE_PROJECT); RESOURCE_MANAGER_HELPER.changeLifecycleState( COMPLETE_PROJECT.getProjectId(), "DELETE_REQUESTED"); try { @@ -260,10 +252,10 @@ public void testDeleteWhenDeleteRequested() { @Test public void testGet() { - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.create(COMPLETE_PROJECT); com.google.api.services.cloudresourcemanager.model.Project returnedProject = rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); - assertEquals(COMPLETE_PROJECT, returnedProject); + compareReadWriteFields(COMPLETE_PROJECT, returnedProject); RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId()); try { rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); @@ -276,7 +268,8 @@ public void testGet() { @Test public void testGetWithOptions() { - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + com.google.api.services.cloudresourcemanager.model.Project originalProject = + rpc.create(COMPLETE_PROJECT); Map rpcOptions = new HashMap<>(); rpcOptions.put(ResourceManagerRpc.Option.FIELDS, "projectId,name,createTime"); com.google.api.services.cloudresourcemanager.model.Project returnedProject = @@ -284,7 +277,7 @@ public void testGetWithOptions() { assertFalse(COMPLETE_PROJECT.equals(returnedProject)); assertEquals(COMPLETE_PROJECT.getProjectId(), returnedProject.getProjectId()); assertEquals(COMPLETE_PROJECT.getName(), returnedProject.getName()); - assertEquals(COMPLETE_PROJECT.getCreateTime(), returnedProject.getCreateTime()); + assertEquals(originalProject.getCreateTime(), returnedProject.getCreateTime()); assertNull(returnedProject.getParent()); assertNull(returnedProject.getProjectNumber()); assertNull(returnedProject.getLifecycleState()); @@ -297,13 +290,15 @@ public void testList() { rpc.list(EMPTY_RPC_OPTIONS); assertNull(projects.x()); // change this when #421 is resolved assertFalse(projects.y().iterator().hasNext()); - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); - RESOURCE_MANAGER_HELPER.addProject(PROJECT_WITH_PARENT); + rpc.create(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_REQUESTED"); + rpc.create(PROJECT_WITH_PARENT); projects = rpc.list(EMPTY_RPC_OPTIONS); Iterator it = projects.y().iterator(); - assertEquals(COMPLETE_PROJECT, it.next()); - assertEquals(PROJECT_WITH_PARENT, it.next()); + compareReadWriteFields(COMPLETE_PROJECT, it.next()); + compareReadWriteFields(PROJECT_WITH_PARENT, it.next()); } @Test @@ -312,7 +307,7 @@ public void testListFieldOptions() { rpcOptions.put(ResourceManagerRpc.Option.FIELDS, "projectId,name,labels"); rpcOptions.put(ResourceManagerRpc.Option.PAGE_TOKEN, "somePageToken"); rpcOptions.put(ResourceManagerRpc.Option.PAGE_SIZE, 1); - RESOURCE_MANAGER_HELPER.addProject(PROJECT_WITH_PARENT); + rpc.create(PROJECT_WITH_PARENT); Tuple> projects = rpc.list(rpcOptions); com.google.api.services.cloudresourcemanager.model.Project returnedProject = @@ -336,39 +331,36 @@ public void testListFilterOptions() { new com.google.api.services.cloudresourcemanager.model.Project() .setProjectId("matching-project") .setName("MyProject") - .setProjectNumber(DEFAULT_PROJECT_NUMBER) - .setLabels(ImmutableMap.of("Color", "blue", "size", "Big")); + .setLabels(ImmutableMap.of("color", "blue", "size", "big")); com.google.api.services.cloudresourcemanager.model.Project nonMatchingProject1 = new com.google.api.services.cloudresourcemanager.model.Project() .setProjectId("non-matching-project1") - .setName("myProject") - .setProjectNumber(DEFAULT_PROJECT_NUMBER); + .setName("myProject"); nonMatchingProject1.setLabels(ImmutableMap.of("color", "blue")); com.google.api.services.cloudresourcemanager.model.Project nonMatchingProject2 = new com.google.api.services.cloudresourcemanager.model.Project() .setProjectId("non-matching-project2") .setName("myProj") - .setProjectNumber(DEFAULT_PROJECT_NUMBER) .setLabels(ImmutableMap.of("color", "blue", "size", "big")); com.google.api.services.cloudresourcemanager.model.Project nonMatchingProject3 = - new com.google.api.services.cloudresourcemanager.model.Project() - .setProjectId("non-matching-project3") - .setProjectNumber(DEFAULT_PROJECT_NUMBER); - RESOURCE_MANAGER_HELPER.addProject(matchingProject); - RESOURCE_MANAGER_HELPER.addProject(nonMatchingProject1); - RESOURCE_MANAGER_HELPER.addProject(nonMatchingProject2); - RESOURCE_MANAGER_HELPER.addProject(nonMatchingProject3); + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + "non-matching-project3"); + rpc.create(matchingProject); + rpc.create(nonMatchingProject1); + rpc.create(nonMatchingProject2); + rpc.create(nonMatchingProject3); for (com.google.api.services.cloudresourcemanager.model.Project p : rpc.list(rpcFilterOptions).y()) { assertFalse(p.equals(nonMatchingProject1)); assertFalse(p.equals(nonMatchingProject2)); - assertTrue(p.equals(matchingProject)); + compareReadWriteFields(matchingProject, p); } } @Test public void testReplace() { - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + com.google.api.services.cloudresourcemanager.model.Project createdProject = + rpc.create(COMPLETE_PROJECT); String newName = "new name"; Map newLabels = ImmutableMap.of("new k1", "new v1"); com.google.api.services.cloudresourcemanager.model.Project anotherCompleteProject = @@ -379,16 +371,12 @@ public void testReplace() { .setProjectNumber(987654321L) .setCreateTime("2000-01-01T00:00:00.001Z") .setLifecycleState("DELETE_REQUESTED"); - rpc.replace(anotherCompleteProject); com.google.api.services.cloudresourcemanager.model.Project returnedProject = - RESOURCE_MANAGER_HELPER.getProject(COMPLETE_PROJECT.getProjectId()); - assertEquals(COMPLETE_PROJECT.getProjectId(), returnedProject.getProjectId()); - assertEquals(newName, returnedProject.getName()); - assertEquals(newLabels, returnedProject.getLabels()); - assertEquals(COMPLETE_PROJECT.getProjectNumber(), returnedProject.getProjectNumber()); - assertEquals(COMPLETE_PROJECT.getCreateTime(), returnedProject.getCreateTime()); - assertEquals(COMPLETE_PROJECT.getLifecycleState(), returnedProject.getLifecycleState()); - assertNull(returnedProject.getParent()); + rpc.replace(anotherCompleteProject); + compareReadWriteFields(anotherCompleteProject, returnedProject); + assertEquals(createdProject.getProjectNumber(), returnedProject.getProjectNumber()); + assertEquals(createdProject.getCreateTime(), returnedProject.getCreateTime()); + assertEquals(createdProject.getLifecycleState(), returnedProject.getLifecycleState()); com.google.api.services.cloudresourcemanager.model.Project nonexistantProject = new com.google.api.services.cloudresourcemanager.model.Project(); nonexistantProject.setProjectId("some-project-id-that-does-not-exist"); @@ -403,10 +391,11 @@ public void testReplace() { @Test public void testReplaceWhenDeleteRequested() { - RESOURCE_MANAGER_HELPER.addProject(DELETE_REQUESTED_PROJECT); + rpc.create(COMPLETE_PROJECT); + rpc.delete(COMPLETE_PROJECT.getProjectId()); com.google.api.services.cloudresourcemanager.model.Project anotherProject = new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( - DELETE_REQUESTED_PROJECT.getProjectId()); + COMPLETE_PROJECT.getProjectId()); try { rpc.replace(anotherProject); fail("Should fail because the project is not ACTIVE."); @@ -418,10 +407,12 @@ public void testReplaceWhenDeleteRequested() { @Test public void testReplaceWhenDeleteInProgress() { - RESOURCE_MANAGER_HELPER.addProject(DELETE_IN_PROGRESS_PROJECT); + rpc.create(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS"); com.google.api.services.cloudresourcemanager.model.Project anotherProject = new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( - DELETE_IN_PROGRESS_PROJECT.getProjectId()); + COMPLETE_PROJECT.getProjectId()); try { rpc.replace(anotherProject); fail("Should fail because the project is not ACTIVE."); @@ -433,7 +424,7 @@ public void testReplaceWhenDeleteInProgress() { @Test public void testReplaceAddingParent() { - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.create(COMPLETE_PROJECT); com.google.api.services.cloudresourcemanager.model.Project anotherProject = new com.google.api.services.cloudresourcemanager.model.Project() .setProjectId(COMPLETE_PROJECT.getProjectId()) @@ -452,7 +443,7 @@ public void testReplaceAddingParent() { @Test public void testReplaceRemovingParent() { - RESOURCE_MANAGER_HELPER.addProject(PROJECT_WITH_PARENT); + rpc.create(PROJECT_WITH_PARENT); com.google.api.services.cloudresourcemanager.model.Project anotherProject = new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( PROJECT_WITH_PARENT.getProjectId()); @@ -470,11 +461,16 @@ public void testReplaceRemovingParent() { @Test public void testUndelete() { - RESOURCE_MANAGER_HELPER.addProject(DELETE_REQUESTED_PROJECT); - rpc.undelete(DELETE_REQUESTED_PROJECT.getProjectId()); - com.google.api.services.cloudresourcemanager.model.Project returnedProject = - rpc.get(DELETE_REQUESTED_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); - assertEquals("ACTIVE", returnedProject.getLifecycleState()); + rpc.create(COMPLETE_PROJECT); + rpc.delete(COMPLETE_PROJECT.getProjectId()); + assertEquals( + "DELETE_REQUESTED", + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS).getLifecycleState()); + rpc.undelete(COMPLETE_PROJECT.getProjectId()); + com.google.api.services.cloudresourcemanager.model.Project revivedProject = + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); + compareReadWriteFields(COMPLETE_PROJECT, revivedProject); + assertEquals("ACTIVE", revivedProject.getLifecycleState()); try { rpc.undelete("invalid-project-id"); fail("Should fail because the project doesn't exist."); @@ -486,7 +482,7 @@ public void testUndelete() { @Test public void testUndeleteWhenActive() { - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.create(COMPLETE_PROJECT); try { rpc.undelete(COMPLETE_PROJECT.getProjectId()); fail("Should fail because the project is not deleted."); @@ -498,10 +494,12 @@ public void testUndeleteWhenActive() { @Test public void testUndeleteWhenDeleteInProgress() { - RESOURCE_MANAGER_HELPER.addProject(DELETE_IN_PROGRESS_PROJECT); + rpc.create(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS"); try { - rpc.undelete(DELETE_IN_PROGRESS_PROJECT.getProjectId()); - fail("Should fail because the project is not deleted."); + rpc.undelete(COMPLETE_PROJECT.getProjectId()); + fail("Should fail because the project is in the process of being deleted."); } catch (ResourceManagerException e) { assertEquals(400, e.code()); assertTrue(e.getMessage().contains("lifecycle state was not DELETE_REQUESTED")); @@ -512,9 +510,12 @@ public void testUndeleteWhenDeleteInProgress() { public void testChangeLifecycleStatus() { assertFalse(RESOURCE_MANAGER_HELPER.changeLifecycleState( COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS")); - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.create(COMPLETE_PROJECT); assertTrue(RESOURCE_MANAGER_HELPER.changeLifecycleState( COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS")); + assertEquals( + "DELETE_IN_PROGRESS", + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS).getLifecycleState()); try { RESOURCE_MANAGER_HELPER.changeLifecycleState( COMPLETE_PROJECT.getProjectId(), "INVALID_STATE"); @@ -524,53 +525,26 @@ public void testChangeLifecycleStatus() { } } - @Test - public void testAddProject() { - assertTrue(RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT)); - com.google.api.services.cloudresourcemanager.model.Project project = - new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( - COMPLETE_PROJECT.getProjectId()); - assertFalse(RESOURCE_MANAGER_HELPER.addProject(project)); - assertFalse( - project.equals(RESOURCE_MANAGER_HELPER.getProject(COMPLETE_PROJECT.getProjectId()))); - assertFalse(RESOURCE_MANAGER_HELPER.addProject( - new com.google.api.services.cloudresourcemanager.model.Project())); - } - - @Test - public void testGetProject() { - assertNull(RESOURCE_MANAGER_HELPER.getProject("some-invalid-project-id")); - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); - assertEquals( - COMPLETE_PROJECT, RESOURCE_MANAGER_HELPER.getProject(COMPLETE_PROJECT.getProjectId())); - } - @Test public void testRemoveProject() { assertFalse(RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId())); - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); + rpc.create(COMPLETE_PROJECT); assertTrue(RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId())); + try { + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); + fail("Project shouldn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("not found.")); + } } - @Test - public void testChangeProjectNumber() { - assertFalse(RESOURCE_MANAGER_HELPER.changeProjectNumber(COMPLETE_PROJECT.getProjectId(), 123L)); - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); - assertTrue(RESOURCE_MANAGER_HELPER.changeProjectNumber(COMPLETE_PROJECT.getProjectId(), 123L)); - } - - @Test - public void testChangeCreateTime() { - assertFalse(RESOURCE_MANAGER_HELPER.changeCreateTime( - COMPLETE_PROJECT.getProjectId(), "2015-01-01T01:01:01.001Z")); - RESOURCE_MANAGER_HELPER.addProject(COMPLETE_PROJECT); - assertTrue(RESOURCE_MANAGER_HELPER.changeCreateTime( - COMPLETE_PROJECT.getProjectId(), "2015-01-01T01:01:01.001Z")); - } - - @Test - public void testClearProjects() { - RESOURCE_MANAGER_HELPER.clearProjects(); - assertFalse(rpc.list(EMPTY_RPC_OPTIONS).y().iterator().hasNext()); + private void compareReadWriteFields( + com.google.api.services.cloudresourcemanager.model.Project expected, + com.google.api.services.cloudresourcemanager.model.Project actual) { + assertEquals(expected.getProjectId(), actual.getProjectId()); + assertEquals(expected.getName(), actual.getName()); + assertEquals(expected.getLabels(), actual.getLabels()); + assertEquals(expected.getParent(), actual.getParent()); } } From 4fdd477e02fbedfc4faa620096b67064f3287597 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Fri, 11 Dec 2015 15:19:40 -0800 Subject: [PATCH 24/35] minor fixes --- .../testing/LocalResourceManagerHelper.java | 94 +++++++++++-------- .../LocalResourceManagerHelperTest.java | 10 +- 2 files changed, 60 insertions(+), 44 deletions(-) diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java index 00475922c5b3..aacab5c6ee40 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java @@ -34,6 +34,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; @@ -51,6 +52,19 @@ public class LocalResourceManagerHelper { private static final Random PROJECT_NUMBER_GENERATOR = new Random(); private static final String VERSION = "v1beta1"; private static final String CONTEXT = "/" + VERSION + "/projects"; + private static final URI BASE_CONTEXT; + private static final Set SUPPORTED_COMPRESSION_ENCODINGS = + ImmutableSet.of("gzip", "x-gzip"); + + static { + try { + BASE_CONTEXT = new URI(CONTEXT); + } catch (URISyntaxException e) { + log.log(Level.WARNING, "URI.", e); + throw new RuntimeException( + "Could not initialize LocalResourceManagerHelper due to URISyntaxException.", e); + } + } // see https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects private static final Set PERMISSIBLE_PROJECT_NAME_PUNCTUATION = @@ -60,7 +74,7 @@ public class LocalResourceManagerHelper { private final ConcurrentHashMap projects = new ConcurrentHashMap<>(); private final int port; - static class Response { + private static class Response { private final int code; private final String body; @@ -78,7 +92,7 @@ String body() { } } - enum Error { + private enum Error { ALREADY_EXISTS(409, "global", "alreadyExists", "ALREADY_EXISTS"), PERMISSION_DENIED(403, "global", "forbidden", "PERMISSION_DENIED"), // change failed precondition error code to 412 when #440 is fixed @@ -126,24 +140,14 @@ private class RequestHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) { // see https://cloud.google.com/resource-manager/reference/rest/ - Response response = null; - URI baseContext = null; - try { - baseContext = new URI(CONTEXT); - } catch (URISyntaxException e) { - writeResponse( - exchange, - Error.INTERNAL_ERROR.response( - "URI syntax exception when constructing URI from the path '" + CONTEXT + "'")); - return; - } - String path = baseContext.relativize(exchange.getRequestURI()).getPath(); + Response response; + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String requestMethod = exchange.getRequestMethod(); try { switch (requestMethod) { case "POST": - if (path.contains(":undelete")) { - response = undelete(projectIdFromURI(path)); + if (path.endsWith(":undelete")) { + response = undelete(projectIdFromUri(path)); } else { String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); @@ -151,12 +155,12 @@ public void handle(HttpExchange exchange) { } break; case "DELETE": - response = delete(projectIdFromURI(path)); + response = delete(projectIdFromUri(path)); break; case "GET": if (!path.isEmpty()) { response = - get(projectIdFromURI(path), parseFields(exchange.getRequestURI().getQuery())); + get(projectIdFromUri(path), parseFields(exchange.getRequestURI().getQuery())); } else { response = list(parseListOptions(exchange.getRequestURI().getQuery())); } @@ -165,10 +169,12 @@ public void handle(HttpExchange exchange) { String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); response = - replace(projectIdFromURI(path), jsonFactory.fromString(requestBody, Project.class)); + replace(projectIdFromUri(path), jsonFactory.fromString(requestBody, Project.class)); break; default: - response = Error.BAD_REQUEST.response("The server could not understand the request."); + response = Error.BAD_REQUEST.response( + "The server could not understand the following request URI: " + requestMethod + " " + + path); } } catch (IOException e) { response = Error.BAD_REQUEST.response(e.getMessage()); @@ -185,7 +191,7 @@ private static void writeResponse(HttpExchange exchange, Response response) { outputStream.write(response.body().getBytes(StandardCharsets.UTF_8)); outputStream.close(); } catch (IOException e) { - log.info("IOException encountered when sending response."); + log.log(Level.WARNING, "IOException encountered when sending response.", e); } } @@ -194,10 +200,12 @@ private static String decodeContent(Headers headers, InputStream inputStream) th InputStream input = inputStream; try { if (contentEncoding != null && !contentEncoding.isEmpty()) { - if (contentEncoding.get(0).equals("gzip") || contentEncoding.get(0).equals("x-gzip")) { + String encoding = contentEncoding.get(0); + if (SUPPORTED_COMPRESSION_ENCODINGS.contains(encoding)) { input = new GZIPInputStream(inputStream); - } else if (!contentEncoding.equals("identity")) { - throw new IOException("The request has an unsupported HTTP content encoding."); + } else if (!encoding.equals("identity")) { + throw new IOException( + "The request has the following unsupported HTTP content encoding: " + encoding); } } return new String(ByteStreams.toByteArray(input), StandardCharsets.UTF_8); @@ -206,7 +214,7 @@ private static String decodeContent(Headers headers, InputStream inputStream) th } } - private static String projectIdFromURI(String path) throws IOException { + private static String projectIdFromUri(String path) throws IOException { if (path.isEmpty()) { throw new IOException("The URI path '" + path + "' doesn't have a project ID."); } @@ -286,7 +294,7 @@ private static final boolean isValidIdOrLabel(String value, int minLength, int m return false; } } - if (value.length() > 0 && (!Character.isLetter(value.charAt(0)) || value.endsWith("-"))) { + if (!value.isEmpty() && (!Character.isLetter(value.charAt(0)) || value.endsWith("-"))) { return false; } return value.length() >= minLength && value.length() <= maxLength; @@ -316,7 +324,9 @@ Response create(Project project) { Response delete(String projectId) { Project project = projects.get(projectId); if (project == null) { - // when possible, change this to 404 (#440) + // Currently the service returns 403 Permission Denied when trying to delete a project that + // doesn't exist. Here we mimic this behavior, but this line should be changed to throw a + // 404 Not Found error when the service fixes this (#440). return Error.PERMISSION_DENIED.response( "Error when deleting " + projectId + " because the project was not found."); } @@ -331,7 +341,9 @@ Response delete(String projectId) { Response get(String projectId, String[] fields) { if (!projects.containsKey(projectId)) { - // when possible, change this to 404 (#440) + // Currently the service returns 403 Permission Denied when trying to get a project that + // doesn't exist. Here we mimic this behavior, but this line should be changed to throw a + // 404 Not Found error when the service fixes this (#440). return Error.PERMISSION_DENIED.response("Project " + projectId + " not found."); } Project project = projects.get(projectId); @@ -385,16 +397,17 @@ private static boolean includeProject(Project project, String[] filters) { } for (String filter : filters) { String[] filterEntry = filter.toLowerCase().split(":"); - if ("id".equals(filterEntry[0])) { + String filterType = filterEntry[0]; + if ("id".equals(filterType)) { if (!satisfiesFilter(project.getProjectId(), filterEntry[1])) { return false; } - } else if ("name".equals(filterEntry[0])) { + } else if ("name".equals(filterType)) { if (!satisfiesFilter(project.getName(), filterEntry[1])) { return false; } - } else if (filterEntry[0].startsWith("labels")) { - String labelKey = filterEntry[0].split("\\.")[1]; + } else if (filterType.startsWith("labels.")) { + String labelKey = filterType.substring("labels.".length()); if (project.getLabels() != null) { String labelValue = project.getLabels().get(labelKey); if (!satisfiesFilter(labelValue, filterEntry[1])) { @@ -410,7 +423,7 @@ private static boolean satisfiesFilter(String projectValue, String filterValue) if (projectValue == null) { return false; } - return "*".equals(filterValue) ? true : filterValue.equals(projectValue.toLowerCase()); + return "*".equals(filterValue) || filterValue.equals(projectValue.toLowerCase()); } private static Project extractFields(Project fullProject, String[] fields) { @@ -449,7 +462,9 @@ private static Project extractFields(Project fullProject, String[] fields) { Response replace(String projectId, Project project) { Project originalProject = projects.get(projectId); if (originalProject == null) { - // when possible, change this to 404 (#440) + // Currently the service returns 403 Permission Denied when trying to replace a project that + // doesn't exist. Here we mimic this behavior, but this line should be changed to throw a + // 404 Not Found error when the service fixes this (#440). return Error.PERMISSION_DENIED.response( "Error when replacing " + projectId + " because the project was not found."); } else if (!originalProject.getLifecycleState().equals("ACTIVE")) { @@ -474,7 +489,9 @@ Response undelete(String projectId) { Project project = projects.get(projectId); Response response; if (project == null) { - // when possible, change this to 404 (#440) + // Currently the service returns 403 Permission Denied when trying to undelete a project that + // doesn't exist. Here we mimic this behavior, but this line should be changed to throw a + // 404 Not Found error when the service fixes this (#440). response = Error.PERMISSION_DENIED.response( "Error when undeleting " + projectId + " because the project was not found."); } else if (!project.getLifecycleState().equals("DELETE_REQUESTED")) { @@ -488,9 +505,8 @@ Response undelete(String projectId) { } private LocalResourceManagerHelper() { - InetSocketAddress addr = new InetSocketAddress(0); try { - server = HttpServer.create(addr, 0); + server = HttpServer.create(new InetSocketAddress(0), 0); port = server.getAddress().getPort(); server.createContext(CONTEXT, new RequestHandler()); } catch (IOException e) { @@ -550,9 +566,9 @@ public boolean changeLifecycleState(String projectId, String lifecycleState) { *

              This method can be used to fully remove a project (to mimic when the server completely * deletes a project). * - * @return true if the project was successfully deleted, false otherwise. + * @return true if the project was successfully deleted, false if the project didn't exist. */ public boolean removeProject(String projectId) { - return projects.remove(checkNotNull(projectId)) != null ? true : false; + return projects.remove(checkNotNull(projectId)) != null; } } diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java index 5bd87d1d4335..785aa88d49fb 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java @@ -68,10 +68,9 @@ private static com.google.api.services.cloudresourcemanager.model.Project copyFr } private void clearProjects() { - Iterator it = - rpc.list(EMPTY_RPC_OPTIONS).y().iterator(); - while (it.hasNext()) { - RESOURCE_MANAGER_HELPER.removeProject(it.next().getProjectId()); + for (com.google.api.services.cloudresourcemanager.model.Project project : + rpc.list(EMPTY_RPC_OPTIONS).y()) { + RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); } } @@ -196,7 +195,8 @@ public void testIsInvalidProjectLabels() { for (int i = 0; i < 257; i++) { tooManyLabels.put("k" + Integer.toString(i), "v" + Integer.toString(i)); } - expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(tooManyLabels); + expectInvalidArgumentException(project, "exceeds the limit of 256 labels"); project.setLabels(ImmutableMap.of("k-1", "")); rpc.create(project); assertNotNull(rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS)); From 937fe22bbc2cfaefdf8c8fdcb9fb030e9e7347c8 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Fri, 11 Dec 2015 15:26:44 -0800 Subject: [PATCH 25/35] minor fix --- .../resourcemanager/testing/LocalResourceManagerHelper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java index aacab5c6ee40..beb824ac812d 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java @@ -60,7 +60,6 @@ public class LocalResourceManagerHelper { try { BASE_CONTEXT = new URI(CONTEXT); } catch (URISyntaxException e) { - log.log(Level.WARNING, "URI.", e); throw new RuntimeException( "Could not initialize LocalResourceManagerHelper due to URISyntaxException.", e); } From ddf447fc258994c4a0baa1de834ab32bc895805d Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Wed, 2 Dec 2015 14:36:18 -0800 Subject: [PATCH 26/35] ResourceManagerImpl + docs --- README.md | 36 ++ TESTING.md | 46 +++ gcloud-java-resourcemanager/README.md | 143 +++++++- gcloud-java-resourcemanager/pom.xml | 6 + .../gcloud/resourcemanager/ProjectInfo.java | 3 +- .../resourcemanager/ResourceManagerImpl.java | 231 +++++++++++++ .../ResourceManagerOptions.java | 17 +- .../testing/LocalResourceManagerHelper.java | 5 +- .../LocalResourceManagerHelperTest.java | 16 +- .../ResourceManagerImplTest.java | 322 ++++++++++++++++++ 10 files changed, 806 insertions(+), 19 deletions(-) create mode 100644 gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java create mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java diff --git a/README.md b/README.md index 0fb8d82623b5..0f6e887b7ef2 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This client supports the following Google Cloud Platform services: - [Google Cloud Datastore] (#google-cloud-datastore) - [Google Cloud Storage] (#google-cloud-storage) +- [Google Cloud Resource Manager] (#google-cloud-resource-manager) > Note: This client is a work-in-progress, and may occasionally > make backwards-incompatible changes. @@ -182,6 +183,38 @@ if (blob == null) { } ``` +Google Cloud Resource Manager +---------------------- + +- [API Documentation][resourcemanager-api] +- [Official Documentation][cloud-resourcemanager-docs] + +#### Preview + +Here is a code snippet showing a simple usage example. Note that you must supply Google SDK credentials for this service, not other forms of authentication listed in the [Authentication section](#authentication). + +```java +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.resourcemanager.ProjectInfo; +import com.google.gcloud.resourcemanager.ResourceManager; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +import java.util.Iterator; + +ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); +ProjectInfo myProject = resourceManager.get("some-project-id-that-I-own"); +ProjectInfo newProjectInfo = resourceManager.replace(projectFromServer.toBuilder() + .labels(ImmutableMap.of("launch-status", "in-development")).build()); +System.out.println("Updated the labels of project " + newProjectInfo.projectId() + + " to be " + newProjectInfo.labels() + System.lineSeparator()); +// List all the projects you have permission to view. +Iterator projectIterator = resourceManager.list().iterateAll(); +System.out.println("Projects I can view:"); +while (projectIterator.hasNext()) { + System.out.println(projectIterator.next().projectId()); +} +``` + Troubleshooting --------------- @@ -241,3 +274,6 @@ Apache 2.0 - See [LICENSE] for more information. [cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets [cloud-storage-activation]: https://cloud.google.com/storage/docs/signup [storage-api]: http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/storage/package-summary.html + +[resourcemanager-api]:http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/resourcemanager/package-summary.html +[cloud-resourcemanager-docs]:https://cloud.google.com/resource-manager/ diff --git a/TESTING.md b/TESTING.md index 02a3d14ab0bf..d6edd000e6d6 100644 --- a/TESTING.md +++ b/TESTING.md @@ -65,5 +65,51 @@ Here is an example that clears the bucket created in Step 3 with a timeout of 5 RemoteGcsHelper.forceDelete(storage, bucket, 5, TimeUnit.SECONDS); ``` +### Testing code that uses Resource Manager + +#### On your machine + +You can test against a temporary local Resource Manager by following these steps: + +1. Before running your testing code, start the Resource Manager emulator `LocalResourceManagerHelper`. This can be done as follows: + + ```java + import com.google.gcloud.resourcemanager.testing.LocalResourceManagerHelper; + + LocalResourceManagerHelper helper = LocalResourceManagerHelper.create(); + helper.start(); + ``` + + This will spawn a server thread that listens to `localhost` at an ephemeral port for Resource Manager requests. + +2. In your program, create and use a Resource Manager service object whose host is set host to `localhost` at the appropriate port. For example: + + ```java + ResourceManager resourceManager = ResourceManagerOptions.builder() + .host("http://localhost:" + helper.port()).build().service(); + ``` + +3. Run your tests. + +4. Stop the Resource Manager emulator. + + ```java + helper.stop(); + ``` + + This method will block a short amount of time until the server thread has been terminated. + +#### On a remote machine + +You can test against a remote Resource Manager emulator as well. To do this, set the host to the hostname of the remote machine, like the example below. + + ```java + ResourceManager resourceManager = ResourceManagerOptions.builder() + .host("http://:").build(); + ``` + +Note that the remote Resource Manager emulator must be running before your tests are run. + + [cloud-platform-storage-authentication]:https://cloud.google.com/storage/docs/authentication?hl=en#service_accounts diff --git a/gcloud-java-resourcemanager/README.md b/gcloud-java-resourcemanager/README.md index 11c505c14920..2a229720536c 100644 --- a/gcloud-java-resourcemanager/README.md +++ b/gcloud-java-resourcemanager/README.md @@ -27,27 +27,153 @@ Example Application Authentication -------------- -See the [Authentication](https://github.com/GoogleCloudPlatform/gcloud-java#authentication) section in the base directory's README. +Unlike other `gcloud-java` service libraries, `gcloud-java-resourcemanager` only accepts Google Cloud SDK credentials at this time. If you are having trouble authenticating, it may be that you have other types of credentials that override your Google Cloud SDK credentials. See more about Google Cloud SDK credentials and credential precedence in the global README's [Authentication section](https://github.com/GoogleCloudPlatform/gcloud-java#authentication). About Google Cloud Resource Manager ----------------------------------- -Google [Cloud Resource Manager][cloud-resourcemanager] provides a programmatic way to manage your Google Cloud Platform projects. Google Cloud Resource Manager is currently in beta and may occasionally make backwards incompatible changes. +Google [Cloud Resource Manager][cloud-resourcemanager] provides a programmatic way to manage your Google Cloud Platform projects. With this API, you can do the following: + +* Get a list of all projects associated with an account. +* Create new projects. +* Update existing projects. +* Delete projects. +* Undelete, or recover, projects that you don't want to delete. + +Google Cloud Resource Manager is currently in beta and may occasionally make backwards incompatible changes. Be sure to activate the Google Cloud Resource Manager API on the Developer's Console to use Resource Manager from your project. See the ``gcloud-java`` API [Resource Manager documentation][resourcemanager-api] to learn how to interact with the Cloud Resource Manager using this client Library. - +Getting Started +--------------- +#### Prerequisites +You will also need to set up the local development environment by [installing the Google Cloud SDK](https://cloud.google.com/sdk/) and running the following commands in command line: `gcloud auth login`. + +> Note: You don't need a project ID to use this service. If you have a project ID set in the Google Cloud SDK, you can unset it by typing `gcloud config unset project` in command line. + +#### Installation and setup +You'll need to obtain the `gcloud-java-resourcemanager` library. See the [Quickstart](#quickstart) section to add `gcloud-java-resourcemanager` as a dependency in your code. + +#### Creating an authorized service object +To make authenticated requests to Google Cloud Resource Manager, you must create a service object with Google Cloud SDK credentials. You can then make API calls by calling methods on the Resource Manager service object. The simplest way to authenticate is to use [Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials). These credentials are automatically inferred from your environment, so you only need the following code to create your service object: + +```java +import com.google.gcloud.resourcemanager.ResourceManager; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); +``` + +#### Creating a project +All you need to create a project is a globally unique project ID. You can also optionally attach a non-unique name and labels to your project. Read more about naming guidelines for project IDs, names, and labels [here](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). To create a project, add the following imports at the top of your file: + +```java +import com.google.gcloud.resourcemanager.ProjectInfo; +``` + +Then add the following code to create a project (be sure to change `myProjectId` to be something unique). + +```java +String myProjectId = "my-globally-unique-project-id"; // Change to a unique project ID. +ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build()); +``` + +Note that the return value from `create` is a `ProjectInfo` that includes additional read-only information, like creation time, project number, and lifecycle state. Read more about these fields on the [Projects page](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). + +#### Getting a specific project +You can load a project if you know it's project ID and have read permissions to the project. For example, say we wanted to get the project we just created. We can do the following: + +```java +ProjectInfo projectFromServer = resourceManager.get(myProjectId); +``` + +#### Editing a project +To edit a project, create a new `ProjectInfo` object and pass it in to the `ResourceManager.replace` method. + +Suppose that you want to add a label for the newly created project to denote that it's launch status is "in development". Import the following: + +```java +import com.google.common.collect.ImmutableMap; +``` + +Then add the following code to your program: + +```java +ProjectInfo newProjectInfo = resourceManager.replace(projectFromServer.toBuilder() + .labels(ImmutableMap.of("launch-status", "in-development")).build()); +``` + +Note that the values of the project you pass in to `replace` overwrite the server's values for non-read-only fields, namely `projectName` and `labels`. For example, if you create a project with `projectName` "some-project-name" and subsequently call replace using a `ProjectInfo` object that didn't set the `projectName`, then the server will unset the project's name. The server ignores any attempted changes to the read-only fields `projectNumber`, `lifecycleState`, and `createTime`. The `projectId` cannot change. + +#### Listing all projects +Suppose that we want list of all projects for which we have read permissions. Add the following import: + +```java +import java.util.Iterator; +``` + +Then add the following code to print a list of projects you can view: + +```java +Iterator projectIterator = resourceManager.list().iterateAll(); +System.out.println("Projects I can view:"); +while (projectIterator.hasNext()) { + System.out.println(projectIterator.next().projectId()); +} +``` + +#### Complete source code + +Here we put together all the code shown above into one program. This program assumes that you are running from your own desktop. + +```java +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.resourcemanager.ProjectInfo; +import com.google.gcloud.resourcemanager.ResourceManager; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +import java.util.Iterator; + +public class GcloudJavaResourceManagerExample { + + public static void main(String[] args) { + // Create Resource Manager service object. + // By default, credentials are inferred from the runtime environment. + ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); + + // Create a project. + String myProjectId = "my-globally-unique-project-id"; // Change to a unique project ID. + ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build()); + + // Get a project from the server. + ProjectInfo projectFromServer = resourceManager.get(myProjectId); + System.out.println("Got project " + projectFromServer.projectId() + " from the server." + + System.lineSeparator()); + + // Update a project + ProjectInfo newProjectInfo = resourceManager.replace(projectFromServer.toBuilder() + .labels(ImmutableMap.of("launch-status", "in-development")).build()); + System.out.println("Updated the labels of project " + newProjectInfo.projectId() + + " to be " + newProjectInfo.labels() + System.lineSeparator()); + + // List all the projects you have permission to view. + Iterator projectIterator = resourceManager.list().iterateAll(); + System.out.println("Projects I can view:"); + while (projectIterator.hasNext()) { + System.out.println(projectIterator.next().projectId()); + } + } +} +``` Java Versions ------------- Java 7 or above is required for using this client. - - Versioning ---------- @@ -57,6 +183,13 @@ It is currently in major version zero (``0.y.z``), which means that anything may change at any time and the public API should not be considered stable. +Testing +------- + +This library has tools to help write tests for code that uses Resource Manager. + +See [TESTING] to read more about testing. + Contributing ------------ diff --git a/gcloud-java-resourcemanager/pom.xml b/gcloud-java-resourcemanager/pom.xml index 515a5c50e04f..70a1b2148c46 100644 --- a/gcloud-java-resourcemanager/pom.xml +++ b/gcloud-java-resourcemanager/pom.xml @@ -27,6 +27,12 @@ google-api-services-cloudresourcemanager v1beta1-rev6-1.19.0 compile + + + com.google.guava + guava-jdk5 + + junit 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 index 5d490e662df0..8e731ecfdbf9 100644 --- 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 @@ -31,7 +31,6 @@ /** * 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. */ @@ -324,7 +323,7 @@ com.google.api.services.cloudresourcemanager.model.Project toPb() { projectPb.setLifecycleState(state.toString()); } if (createTimeMillis != null) { - projectPb.setCreateTime(ISODateTimeFormat.dateTime().print(createTimeMillis)); + projectPb.setCreateTime(ISODateTimeFormat.dateTime().withZoneUTC().print(createTimeMillis)); } if (parent != null) { projectPb.setParent(parent.toPb()); diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java new file mode 100644 index 000000000000..3f01e44c3b52 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java @@ -0,0 +1,231 @@ +/* + * 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.checkArgument; +import static com.google.gcloud.RetryHelper.runWithRetries; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.gcloud.BaseService; +import com.google.gcloud.ExceptionHandler; +import com.google.gcloud.ExceptionHandler.Interceptor; +import com.google.gcloud.Page; +import com.google.gcloud.PageImpl; +import com.google.gcloud.RetryHelper.RetryHelperException; +import com.google.gcloud.spi.ResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpc.Tuple; + +import java.io.Serializable; +import java.util.Map; +import java.util.concurrent.Callable; + +public class ResourceManagerImpl + extends BaseService implements ResourceManager { + + private static final Interceptor EXCEPTION_HANDLER_INTERCEPTOR = new Interceptor() { + + @Override + public RetryResult afterEval(Exception exception, RetryResult retryResult) { + return Interceptor.RetryResult.CONTINUE_EVALUATION; + } + + @Override + public RetryResult beforeEval(Exception exception) { + if (exception instanceof ResourceManagerException) { + boolean retriable = ((ResourceManagerException) exception).retryable(); + return retriable ? Interceptor.RetryResult.RETRY : Interceptor.RetryResult.NO_RETRY; + } + return Interceptor.RetryResult.CONTINUE_EVALUATION; + } + }; + static final ExceptionHandler EXCEPTION_HANDLER = ExceptionHandler.builder() + .abortOn(RuntimeException.class) + .interceptor(EXCEPTION_HANDLER_INTERCEPTOR) + .build(); + + private final ResourceManagerRpc resourceManagerRpc; + + ResourceManagerImpl(ResourceManagerOptions options) { + super(options); + resourceManagerRpc = options.rpc(); + } + + @Override + public ProjectInfo create(ProjectInfo project) { + final com.google.api.services.cloudresourcemanager.model.Project projectPb = project.toPb(); + try { + return ProjectInfo.fromPb(runWithRetries( + new Callable() { + @Override + public com.google.api.services.cloudresourcemanager.model.Project call() { + return resourceManagerRpc.create(projectPb); + } + }, options().retryParams(), EXCEPTION_HANDLER)); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + @Override + public void delete(final String projectId) { + try { + runWithRetries(new Callable() { + @Override + public Void call() { + resourceManagerRpc.delete(projectId); + return null; + } + }, options().retryParams(), EXCEPTION_HANDLER); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + @Override + public ProjectInfo get(final String projectId, ProjectGetOption... options) { + final Map optionsMap = optionMap(options); + try { + return ProjectInfo.fromPb(runWithRetries( + new Callable() { + @Override + public com.google.api.services.cloudresourcemanager.model.Project call() { + return resourceManagerRpc.get(projectId, optionsMap); + } + }, options().retryParams(), EXCEPTION_HANDLER)); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + private abstract static class BasePageFetcher + implements PageImpl.NextPageFetcher { + protected final Map requestOptions; + protected final ResourceManagerOptions serviceOptions; + + BasePageFetcher(ResourceManagerOptions serviceOptions, String cursor, + Map optionMap) { + this.serviceOptions = serviceOptions; + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (cursor != null) { + builder.put(ResourceManagerRpc.Option.PAGE_TOKEN, cursor); + } + for (Map.Entry option : optionMap.entrySet()) { + if (option.getKey() != ResourceManagerRpc.Option.PAGE_TOKEN) { + builder.put(option.getKey(), option.getValue()); + } + } + this.requestOptions = builder.build(); + } + } + + private static class ProjectPageFetcher extends BasePageFetcher { + ProjectPageFetcher(ResourceManagerOptions serviceOptions, String cursor, + Map optionMap) { + super(serviceOptions, cursor, optionMap); + } + + @Override + public Page nextPage() { + return listProjects(serviceOptions, requestOptions); + } + } + + @Override + public Page list(ProjectListOption... options) { + return listProjects(options(), optionMap(options)); + } + + private static Page listProjects( + final ResourceManagerOptions serviceOptions, + final Map optionsMap) { + try { + Tuple> result = + runWithRetries( + new Callable>>() { + @Override + public Tuple> + call() { + return serviceOptions.rpc().list(optionsMap); + } + }, + serviceOptions.retryParams(), EXCEPTION_HANDLER); + String cursor = result.x(); + Iterable projects = + result.y() == null + ? ImmutableList.of() : Iterables.transform( + result.y(), + new Function() { + @Override + public ProjectInfo apply( + com.google.api.services.cloudresourcemanager.model.Project projectPb) { + return ProjectInfo.fromPb(projectPb); + } + }); + return new PageImpl<>( + new ProjectPageFetcher(serviceOptions, cursor, optionsMap), cursor, projects); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + @Override + public ProjectInfo replace(ProjectInfo newProject) { + final com.google.api.services.cloudresourcemanager.model.Project projectPb = newProject.toPb(); + try { + return ProjectInfo.fromPb(runWithRetries( + new Callable() { + @Override + public com.google.api.services.cloudresourcemanager.model.Project call() { + return resourceManagerRpc.replace(projectPb); + } + }, options().retryParams(), EXCEPTION_HANDLER)); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + @Override + public void undelete(final String projectId) { + try { + runWithRetries(new Callable() { + @Override + public Void call() { + resourceManagerRpc.undelete(projectId); + return null; + } + }, options().retryParams(), EXCEPTION_HANDLER); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + private Map optionMap(Option... options) { + Map temp = Maps.newEnumMap(ResourceManagerRpc.Option.class); + for (Option option : options) { + Object prev = temp.put(option.rpcOption(), option.value()); + checkArgument(prev == null, "Duplicate option %s", option); + } + return ImmutableMap.copyOf(temp); + } +} 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 990163c459da..563d1f9c3489 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 @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableSet; import com.google.gcloud.ServiceOptions; +import com.google.gcloud.spi.DefaultResourceManagerRpc; import com.google.gcloud.spi.ResourceManagerRpc; import com.google.gcloud.spi.ResourceManagerRpcFactory; @@ -28,15 +29,17 @@ public class ResourceManagerOptions private static final long serialVersionUID = 538303101192527452L; private static final String GCRM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; - private static final Set SCOPES = ImmutableSet.of(GCRM_SCOPE); + private static final String READ_ONLY_GCRM_SCOPE = + "https://www.googleapis.com/auth/cloud-platform.read-only"; + private static final Set SCOPES = ImmutableSet.of(GCRM_SCOPE, READ_ONLY_GCRM_SCOPE); + private static final String DEFAULT_HOST = "https://cloudresourcemanager.googleapis.com"; public static class DefaultResourceManagerFactory implements ResourceManagerFactory { private static final ResourceManagerFactory INSTANCE = new DefaultResourceManagerFactory(); @Override public ResourceManager create(ResourceManagerOptions options) { - // return new ResourceManagerImpl(options); - return null; // TODO(ajaykannan): Fix me! + return new ResourceManagerImpl(options); } } @@ -53,11 +56,15 @@ public static class DefaultResourceManagerRpcFactory implements ResourceManagerR @Override public ResourceManagerRpc create(ResourceManagerOptions options) { - // return new DefaultResourceManagerRpc(options); - return null; // TODO(ajaykannan): Fix me! + return new DefaultResourceManagerRpc(options); } } + @Override + protected String defaultHost() { + return DEFAULT_HOST; + } + public static class Builder extends ServiceOptions.Builder { diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java index beb824ac812d..61ff82519afe 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java @@ -236,7 +236,10 @@ private static Map parseListOptions(String query) { String[] argEntry = arg.split("="); switch (argEntry[0]) { case "fields": - options.put("fields", argEntry[1].split(",")); + // List fields are in the form "projects(field1, field2, ...)" + options.put( + "fields", + argEntry[1].substring("projects(".length(), argEntry[1].length() - 1).split(",")); break; case "filter": options.put("filter", argEntry[1].split(" ")); diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java index 785aa88d49fb..0b8438b755d6 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java @@ -19,7 +19,6 @@ import org.junit.Test; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; public class LocalResourceManagerHelperTest { @@ -295,16 +294,21 @@ public void testList() { COMPLETE_PROJECT.getProjectId(), "DELETE_REQUESTED"); rpc.create(PROJECT_WITH_PARENT); projects = rpc.list(EMPTY_RPC_OPTIONS); - Iterator it = - projects.y().iterator(); - compareReadWriteFields(COMPLETE_PROJECT, it.next()); - compareReadWriteFields(PROJECT_WITH_PARENT, it.next()); + for (com.google.api.services.cloudresourcemanager.model.Project p : projects.y()) { + if (p.getProjectId().equals(COMPLETE_PROJECT.getProjectId())) { + compareReadWriteFields(COMPLETE_PROJECT, p); + } else if (p.getProjectId().equals(PROJECT_WITH_PARENT.getProjectId())) { + compareReadWriteFields(PROJECT_WITH_PARENT, p); + } else { + fail("Unexpected project in list."); + } + } } @Test public void testListFieldOptions() { Map rpcOptions = new HashMap<>(); - rpcOptions.put(ResourceManagerRpc.Option.FIELDS, "projectId,name,labels"); + rpcOptions.put(ResourceManagerRpc.Option.FIELDS, "projects(projectId,name,labels)"); rpcOptions.put(ResourceManagerRpc.Option.PAGE_TOKEN, "somePageToken"); rpcOptions.put(ResourceManagerRpc.Option.PAGE_SIZE, 1); rpc.create(PROJECT_WITH_PARENT); diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java new file mode 100644 index 000000000000..746159138bd0 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java @@ -0,0 +1,322 @@ +/* + * 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.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.Page; +import com.google.gcloud.RetryParams; +import com.google.gcloud.resourcemanager.ProjectInfo.ResourceId; +import com.google.gcloud.resourcemanager.ResourceManager.ProjectField; +import com.google.gcloud.resourcemanager.ResourceManager.ProjectGetOption; +import com.google.gcloud.resourcemanager.ResourceManager.ProjectListOption; +import com.google.gcloud.resourcemanager.testing.LocalResourceManagerHelper; +import com.google.gcloud.spi.ResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpcFactory; + +import org.easymock.EasyMock; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.Map; + +public class ResourceManagerImplTest { + + private static final LocalResourceManagerHelper RESOURCE_MANAGER_HELPER = + LocalResourceManagerHelper.create(); + private static final ResourceManager RESOURCE_MANAGER = ResourceManagerOptions.builder() + .host("http://localhost:" + RESOURCE_MANAGER_HELPER.port()) + .build() + .service(); + private static final ProjectGetOption GET_FIELDS = + ProjectGetOption.fields(ProjectField.NAME, ProjectField.CREATE_TIME); + private static final ProjectListOption LIST_FIELDS = + ProjectListOption.fields(ProjectField.NAME, ProjectField.LABELS); + private static final ProjectListOption LIST_FILTER = + ProjectListOption.filter("id:* name:myProject labels.color:blue LABELS.SIZE:*"); + private static final ProjectInfo PARTIAL_PROJECT = ProjectInfo.builder("partial-project").build(); + private static final ResourceId PARENT = new ResourceId("id", "type"); + private static final ProjectInfo COMPLETE_PROJECT = ProjectInfo.builder("complete-project") + .name("name") + .labels(ImmutableMap.of("k1", "v1")) + .parent(PARENT) + .build(); + private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @BeforeClass + public static void beforeClass() { + RESOURCE_MANAGER_HELPER.start(); + } + + @Before + public void setUp() { + clearProjects(); + } + + private void clearProjects() { + for (ProjectInfo project : RESOURCE_MANAGER.list().values()) { + RESOURCE_MANAGER_HELPER.removeProject(project.projectId()); + } + } + + @AfterClass + public static void afterClass() { + RESOURCE_MANAGER_HELPER.stop(); + } + + private void compareReadWriteFields(ProjectInfo expected, ProjectInfo actual) { + assertEquals(expected.projectId(), actual.projectId()); + assertEquals(expected.name(), actual.name()); + assertEquals(expected.labels(), actual.labels()); + assertEquals(expected.parent(), actual.parent()); + } + + @Test + public void testCreate() { + ProjectInfo returnedProject = RESOURCE_MANAGER.create(PARTIAL_PROJECT); + compareReadWriteFields(PARTIAL_PROJECT, returnedProject); + assertEquals(ProjectInfo.State.ACTIVE, returnedProject.state()); + assertTrue(returnedProject.labels().isEmpty()); + assertNull(returnedProject.name()); + assertNull(returnedProject.parent()); + assertNotNull(returnedProject.projectNumber()); + assertNotNull(returnedProject.createTimeMillis()); + try { + RESOURCE_MANAGER.create(PARTIAL_PROJECT); + fail("Should fail, project already exists."); + } catch (ResourceManagerException e) { + assertEquals(409, e.code()); + assertTrue(e.getMessage().startsWith("A project with the same project ID") + && e.getMessage().endsWith("already exists.")); + } + returnedProject = RESOURCE_MANAGER.create(COMPLETE_PROJECT); + compareReadWriteFields(COMPLETE_PROJECT, returnedProject); + assertEquals(ProjectInfo.State.ACTIVE, returnedProject.state()); + assertNotNull(returnedProject.projectNumber()); + assertNotNull(returnedProject.createTimeMillis()); + } + + @Test + public void testDelete() { + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + RESOURCE_MANAGER.delete(COMPLETE_PROJECT.projectId()); + assertEquals( + ProjectInfo.State.DELETE_REQUESTED, + RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()).state()); + try { + RESOURCE_MANAGER.delete("some-nonexistant-project-id"); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("not found.")); + } + } + + @Test + public void testGet() { + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + ProjectInfo returnedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()); + compareReadWriteFields(COMPLETE_PROJECT, returnedProject); + RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.projectId()); + try { + RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("not found")); + } + } + + @Test + public void testGetWithOptions() { + ProjectInfo originalProject = RESOURCE_MANAGER.create(COMPLETE_PROJECT); + ProjectInfo returnedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId(), GET_FIELDS); + assertFalse(COMPLETE_PROJECT.equals(returnedProject)); + assertEquals(COMPLETE_PROJECT.projectId(), returnedProject.projectId()); + assertEquals(COMPLETE_PROJECT.name(), returnedProject.name()); + assertEquals(originalProject.createTimeMillis(), returnedProject.createTimeMillis()); + assertNull(returnedProject.parent()); + assertNull(returnedProject.projectNumber()); + assertNull(returnedProject.state()); + assertTrue(returnedProject.labels().isEmpty()); + } + + @Test + public void testList() { + Page projects = RESOURCE_MANAGER.list(); + assertFalse(projects.values().iterator().hasNext()); // change this when #421 is resolved + RESOURCE_MANAGER.create(PARTIAL_PROJECT); + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + for (ProjectInfo p : RESOURCE_MANAGER.list().values()) { + if (p.projectId().equals(PARTIAL_PROJECT.projectId())) { + compareReadWriteFields(PARTIAL_PROJECT, p); + } else if (p.projectId().equals(COMPLETE_PROJECT.projectId())) { + compareReadWriteFields(COMPLETE_PROJECT, p); + } else { + fail("Some unexpected project returned by list."); + } + } + } + + @Test + public void testListFieldOptions() { + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + Page projects = RESOURCE_MANAGER.list(LIST_FIELDS); + ProjectInfo returnedProject = projects.iterateAll().next(); + assertEquals(COMPLETE_PROJECT.projectId(), returnedProject.projectId()); + assertEquals(COMPLETE_PROJECT.name(), returnedProject.name()); + assertEquals(COMPLETE_PROJECT.labels(), returnedProject.labels()); + assertNull(returnedProject.parent()); + assertNull(returnedProject.projectNumber()); + assertNull(returnedProject.state()); + assertNull(returnedProject.createTimeMillis()); + } + + @Test + public void testListFilterOptions() { + ProjectInfo matchingProject = ProjectInfo.builder("matching-project") + .name("MyProject") + .labels(ImmutableMap.of("color", "blue", "size", "big")) + .build(); + ProjectInfo nonMatchingProject1 = ProjectInfo.builder("non-matching-project1") + .name("myProject") + .labels(ImmutableMap.of("color", "blue")) + .build(); + ProjectInfo nonMatchingProject2 = ProjectInfo.builder("non-matching-project2") + .name("myProj") + .labels(ImmutableMap.of("color", "blue", "size", "big")) + .build(); + ProjectInfo nonMatchingProject3 = ProjectInfo.builder("non-matching-project3").build(); + RESOURCE_MANAGER.create(matchingProject); + RESOURCE_MANAGER.create(nonMatchingProject1); + RESOURCE_MANAGER.create(nonMatchingProject2); + RESOURCE_MANAGER.create(nonMatchingProject3); + for (ProjectInfo p : RESOURCE_MANAGER.list(LIST_FILTER).values()) { + assertFalse(p.equals(nonMatchingProject1)); + assertFalse(p.equals(nonMatchingProject2)); + compareReadWriteFields(matchingProject, p); + } + } + + @Test + public void testReplace() { + ProjectInfo createdProject = RESOURCE_MANAGER.create(COMPLETE_PROJECT); + String newName = "new name"; + Map newLabels = ImmutableMap.of("new k1", "new v1"); + ProjectInfo anotherCompleteProject = ProjectInfo.builder(COMPLETE_PROJECT.projectId()) + .name(newName) + .labels(newLabels) + .projectNumber(987654321L) + .createTimeMillis(230682061315L) + .state(ProjectInfo.State.DELETE_REQUESTED) + .parent(createdProject.parent()) + .build(); + ProjectInfo returnedProject = RESOURCE_MANAGER.replace(anotherCompleteProject); + compareReadWriteFields(anotherCompleteProject, returnedProject); + assertEquals(createdProject.projectNumber(), returnedProject.projectNumber()); + assertEquals(createdProject.createTimeMillis(), returnedProject.createTimeMillis()); + assertEquals(createdProject.state(), returnedProject.state()); + ProjectInfo nonexistantProject = + ProjectInfo.builder("some-project-id-that-does-not-exist").build(); + try { + RESOURCE_MANAGER.replace(nonexistantProject); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("the project was not found")); + } + } + + @Test + public void testUndelete() { + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + RESOURCE_MANAGER.delete(COMPLETE_PROJECT.projectId()); + assertEquals( + ProjectInfo.State.DELETE_REQUESTED, + RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()).state()); + RESOURCE_MANAGER.undelete(COMPLETE_PROJECT.projectId()); + ProjectInfo revivedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()); + compareReadWriteFields(COMPLETE_PROJECT, revivedProject); + assertEquals(ProjectInfo.State.ACTIVE, revivedProject.state()); + try { + RESOURCE_MANAGER.undelete("invalid-project-id"); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("the project was not found")); + } + } + + @Test + public void testRetryableException() { + ResourceManagerRpcFactory rpcFactoryMock = EasyMock.createMock(ResourceManagerRpcFactory.class); + ResourceManagerRpc resourceManagerRpcMock = EasyMock.createMock(ResourceManagerRpc.class); + EasyMock.expect(rpcFactoryMock.create(EasyMock.anyObject(ResourceManagerOptions.class))) + .andReturn(resourceManagerRpcMock); + EasyMock.replay(rpcFactoryMock); + ResourceManager resourceManagerMock = ResourceManagerOptions.builder() + .serviceRpcFactory(rpcFactoryMock) + .retryParams(RetryParams.defaultInstance()) + .build() + .service(); + EasyMock.expect(resourceManagerRpcMock.get(PARTIAL_PROJECT.projectId(), EMPTY_RPC_OPTIONS)) + .andThrow(new ResourceManagerException(500, "Internal Error", true)) + .andReturn(PARTIAL_PROJECT.toPb()); + EasyMock.replay(resourceManagerRpcMock); + ProjectInfo returnedProject = resourceManagerMock.get(PARTIAL_PROJECT.projectId()); + assertEquals(PARTIAL_PROJECT, returnedProject); + } + + @Test + public void testNonRetryableException() { + String projectId = "some-project-id"; + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Project " + projectId + " not found."); + RESOURCE_MANAGER.get(projectId); + } + + @Test + public void testRuntimeException() { + ResourceManagerRpcFactory rpcFactoryMock = EasyMock.createMock(ResourceManagerRpcFactory.class); + ResourceManagerRpc resourceManagerRpcMock = EasyMock.createMock(ResourceManagerRpc.class); + EasyMock.expect(rpcFactoryMock.create(EasyMock.anyObject(ResourceManagerOptions.class))) + .andReturn(resourceManagerRpcMock); + EasyMock.replay(rpcFactoryMock); + ResourceManager resourceManagerMock = + ResourceManagerOptions.builder().serviceRpcFactory(rpcFactoryMock).build().service(); + String exceptionMessage = "Artificial runtime exception"; + EasyMock.expect(resourceManagerRpcMock.get(PARTIAL_PROJECT.projectId(), EMPTY_RPC_OPTIONS)) + .andThrow(new RuntimeException(exceptionMessage)); + EasyMock.replay(resourceManagerRpcMock); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage(exceptionMessage); + resourceManagerMock.get(PARTIAL_PROJECT.projectId()); + } +} From 8c12d6797ca95c641e7e59a3dc1a5c1b259a0b80 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Wed, 16 Dec 2015 09:44:58 -0800 Subject: [PATCH 27/35] Fix docs and return null if project not found by get. --- README.md | 7 ++-- TESTING.md | 4 +-- gcloud-java-resourcemanager/README.md | 28 ++++++--------- .../resourcemanager/ResourceManager.java | 30 ++++++++-------- .../resourcemanager/ResourceManagerImpl.java | 36 ++++++++++--------- .../ResourceManagerOptions.java | 4 +-- .../gcloud/spi/DefaultResourceManagerRpc.java | 7 +++- .../LocalResourceManagerHelperTest.java | 16 ++------- .../ResourceManagerImplTest.java | 35 ++++++++++-------- 9 files changed, 80 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 0f6e887b7ef2..8a454c2933a3 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,6 @@ Google Cloud Resource Manager Here is a code snippet showing a simple usage example. Note that you must supply Google SDK credentials for this service, not other forms of authentication listed in the [Authentication section](#authentication). ```java -import com.google.common.collect.ImmutableMap; import com.google.gcloud.resourcemanager.ProjectInfo; import com.google.gcloud.resourcemanager.ResourceManager; import com.google.gcloud.resourcemanager.ResourceManagerOptions; @@ -203,10 +202,10 @@ import java.util.Iterator; ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); ProjectInfo myProject = resourceManager.get("some-project-id-that-I-own"); -ProjectInfo newProjectInfo = resourceManager.replace(projectFromServer.toBuilder() - .labels(ImmutableMap.of("launch-status", "in-development")).build()); +ProjectInfo newProjectInfo = resourceManager.replace(myProject.toBuilder() + .addLabel("launch-status", "in-development").build()); System.out.println("Updated the labels of project " + newProjectInfo.projectId() - + " to be " + newProjectInfo.labels() + System.lineSeparator()); + + " to be " + newProjectInfo.labels()); // List all the projects you have permission to view. Iterator projectIterator = resourceManager.list().iterateAll(); System.out.println("Projects I can view:"); diff --git a/TESTING.md b/TESTING.md index d6edd000e6d6..7f513a023621 100644 --- a/TESTING.md +++ b/TESTING.md @@ -82,7 +82,7 @@ You can test against a temporary local Resource Manager by following these steps This will spawn a server thread that listens to `localhost` at an ephemeral port for Resource Manager requests. -2. In your program, create and use a Resource Manager service object whose host is set host to `localhost` at the appropriate port. For example: +2. In your program, create and use a Resource Manager service object whose host is set to `localhost` at the appropriate port. For example: ```java ResourceManager resourceManager = ResourceManagerOptions.builder() @@ -97,7 +97,7 @@ You can test against a temporary local Resource Manager by following these steps helper.stop(); ``` - This method will block a short amount of time until the server thread has been terminated. + This method will block until the server thread has been terminated. #### On a remote machine diff --git a/gcloud-java-resourcemanager/README.md b/gcloud-java-resourcemanager/README.md index 2a229720536c..a58386776345 100644 --- a/gcloud-java-resourcemanager/README.md +++ b/gcloud-java-resourcemanager/README.md @@ -50,7 +50,7 @@ with the Cloud Resource Manager using this client Library. Getting Started --------------- #### Prerequisites -You will also need to set up the local development environment by [installing the Google Cloud SDK](https://cloud.google.com/sdk/) and running the following commands in command line: `gcloud auth login`. +You will need to set up the local development environment by [installing the Google Cloud SDK](https://cloud.google.com/sdk/) and running the following command in command line: `gcloud auth login`. > Note: You don't need a project ID to use this service. If you have a project ID set in the Google Cloud SDK, you can unset it by typing `gcloud config unset project` in command line. @@ -68,7 +68,7 @@ ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().servi ``` #### Creating a project -All you need to create a project is a globally unique project ID. You can also optionally attach a non-unique name and labels to your project. Read more about naming guidelines for project IDs, names, and labels [here](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). To create a project, add the following imports at the top of your file: +All you need to create a project is a globally unique project ID. You can also optionally attach a non-unique name and labels to your project. Read more about naming guidelines for project IDs, names, and labels [here](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). To create a project, add the following import at the top of your file: ```java import com.google.gcloud.resourcemanager.ProjectInfo; @@ -84,7 +84,7 @@ ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId). Note that the return value from `create` is a `ProjectInfo` that includes additional read-only information, like creation time, project number, and lifecycle state. Read more about these fields on the [Projects page](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). #### Getting a specific project -You can load a project if you know it's project ID and have read permissions to the project. For example, say we wanted to get the project we just created. We can do the following: +You can load a project if you know it's project ID and have read permissions to the project. For example, to get the project we just created we can do the following: ```java ProjectInfo projectFromServer = resourceManager.get(myProjectId); @@ -93,23 +93,17 @@ ProjectInfo projectFromServer = resourceManager.get(myProjectId); #### Editing a project To edit a project, create a new `ProjectInfo` object and pass it in to the `ResourceManager.replace` method. -Suppose that you want to add a label for the newly created project to denote that it's launch status is "in development". Import the following: - -```java -import com.google.common.collect.ImmutableMap; -``` - -Then add the following code to your program: +For example, to add a label for the newly created project to denote that it's launch status is "in development", add the following code: ```java ProjectInfo newProjectInfo = resourceManager.replace(projectFromServer.toBuilder() - .labels(ImmutableMap.of("launch-status", "in-development")).build()); + .addLabel("launch-status", "in-development").build()); ``` Note that the values of the project you pass in to `replace` overwrite the server's values for non-read-only fields, namely `projectName` and `labels`. For example, if you create a project with `projectName` "some-project-name" and subsequently call replace using a `ProjectInfo` object that didn't set the `projectName`, then the server will unset the project's name. The server ignores any attempted changes to the read-only fields `projectNumber`, `lifecycleState`, and `createTime`. The `projectId` cannot change. #### Listing all projects -Suppose that we want list of all projects for which we have read permissions. Add the following import: +Suppose that we want a list of all projects for which we have read permissions. Add the following import: ```java import java.util.Iterator; @@ -130,7 +124,6 @@ while (projectIterator.hasNext()) { Here we put together all the code shown above into one program. This program assumes that you are running from your own desktop. ```java -import com.google.common.collect.ImmutableMap; import com.google.gcloud.resourcemanager.ProjectInfo; import com.google.gcloud.resourcemanager.ResourceManager; import com.google.gcloud.resourcemanager.ResourceManagerOptions; @@ -150,14 +143,13 @@ public class GcloudJavaResourceManagerExample { // Get a project from the server. ProjectInfo projectFromServer = resourceManager.get(myProjectId); - System.out.println("Got project " + projectFromServer.projectId() + " from the server." - + System.lineSeparator()); + System.out.println("Got project " + projectFromServer.projectId() + " from the server."); // Update a project - ProjectInfo newProjectInfo = resourceManager.replace(projectFromServer.toBuilder() - .labels(ImmutableMap.of("launch-status", "in-development")).build()); + ProjectInfo newProjectInfo = resourceManager.replace(myProject.toBuilder() + .addLabel("launch-status", "in-development").build()); System.out.println("Updated the labels of project " + newProjectInfo.projectId() - + " to be " + newProjectInfo.labels() + System.lineSeparator()); + + " to be " + newProjectInfo.labels()); // List all the projects you have permission to view. Iterator projectIterator = resourceManager.list().iterateAll(); 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 1562fe51dad1..9ae524e28017 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 @@ -36,7 +36,7 @@ public interface ResourceManager extends Service { /** * The fields of a project. * - * These values can be used to specify the fields to include in a partial response when calling + *

              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. */ @@ -82,7 +82,7 @@ private ProjectGetOption(ResourceManagerRpc.Option option, Object 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. + *

              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. @@ -106,17 +106,17 @@ private ProjectListOption(ResourceManagerRpc.Option option, Object value) { /** * Returns an option to specify a filter. * - * Filter rules are case insensitive. The fields eligible for filtering are: + *

              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 + *

              You can specify multiple filters by adding a space between each filter. Multiple filters * are composed using "and". * - * Some examples of filters: + *

              Some examples of filters: *