Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion google-cloud-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-cloudresourcemanager</artifactId>
<version>v1beta1-rev10-1.21.0</version>
<version>v1-rev470-1.23.0</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,58 +16,198 @@

package com.google.cloud.resourcemanager.spi.v1beta1;

import static com.google.cloud.RetryHelper.runWithRetries;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.cloudresourcemanager.Cloudresourcemanager;
import com.google.api.core.ApiClock;
import com.google.api.gax.retrying.ResultRetryAlgorithm;
import com.google.api.gax.retrying.RetrySettings;
import com.google.api.gax.retrying.TimedAttemptSettings;
import com.google.api.services.cloudresourcemanager.CloudResourceManager;
import com.google.api.services.cloudresourcemanager.model.GetIamPolicyRequest;
import com.google.api.services.cloudresourcemanager.model.ListProjectsResponse;
import com.google.api.services.cloudresourcemanager.model.Operation;
import com.google.api.services.cloudresourcemanager.model.Policy;
import com.google.api.services.cloudresourcemanager.model.Project;
import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
import com.google.api.services.cloudresourcemanager.model.Status;
import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsRequest;
import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsResponse;
import com.google.api.services.cloudresourcemanager.model.UndeleteProjectRequest;
import com.google.cloud.Tuple;
import com.google.cloud.http.BaseHttpServiceException;
import com.google.cloud.http.HttpTransportOptions;
import com.google.cloud.resourcemanager.ResourceManagerException;
import com.google.cloud.resourcemanager.ResourceManagerOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import com.google.api.client.json.JsonFactory;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import org.threeten.bp.Duration;

