Skip to content

Commit eed8800

Browse files
authored
resourcemanager: bump dependency version (#2944)
Fixes #2942.
1 parent c7b5afe commit eed8800

3 files changed

Lines changed: 173 additions & 12 deletions

File tree

google-cloud-bom/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@
674674
<dependency>
675675
<groupId>com.google.apis</groupId>
676676
<artifactId>google-api-services-cloudresourcemanager</artifactId>
677-
<version>v1beta1-rev10-1.21.0</version>
677+
<version>v1-rev470-1.23.0</version>
678678
</dependency>
679679

680680
<dependency>

google-cloud-resourcemanager/src/main/java/com/google/cloud/resourcemanager/spi/v1beta1/HttpResourceManagerRpc.java

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,55 +16,171 @@
1616

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

19+
import static com.google.cloud.RetryHelper.runWithRetries;
1920
import static com.google.common.base.MoreObjects.firstNonNull;
2021
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
2122
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
2223

2324
import com.google.api.client.http.HttpRequestInitializer;
2425
import com.google.api.client.http.HttpTransport;
26+
import com.google.api.client.json.JsonFactory;
2527
import com.google.api.client.json.jackson.JacksonFactory;
26-
import com.google.api.services.cloudresourcemanager.Cloudresourcemanager;
28+
import com.google.api.core.ApiClock;
29+
import com.google.api.gax.retrying.ResultRetryAlgorithm;
30+
import com.google.api.gax.retrying.RetrySettings;
31+
import com.google.api.gax.retrying.TimedAttemptSettings;
32+
import com.google.api.services.cloudresourcemanager.CloudResourceManager;
2733
import com.google.api.services.cloudresourcemanager.model.GetIamPolicyRequest;
2834
import com.google.api.services.cloudresourcemanager.model.ListProjectsResponse;
35+
import com.google.api.services.cloudresourcemanager.model.Operation;
2936
import com.google.api.services.cloudresourcemanager.model.Policy;
3037
import com.google.api.services.cloudresourcemanager.model.Project;
3138
import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
39+
import com.google.api.services.cloudresourcemanager.model.Status;
3240
import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsRequest;
3341
import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsResponse;
42+
import com.google.api.services.cloudresourcemanager.model.UndeleteProjectRequest;
3443
import com.google.cloud.Tuple;
44+
import com.google.cloud.http.BaseHttpServiceException;
3545
import com.google.cloud.http.HttpTransportOptions;
3646
import com.google.cloud.resourcemanager.ResourceManagerException;
3747
import com.google.cloud.resourcemanager.ResourceManagerOptions;
3848
import com.google.common.collect.ImmutableList;
49+
import com.google.common.collect.ImmutableMap;
3950
import com.google.common.collect.ImmutableSet;
4051
import java.io.IOException;
4152
import java.util.List;
4253
import java.util.Map;
4354
import java.util.Set;
55+
import java.util.concurrent.Callable;
56+
import org.threeten.bp.Duration;
4457

4558
public class HttpResourceManagerRpc implements ResourceManagerRpc {
4659

47-
private final Cloudresourcemanager resourceManager;
60+
private static final JsonFactory JSON_FACTORY =
61+
new JacksonFactory();
62+
63+
// See doc of create() for more details:
64+
// 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)
65+
private static final RetrySettings CREATE_RETRY_SETTINGS =
66+
RetrySettings.newBuilder()
67+
// SLO permits 30s at 90th percentile, 4x it for total limit.
68+
// Observed latency is much lower: 11s at 95th percentile.
69+
.setTotalTimeout(Duration.ofMinutes(2))
70+
// Linked doc recommends polling at 5th second.
71+
.setInitialRetryDelay(Duration.ofSeconds(5))
72+
.setRetryDelayMultiplier(1.5)
73+
// Observed P95 latency is 11s. We probably shouldn't sleep longer than this.
74+
.setMaxRetryDelay(Duration.ofSeconds(11))
75+
.setJittered(true)
76+
.setInitialRpcTimeout(Duration.ofSeconds(5))
77+
.setMaxRpcTimeout(Duration.ofSeconds(5))
78+
.build();
79+
80+
// reference: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
81+
private static final ImmutableMap<Integer, Integer> RPC_TO_HTTP_CODES =
82+
ImmutableMap.<Integer, Integer>builder()
83+
.put(0, 200)
84+
.put(1, 499)
85+
.put(2, 500)
86+
.put(3, 400)
87+
.put(4, 504)
88+
.put(5, 404)
89+
.put(6, 409)
90+
.put(7, 403)
91+
.put(16, 401)
92+
.put(8, 429)
93+
.put(9, 400)
94+
.put(10, 409)
95+
.put(11, 400)
96+
.put(12, 501)
97+
.put(13, 500)
98+
.put(14, 503)
99+
.put(15, 500)
100+
.build();
101+
102+
private static final ResultRetryAlgorithm<Operation> OPERATION_HANDLER =
103+
new ResultRetryAlgorithm<Operation>() {
104+
@Override
105+
public TimedAttemptSettings createNextAttempt(
106+
Throwable prevThrowable, Operation prevResponse, TimedAttemptSettings prevSettings) {
107+
return null;
108+
}
109+
110+
@Override
111+
public boolean shouldRetry(Throwable prevThrowable, Operation prevOp) {
112+
if (prevThrowable == null) {
113+
return prevOp.getDone() == null || !prevOp.getDone();
114+
}
115+
return prevThrowable instanceof ResourceManagerException
116+
&& ((ResourceManagerException) prevThrowable).isRetryable();
117+
}
118+
};
119+
120+
private final CloudResourceManager resourceManager;
121+
private final ApiClock clock;
48122

49123
public HttpResourceManagerRpc(ResourceManagerOptions options) {
50124
HttpTransportOptions transportOptions = (HttpTransportOptions) options.getTransportOptions();
51125
HttpTransport transport = transportOptions.getHttpTransportFactory().create();
52126
HttpRequestInitializer initializer = transportOptions.getHttpRequestInitializer(options);
53127
resourceManager =
54-
new Cloudresourcemanager.Builder(transport, new JacksonFactory(), initializer)
128+
new CloudResourceManager.Builder(transport, new JacksonFactory(), initializer)
55129
.setRootUrl(options.getHost())
56130
.setApplicationName(options.getApplicationName())
57131
.build();
132+
clock = options.getClock();
58133
}
59134

60135
private static ResourceManagerException translate(IOException exception) {
61136
return new ResourceManagerException(exception);
62137
}
63138

139+
private static ResourceManagerException translate(Status status) {
140+
Integer code = RPC_TO_HTTP_CODES.get(status.getCode());
141+
if (code == null) {
142+
code = BaseHttpServiceException.UNKNOWN_CODE;
143+
}
144+
return new ResourceManagerException(code, status.getMessage());
145+
}
146+
64147
@Override
65148
public Project create(Project project) {
149+
final Operation operation;
150+
try {
151+
operation = resourceManager.projects().create(project).execute();
152+
} catch (IOException ex) {
153+
throw translate(ex);
154+
}
155+
156+
Operation finishedOp =
157+
runWithRetries(
158+
new Callable<Operation>() {
159+
@Override
160+
public Operation call() {
161+
try {
162+
return resourceManager.operations().get(operation.getName()).execute();
163+
} catch (IOException ex) {
164+
throw translate(ex);
165+
}
166+
}
167+
},
168+
CREATE_RETRY_SETTINGS,
169+
OPERATION_HANDLER,
170+
clock);
171+
if (finishedOp.getError() != null) {
172+
throw translate(finishedOp.getError());
173+
}
174+
175+
// NOTE(pongad): Operation.getResponse() returns a Map<String, Object>.
176+
// 1. `(Project) finishedOp.getResponse()` doesn't work,
177+
// because JSON deserializer in execute() didn't know to create a Project object.
178+
// 2. `new Project().putAll(finishedOp.getResponse())` doesn't work either.
179+
// 64-bit integers are sent as strings in JSON,
180+
// so execute(), not knowing the type, parses it as String, not Long.
66181
try {
67-
return resourceManager.projects().create(project).execute();
182+
String responseTxt = JSON_FACTORY.toString(finishedOp.getResponse());
183+
return JSON_FACTORY.fromString(responseTxt, Project.class);
68184
} catch (IOException ex) {
69185
throw translate(ex);
70186
}
@@ -117,7 +233,7 @@ public Tuple<String, Iterable<Project>> list(Map<Option, ?> options) {
117233
@Override
118234
public void undelete(String projectId) {
119235
try {
120-
resourceManager.projects().undelete(projectId).execute();
236+
resourceManager.projects().undelete(projectId, new UndeleteProjectRequest()).execute();
121237
} catch (IOException ex) {
122238
throw translate(ex);
123239
}

google-cloud-resourcemanager/src/main/java/com/google/cloud/resourcemanager/testing/LocalResourceManagerHelper.java

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import com.google.api.client.json.JsonFactory;
2424
import com.google.api.services.cloudresourcemanager.model.Binding;
25+
import com.google.api.services.cloudresourcemanager.model.Operation;
2526
import com.google.api.services.cloudresourcemanager.model.Policy;
2627
import com.google.api.services.cloudresourcemanager.model.Project;
2728
import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
@@ -35,14 +36,10 @@
3536
import com.google.common.collect.ImmutableMap;
3637
import com.google.common.collect.ImmutableSet;
3738
import com.google.common.io.ByteStreams;
38-
3939
import com.sun.net.httpserver.Headers;
4040
import com.sun.net.httpserver.HttpExchange;
4141
import com.sun.net.httpserver.HttpHandler;
4242
import com.sun.net.httpserver.HttpServer;
43-
44-
import org.joda.time.format.ISODateTimeFormat;
45-
4643
import java.io.IOException;
4744
import java.io.InputStream;
4845
import java.io.OutputStream;
@@ -64,6 +61,7 @@
6461
import java.util.regex.Matcher;
6562
import java.util.regex.Pattern;
6663
import java.util.zip.GZIPInputStream;
64+
import org.joda.time.format.ISODateTimeFormat;
6765

6866
/**
6967
* Utility to create a local Resource Manager mock for testing.
@@ -96,8 +94,9 @@ public class LocalResourceManagerHelper {
9694
private static final JsonFactory jsonFactory =
9795
new com.google.api.client.json.jackson.JacksonFactory();
9896
private static final Random PROJECT_NUMBER_GENERATOR = new Random();
99-
private static final String VERSION = "v1beta1";
97+
private static final String VERSION = "v1";
10098
private static final String CONTEXT = "/" + VERSION + "/projects";
99+
private static final String OPERATION_CONTEXT = "/" + VERSION + "/operations";
101100
private static final URI BASE_CONTEXT;
102101
private static final Set<String> SUPPORTED_COMPRESSION_ENCODINGS =
103102
ImmutableSet.of("gzip", "x-gzip");
@@ -249,6 +248,48 @@ private Response handlePost(HttpExchange exchange, String path) throws IOExcepti
249248
}
250249
}
251250

251+
private class OperationRequestHandler implements HttpHandler {
252+
@Override
253+
public void handle(HttpExchange exchange) {
254+
// see https://cloud.google.com/resource-manager/reference/rest/
255+
String projectId;
256+
try {
257+
projectId = new URI(OPERATION_CONTEXT).relativize(exchange.getRequestURI()).getPath();
258+
} catch (URISyntaxException e) {
259+
throw new IllegalStateException(e);
260+
}
261+
Response response;
262+
String requestMethod = exchange.getRequestMethod();
263+
switch (requestMethod) {
264+
case "GET":
265+
Project project = projects.get(projectId);
266+
if (project == null) {
267+
response = Error.PERMISSION_DENIED.response("Project " + projectId + " not found.");
268+
break;
269+
}
270+
try {
271+
response =
272+
new Response(
273+
HTTP_OK,
274+
jsonFactory.toString(new Operation().setDone(true).setResponse(project)));
275+
} catch (IOException e) {
276+
response =
277+
Error.INTERNAL_ERROR.response(
278+
"Error when serializing project " + project.getProjectId());
279+
}
280+
break;
281+
default:
282+
response =
283+
Error.BAD_REQUEST.response(
284+
"The server could not understand the following request URI: "
285+
+ requestMethod
286+
+ " "
287+
+ projectId);
288+
}
289+
writeResponse(exchange, response);
290+
}
291+
}
292+
252293
private static void writeResponse(HttpExchange exchange, Response response) {
253294
exchange.getResponseHeaders().set("Content-type", "application/json; charset=UTF-8");
254295
OutputStream outputStream = exchange.getResponseBody();
@@ -397,7 +438,10 @@ synchronized Response create(Project project) {
397438
.setVersion(0);
398439
policies.put(project.getProjectId(), emptyPolicy);
399440
try {
400-
String createdProjectStr = jsonFactory.toString(project);
441+
// Pretend it's not done yet.
442+
String createdProjectStr =
443+
jsonFactory.toString(
444+
new Operation().setDone(false).setName("operations/" + project.getProjectId()));
401445
return new Response(HTTP_OK, createdProjectStr);
402446
} catch (IOException e) {
403447
return Error.INTERNAL_ERROR.response("Error serializing project " + project.getProjectId());
@@ -659,6 +703,7 @@ private LocalResourceManagerHelper() {
659703
server = HttpServer.create(new InetSocketAddress(0), 0);
660704
port = server.getAddress().getPort();
661705
server.createContext(CONTEXT, new RequestHandler());
706+
server.createContext(OPERATION_CONTEXT, new OperationRequestHandler());
662707
} catch (IOException e) {
663708
throw new RuntimeException("Could not bind the mock Resource Manager server.", e);
664709
}

0 commit comments

Comments
 (0)