From a4ed2ffcb6639b0630f5a4ed6c0ad9fb3cf14568 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Sun, 6 Dec 2015 13:15:00 -0800 Subject: [PATCH 1/6] 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 7d67a9f275d37c90c920cb1ca8ca1c9fc1da2334 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Tue, 8 Dec 2015 09:03:51 -0800 Subject: [PATCH 2/6] 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 f52065e9a36708c8df505fd8b372b93c41e52357 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Wed, 9 Dec 2015 16:05:29 -0800 Subject: [PATCH 3/6] 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 b676ed51d4a00e4f0586eb2b15d7ce833b651fb0 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Thu, 10 Dec 2015 09:44:09 -0800 Subject: [PATCH 4/6] 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 ff318236e37edbb44f9053aab66552cdd0428bac Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Fri, 11 Dec 2015 15:19:40 -0800 Subject: [PATCH 5/6] 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 0493c27b86b2779c6fcb7f2c9a079c046d3790ee Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Fri, 11 Dec 2015 15:26:44 -0800 Subject: [PATCH 6/6] 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); }