public class HttpResourceManagerRpc implements ResourceManagerRpc {

private final Cloudresourcemanager resourceManager;
private static final JsonFactory JSON_FACTORY =
new com.google.api.client.json.jackson.JacksonFactory();

// See doc of create() for more details:
// https://developers.google.com/resources/api-libraries/documentation/cloudresourcemanager/v1/java/latest/com/google/api/services/cloudresourcemanager/CloudResourceManager.Projects.html#create(com.google.api.services.cloudresourcemanager.model.Project)
private static final RetrySettings CREATE_RETRY_SETTINGS =
RetrySettings.newBuilder()
// SLO permits 30s at 90th percentile, double it for total limit
.setTotalTimeout(Duration.ofMinutes(1))

This comment was marked as spam.

This comment was marked as spam.

.setInitialRetryDelay(Duration.ofSeconds(5))

This comment was marked as spam.

This comment was marked as spam.

.setRetryDelayMultiplier(1.5)
// Observed P95 latency is 11s. We probably shouldn't sleep longer than this.
.setMaxRetryDelay(Duration.ofSeconds(11))
.setJittered(false)

This comment was marked as spam.

This comment was marked as spam.

.setInitialRpcTimeout(Duration.ofSeconds(5))
.setMaxRpcTimeout(Duration.ofSeconds(5))
.build();

private static final ResultRetryAlgorithm<Operation> OPERATION_HANDLER =
new ResultRetryAlgorithm<Operation>() {
@Override
public TimedAttemptSettings createNextAttempt(
Throwable prevThrowable, Operation prevResponse, TimedAttemptSettings prevSettings) {
return null;
}

@Override
public boolean shouldRetry(Throwable prevThrowable, Operation prevOp)
throws CancellationException {
return prevThrowable != null || prevOp.getDone() == null || prevOp.getDone() == false;
}
};

private final CloudResourceManager resourceManager;
private final ApiClock clock;

public HttpResourceManagerRpc(ResourceManagerOptions options) {
HttpTransportOptions transportOptions = (HttpTransportOptions) options.getTransportOptions();
HttpTransport transport = transportOptions.getHttpTransportFactory().create();
HttpRequestInitializer initializer = transportOptions.getHttpRequestInitializer(options);
resourceManager =
new Cloudresourcemanager.Builder(transport, new JacksonFactory(), initializer)
new CloudResourceManager.Builder(transport, new JacksonFactory(), initializer)
.setRootUrl(options.getHost())
.setApplicationName(options.getApplicationName())
.build();
clock = options.getClock();
}

private static ResourceManagerException translate(IOException exception) {
return new ResourceManagerException(exception);
}

private static ResourceManagerException translate(Status status) {
int code;
// reference: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
switch (status.getCode()) {

This comment was marked as spam.

This comment was marked as spam.

case 0:
code = 200;

This comment was marked as spam.

This comment was marked as spam.

break;
case 1:
code = 499;
break;
case 2:
code = 500;
break;
case 3:
code = 400;
break;
case 4:
code = 504;
break;
case 5:
code = 404;
break;
case 6:
code = 409;
break;
case 7:
code = 403;
break;
case 16:
code = 401;
break;
case 8:
code = 429;
break;
case 9:
code = 400;
break;
case 10:
code = 409;
break;
case 11:
code = 400;
break;
case 12:
code = 501;
break;
case 13:
code = 500;
break;
case 14:
code = 503;
break;
case 15:
code = 500;
break;
default:
code = BaseHttpServiceException.UNKNOWN_CODE;
}
return new ResourceManagerException(code, status.getMessage());
}

@Override
public Project create(Project project) {
try {
return resourceManager.projects().create(project).execute();
final Operation operation = resourceManager.projects().create(project).execute();

Operation finishedOp =
runWithRetries(
new Callable<Operation>() {
@Override
public Operation call() {
try {
return resourceManager.operations().get(operation.getName()).execute();
} catch (IOException ex) {
throw translate(ex);

This comment was marked as spam.

This comment was marked as spam.

} catch (Exception ex) {
throw ex;
}
}
},
CREATE_RETRY_SETTINGS,
OPERATION_HANDLER,
clock);
if (finishedOp.getError() != null) {
throw translate(finishedOp.getError());
}

// NOTE(pongad): Operation.getResponse() returns a Map<String, Object>.
// `(Project) finishedOp.getResponse()` doesn't work,
// because JSON deserializer in execute() didn't know to create a Project object.

This comment was marked as spam.

This comment was marked as spam.

// `new Project().putAll(finishedOp.getResponse())` doesn't work either.
// 64-bit integers are sent as strings in JSON,
// so execute(), not knowing the type, parses it as String, not Long.
String responseTxt = JSON_FACTORY.toString(finishedOp.getResponse());
return JSON_FACTORY.fromString(responseTxt, Project.class);

This comment was marked as spam.

} catch (IOException ex) {
throw translate(ex);
}

}

@Override
Expand Down Expand Up @@ -117,7 +257,7 @@ public Tuple<String, Iterable<Project>> list(Map<Option, ?> options) {
@Override
public void undelete(String projectId) {
try {
resourceManager.projects().undelete(projectId).execute();
resourceManager.projects().undelete(projectId, new UndeleteProjectRequest()).execute();
} catch (IOException ex) {
throw translate(ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import com.google.api.client.json.JsonFactory;
import com.google.api.services.cloudresourcemanager.model.Binding;
import com.google.api.services.cloudresourcemanager.model.Operation;
import com.google.api.services.cloudresourcemanager.model.Policy;
import com.google.api.services.cloudresourcemanager.model.Project;
import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
Expand All @@ -35,14 +36,10 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
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;
Expand All @@ -64,6 +61,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.joda.time.format.ISODateTimeFormat;

/**
* Utility to create a local Resource Manager mock for testing.
Expand Down Expand Up @@ -96,8 +94,9 @@ public class LocalResourceManagerHelper {
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 VERSION = "v1";
private static final String CONTEXT = "/" + VERSION + "/projects";
private static final String OPERATION_CONTEXT = "/" + VERSION + "/operations";
private static final URI BASE_CONTEXT;
private static final Set<String> SUPPORTED_COMPRESSION_ENCODINGS =
ImmutableSet.of("gzip", "x-gzip");
Expand Down Expand Up @@ -249,6 +248,48 @@ private Response handlePost(HttpExchange exchange, String path) throws IOExcepti
}
}

private class OperationRequestHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
// see https://cloud.google.com/resource-manager/reference/rest/
String projectId;
try {
projectId = new URI(OPERATION_CONTEXT).relativize(exchange.getRequestURI()).getPath();
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
Response response;
String requestMethod = exchange.getRequestMethod();
switch (requestMethod) {
case "GET":
Project project = projects.get(projectId);
if (project == null) {
response = Error.PERMISSION_DENIED.response("Project " + projectId + " not found.");
break;
}
try {
response =
new Response(
HTTP_OK,
jsonFactory.toString(new Operation().setDone(true).setResponse(project)));
} catch (IOException e) {
response =
Error.INTERNAL_ERROR.response(
"Error when serializing project " + project.getProjectId());
}
break;
default:
response =
Error.BAD_REQUEST.response(
"The server could not understand the following request URI: "
+ requestMethod
+ " "
+ projectId);
}
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();
Expand Down Expand Up @@ -397,7 +438,10 @@ synchronized Response create(Project project) {
.setVersion(0);
policies.put(project.getProjectId(), emptyPolicy);
try {
String createdProjectStr = jsonFactory.toString(project);
// Pretend it's not done yet.
String createdProjectStr =
jsonFactory.toString(
new Operation().setDone(false).setName("operations/" + project.getProjectId()));
return new Response(HTTP_OK, createdProjectStr);
} catch (IOException e) {
return Error.INTERNAL_ERROR.response("Error serializing project " + project.getProjectId());
Expand Down Expand Up @@ -659,6 +703,7 @@ private LocalResourceManagerHelper() {
server = HttpServer.create(new InetSocketAddress(0), 0);
port = server.getAddress().getPort();
server.createContext(CONTEXT, new RequestHandler());
server.createContext(OPERATION_CONTEXT, new OperationRequestHandler());
} catch (IOException e) {
throw new RuntimeException("Could not bind the mock Resource Manager server.", e);
}
Expand Down