From ca53a0317d7a6a6cb4a4a82cef1cac0ea5bca0e9 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Tue, 15 Dec 2015 10:32:58 -0800 Subject: [PATCH] Add tests for ResourceManagerImpl --- README.md | 3 +- TESTING.md | 7 +- gcloud-java-resourcemanager/README.md | 6 +- .../gcloud/resourcemanager/ProjectInfo.java | 1 - .../testing/LocalResourceManagerHelper.java | 5 +- .../LocalResourceManagerHelperTest.java | 16 +- .../ResourceManagerImplTest.java | 322 ++++++++++++++++++ 7 files changed, 344 insertions(+), 16 deletions(-) create mode 100644 gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java diff --git a/README.md b/README.md index 72d5fa8a5920..f8c04e2ffc57 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. @@ -190,7 +191,7 @@ Google Cloud Resource Manager #### Preview -Here is a code snippet showing a simple usage example from within Compute/App Engine. Note that you must [supply credentials](#authentication) if running this snippet elsewhere. +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; diff --git a/TESTING.md b/TESTING.md index 95d930ff8aa2..d6edd000e6d6 100644 --- a/TESTING.md +++ b/TESTING.md @@ -64,11 +64,12 @@ Here is an example that clears the bucket created in Step 3 with a timeout of 5 ```java RemoteGcsHelper.forceDelete(storage, bucket, 5, TimeUnit.SECONDS); ``` + ### Testing code that uses Resource Manager #### On your machine -You can test against a temporary local datastore by following these steps: +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: @@ -79,9 +80,9 @@ You can test against a temporary local datastore by following these steps: helper.start(); ``` - This will spawn a server thread that listens to localhost at an ephemeral port for Resource Manager requests. + 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 host to `localhost` at the appropriate port. For example: ```java ResourceManager resourceManager = ResourceManagerOptions.builder() diff --git a/gcloud-java-resourcemanager/README.md b/gcloud-java-resourcemanager/README.md index c76acf1646e1..2a229720536c 100644 --- a/gcloud-java-resourcemanager/README.md +++ b/gcloud-java-resourcemanager/README.md @@ -27,7 +27,7 @@ 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 ----------------------------------- @@ -67,8 +67,6 @@ import com.google.gcloud.resourcemanager.ResourceManagerOptions; ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); ``` -> Note: Other `gcloud-java` service libraries allow you to authenticate using alternative methods. However, `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 credential precedence in the [Authentication section](https://github.com/GoogleCloudPlatform/gcloud-java#authentication). - #### 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: @@ -108,7 +106,7 @@ 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 (`projectName` and `labels`). For example, if you create a project with `projectName` "some-project-name", and then call replace using a `ProjectInfo` object that didn't set the `projectName`, then the server unsets the project's name. The server ignores any changes to the read-only fields `projectNumber`, `lifecycleState`, and `createTime`. The `projectId` cannot change. +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: 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 9fa8ea06728b..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. */ 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()); + } +}