Skip to content

Commit 5dfcaaf

Browse files
author
Ajay Kannan
committed
---
yaml --- r: 2587 b: refs/heads/update-datastore c: 978fe27 h: refs/heads/master i: 2585: a0082b5 2583: db1bdfe
1 parent 94af2a1 commit 5dfcaaf

4 files changed

Lines changed: 346 additions & 112 deletions

File tree

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ refs/heads/gh-pages: 4e0561bb4504bf647db669a14417b2b2c87ba45d
55
refs/heads/bigquery: 762fa5830e6c398c0396177e3e7fd243bd62cfc3
66
refs/heads/pubsub-alpha: 1a0e970f265af871e02274085b9662b3fe29058b
77
refs/heads/resource-manager: ebf4adc5ee835cd2086c4ac5b4e78d01a5a005a7
8-
refs/heads/update-datastore: 3de4f8f3372ba1f70ce0ef96f55ceba8dc081858
8+
refs/heads/update-datastore: 978fe27693fdf0caa6627b150ac185d4b9a10366
99
refs/tags/0.0.9: 22f1839238f66c39e67ed4dfdcd273b1ae2e8444
1010
refs/tags/v0.0.10: 207ebd2a3472fddee69fe1298eb90429e3306efd
1111
refs/tags/v0.0.11: ffbfba48a6426ff63c08ff2117e58681f251fbf2

branches/update-datastore/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java

Lines changed: 152 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55

66
import com.google.api.client.json.JsonFactory;
77
import com.google.api.services.cloudresourcemanager.model.Project;
8+
import com.google.common.base.Function;
89
import com.google.common.base.Joiner;
910
import com.google.common.base.Objects;
11+
import com.google.common.base.Predicates;
1012
import com.google.common.collect.ImmutableList;
13+
import com.google.common.collect.ImmutableMap;
14+
import com.google.common.collect.ImmutableSet;
15+
import com.google.common.collect.Iterables;
16+
import com.google.common.collect.Lists;
1117
import com.google.common.io.ByteStreams;
1218

1319
import com.sun.net.httpserver.Headers;
@@ -20,15 +26,17 @@
2026
import java.io.IOException;
2127
import java.io.InputStream;
2228
import java.io.OutputStream;
29+
import java.net.HttpURLConnection;
2330
import java.net.InetSocketAddress;
2431
import java.net.ServerSocket;
2532
import java.nio.charset.StandardCharsets;
26-
import java.util.ArrayList;
2733
import java.util.Date;
2834
import java.util.HashMap;
2935
import java.util.List;
3036
import java.util.Map;
3137
import java.util.Random;
38+
import java.util.Set;
39+
import java.util.concurrent.ConcurrentHashMap;
3240
import java.util.logging.Logger;
3341
import java.util.zip.GZIPInputStream;
3442

@@ -42,11 +50,14 @@ public class LocalResourceManagerHelper {
4250
private static final Logger log = Logger.getLogger(LocalResourceManagerHelper.class.getName());
4351
private static final JsonFactory jsonFactory =
4452
new com.google.api.client.json.jackson.JacksonFactory();
45-
private static final int HTTP_STATUS_OK = 200;
4653
private static final Random PROJECT_NUMBER_GENERATOR = new Random();
4754

55+
// see https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects
56+
private static final Set<Character> PERMISSIBLE_PROJECT_NAME_PUNCTUATION =
57+
ImmutableSet.of('-', '\'', '"', ' ', '!');
58+
4859
private HttpServer server;
49-
private final Map<String, Project> projects = new HashMap<>();
60+
private final ConcurrentHashMap<String, Project> projects = new ConcurrentHashMap<>();
5061

5162
static class Response {
5263
private final int code;
@@ -96,7 +107,7 @@ private static String toJson(
96107
args.put("message", message);
97108
args.put("status", status);
98109
try {
99-
return jsonFactory.toString(args);
110+
return jsonFactory.toString(ImmutableMap.of("error", args));
100111
} catch (IOException e) {
101112
throw new RuntimeException("Error when generating JSON error response.");
102113
}
@@ -131,6 +142,7 @@ public void handle(HttpExchange exchange) throws IOException {
131142
if (response == null) {
132143
throw new UnsupportedOperationException("Request not recognized.");
133144
}
145+
exchange.getResponseHeaders().set("Content-type", "application/json; charset=UTF-8");
134146
exchange.sendResponseHeaders(response.code(), response.body().length());
135147
OutputStream outputStream = exchange.getResponseBody();
136148
outputStream.write(response.body().getBytes());
@@ -173,6 +185,9 @@ private static Map<String, Object> parseListOptions(String query) {
173185
case "fields":
174186
options.put("fields", argEntry[1].split(","));
175187
break;
188+
case "filter":
189+
options.put("filter", argEntry[1].split(" "));
190+
break;
176191
case "pageToken":
177192
// support pageToken when Cloud Resource Manager supports this (#421)
178193
break;
@@ -185,20 +200,74 @@ private static Map<String, Object> parseListOptions(String query) {
185200
return options;
186201
}
187202

203+
private static final boolean isValidProject(Project project) {
204+
if (project.getProjectId() == null) {
205+
log.info("Project ID cannot be empty.");
206+
return false;
207+
}
208+
if (!isValidIdOrLabel(project.getProjectId(), 6, 30)) {
209+
log.info("Project " + project.getProjectId() + " has an invalid ID."
210+
+ " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects"
211+
+ " for more information.");
212+
return false;
213+
}
214+
if (project.getName() != null) {
215+
for (char c : project.getName().toCharArray()) {
216+
if (!PERMISSIBLE_PROJECT_NAME_PUNCTUATION.contains(c) && !Character.isLetterOrDigit(c)) {
217+
log.info("Project " + project.getProjectId() + " has an invalid name."
218+
+ " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects"
219+
+ " for more information.");
220+
return false;
221+
}
222+
}
223+
}
224+
if (project.getLabels() != null) {
225+
if (project.getLabels().size() > 256) {
226+
log.info("Project " + project.getProjectId() + " exceeds the limit of 256 labels.");
227+
return false;
228+
}
229+
for (Map.Entry<String, String> entry : project.getLabels().entrySet()) {
230+
if (!isValidIdOrLabel(entry.getKey(), 1, 63)
231+
|| !isValidIdOrLabel(entry.getValue(), 0, 63)) {
232+
log.info("Project " + project.getProjectId() + " has an invalid label entry."
233+
+ " See https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects"
234+
+ " for more information.");
235+
return false;
236+
}
237+
}
238+
}
239+
return true;
240+
}
241+
242+
private static final boolean isValidIdOrLabel(String value, int minLength, int maxLength) {
243+
for (char c : value.toCharArray()) {
244+
if (c != '-' && !Character.isDigit(c)
245+
&& (!Character.isLetter(c) || !Character.isLowerCase(c))) {
246+
return false;
247+
}
248+
}
249+
if (value.length() > 0 && (!Character.isLetter(value.charAt(0)) || value.endsWith("-"))) {
250+
return false;
251+
}
252+
return value.length() >= minLength && value.length() <= maxLength;
253+
}
254+
188255
Response create(Project project) throws IOException {
189256
project.setLifecycleState("ACTIVE");
190257
project.setProjectNumber(Math.abs(PROJECT_NUMBER_GENERATOR.nextLong()));
191258
project.setCreateTime(ISODateTimeFormat.dateTime().print(new Date().getTime()));
192259
Response response;
193-
if (projects.containsKey(checkNotNull(project.getProjectId()))) {
260+
if (!isValidProject(project)) {
261+
response = Error.INVALID_ARGUMENT.response;
262+
} else if (projects.containsKey(project.getProjectId())) {
194263
response = Error.ALREADY_EXISTS.response;
195264
log.info(
196265
"A project with the same project ID (" + project.getProjectId() + ") already exists.");
197266
} else {
198267
projects.put(project.getProjectId(), project);
199268
String createdProjectStr = jsonFactory.toString(project);
200269
log.info("Created the following project: " + createdProjectStr);
201-
response = new Response(HTTP_STATUS_OK, createdProjectStr);
270+
response = new Response(HttpURLConnection.HTTP_OK, createdProjectStr);
202271
}
203272
return response;
204273
}
@@ -214,7 +283,7 @@ Response delete(String projectId) {
214283
log.info("Error when deleting " + projectId + " because the lifecycle state was not ACTIVE.");
215284
} else {
216285
project.setLifecycleState("DELETE_REQUESTED");
217-
response = new Response(HTTP_STATUS_OK, "{}");
286+
response = new Response(HttpURLConnection.HTTP_OK, "{}");
218287
log.info("Successfully requested delete for the following project: " + projectId);
219288
}
220289
return response;
@@ -227,23 +296,69 @@ Response get(String projectId, String[] fields) throws IOException {
227296
log.info("Project not found.");
228297
} else {
229298
response = new Response(
230-
HTTP_STATUS_OK, jsonFactory.toString(extractFields(projects.get(projectId), fields)));
299+
HttpURLConnection.HTTP_OK,
300+
jsonFactory.toString(extractFields(projects.get(projectId), fields)));
231301
}
232302
return response;
233303
}
234304

235-
Response list(Map<String, Object> options) throws IOException {
305+
Response list(final Map<String, Object> options) {
236306
// Use pageSize and pageToken options when Cloud Resource Manager does so (#421)
237-
List<String> projectsSerialized = new ArrayList<>();
238-
for (Project p : projects.values()) {
239-
projectsSerialized.add(
240-
jsonFactory.toString(extractFields(p, (String[]) options.get("fields"))));
241-
}
307+
List<String> projectsSerialized = Lists.newArrayList(Iterables.filter(
308+
Iterables.transform(projects.values(), new Function<Project, String>() {
309+
@Override
310+
public String apply(Project p) {
311+
try {
312+
return includeProject(p, (String[]) options.get("filter"))
313+
? jsonFactory.toString(extractFields(p, (String[]) options.get("fields"))) : null;
314+
} catch (IOException e) {
315+
log.info("Error when serializing project " + p.getProjectId());
316+
return null;
317+
}
318+
}
319+
}),
320+
Predicates.notNull()));
242321
StringBuilder responseBody = new StringBuilder();
243322
responseBody.append("{\"projects\": [");
244323
responseBody.append(Joiner.on(",").join(projectsSerialized));
245324
responseBody.append("]}");
246-
return new Response(HTTP_STATUS_OK, responseBody.toString());
325+
return new Response(HttpURLConnection.HTTP_OK, responseBody.toString());
326+
}
327+
328+
private static boolean includeProject(Project project, String[] filters) {
329+
if (filters == null) {
330+
return true;
331+
}
332+
for (String filter : filters) {
333+
String[] filterEntry = filter.toLowerCase().split(":");
334+
if ("id".equals(filterEntry[0])) {
335+
if (!satisfiesFilter(project.getProjectId(), filterEntry[1])) {
336+
return false;
337+
}
338+
} else if ("name".equals(filterEntry[0])) {
339+
if (!satisfiesFilter(project.getName(), filterEntry[1])) {
340+
return false;
341+
}
342+
} else if (filterEntry[0].startsWith("labels")) {
343+
String labelKey = filterEntry[0].split("\\.")[1];
344+
if (project.getLabels() != null) {
345+
String labelValue = project.getLabels().get(labelKey);
346+
if (!satisfiesFilter(labelValue, filterEntry[1])) {
347+
return false;
348+
}
349+
}
350+
} else {
351+
log.info("Could not parse the following filter: " + filter);
352+
}
353+
}
354+
return true;
355+
}
356+
357+
private static boolean satisfiesFilter(String projectValue, String filterValue) {
358+
if (projectValue == null) {
359+
return false;
360+
}
361+
return "*".equals(filterValue) ? true : filterValue.equals(projectValue.toLowerCase());
247362
}
248363

249364
private static Project extractFields(Project fullProject, String[] fields) {
@@ -281,7 +396,7 @@ private static Project extractFields(Project fullProject, String[] fields) {
281396

282397
Response replace(Project project) throws IOException {
283398
Response response;
284-
Project oldProject = projects.get(checkNotNull(project.getProjectId()));
399+
Project oldProject = projects.get(project.getProjectId());
285400
if (oldProject == null) {
286401
response = Error.PERMISSION_DENIED.response; // when possible, change this to 404 (#440)
287402
log.info(
@@ -302,7 +417,7 @@ Response replace(Project project) throws IOException {
302417
projects.put(project.getProjectId(), project);
303418
String updatedProjectStr = jsonFactory.toString(project);
304419
log.info("Successfully updated the project to be: " + updatedProjectStr);
305-
response = new Response(HTTP_STATUS_OK, updatedProjectStr);
420+
response = new Response(HttpURLConnection.HTTP_OK, updatedProjectStr);
306421
}
307422
return response;
308423
}
@@ -319,7 +434,7 @@ Response undelete(String projectId) {
319434
+ " because the lifecycle state was not DELETE_REQUESTED.");
320435
} else {
321436
project.setLifecycleState("ACTIVE");
322-
response = new Response(HTTP_STATUS_OK, "{}");
437+
response = new Response(HttpURLConnection.HTTP_OK, "{}");
323438
log.info("Successfully undeleted " + projectId + ".");
324439
}
325440
return response;
@@ -378,11 +493,10 @@ public void stop() {
378493
* @return true if the project was successfully added, false otherwise
379494
*/
380495
public boolean addProject(Project project) {
381-
if (projects.containsKey(checkNotNull(project.getProjectId()))) {
382-
return false;
496+
if (isValidProject(project)) {
497+
return projects.putIfAbsent(project.getProjectId(), clone(project)) == null ? true : false;
383498
}
384-
projects.put(project.getProjectId(), clone(project));
385-
return true;
499+
return false;
386500
}
387501

388502
/**
@@ -391,22 +505,20 @@ public boolean addProject(Project project) {
391505
* @return Project (if it exists) or null (if it doesn't exist)
392506
*/
393507
public Project getProject(String projectId) {
394-
com.google.api.services.cloudresourcemanager.model.Project original = projects.get(projectId);
508+
Project original = projects.get(projectId);
395509
return original != null ? clone(projects.get(projectId)) : null;
396510
}
397511

398-
private static com.google.api.services.cloudresourcemanager.model.Project clone(
399-
com.google.api.services.cloudresourcemanager.model.Project original) {
400-
com.google.api.services.cloudresourcemanager.model.Project clone =
401-
new com.google.api.services.cloudresourcemanager.model.Project();
402-
clone.setProjectId(original.getProjectId());
403-
clone.setName(original.getName());
404-
clone.setLabels(original.getLabels());
405-
clone.setProjectNumber(original.getProjectNumber());
406-
clone.setCreateTime(original.getCreateTime());
407-
clone.setLifecycleState(original.getLifecycleState());
408-
clone.setParent(original.getParent());
409-
return clone;
512+
private static Project clone(Project original) {
513+
return new Project()
514+
.setProjectId(original.getProjectId())
515+
.setName(original.getName())
516+
.setLabels(original.getLabels() != null ? ImmutableMap.copyOf(original.getLabels()) : null)
517+
.setProjectNumber(
518+
original.getProjectNumber() != null ? original.getProjectNumber().longValue() : null)
519+
.setCreateTime(original.getCreateTime())
520+
.setLifecycleState(original.getLifecycleState())
521+
.setParent(original.getParent() != null ? original.getParent().clone() : null);
410522
}
411523

412524
/**
@@ -418,11 +530,7 @@ private static com.google.api.services.cloudresourcemanager.model.Project clone(
418530
* @return true if the project was successfully deleted, false otherwise.
419531
*/
420532
public boolean removeProject(String projectId) {
421-
if (projects.containsKey(projectId)) {
422-
projects.remove(checkNotNull(projectId));
423-
return true;
424-
}
425-
return false;
533+
return projects.remove(checkNotNull(projectId)) != null ? true : false;
426534
}
427535

428536
/**
@@ -431,8 +539,7 @@ public boolean removeProject(String projectId) {
431539
* @return true if the project number was successfully changed, false otherwise.
432540
*/
433541
public boolean changeProjectNumber(String projectId, long projectNumber) {
434-
com.google.api.services.cloudresourcemanager.model.Project project =
435-
projects.get(checkNotNull(projectId));
542+
Project project = projects.get(checkNotNull(projectId));
436543
if (project != null) {
437544
project.setProjectNumber(projectNumber);
438545
return true;
@@ -450,8 +557,7 @@ public boolean changeLifecycleState(String projectId, String lifecycleState) {
450557
"ACTIVE".equals(lifecycleState) || "DELETE_REQUESTED".equals(lifecycleState)
451558
|| "DELETE_IN_PROGRESS".equals(lifecycleState),
452559
"Lifecycle state must be ACTIVE, DELETE_REQUESTED, or DELETE_IN_PROGRESS");
453-
com.google.api.services.cloudresourcemanager.model.Project project =
454-
projects.get(checkNotNull(projectId));
560+
Project project = projects.get(checkNotNull(projectId));
455561
if (project != null) {
456562
project.setLifecycleState(lifecycleState);
457563
return true;
@@ -465,10 +571,9 @@ public boolean changeLifecycleState(String projectId, String lifecycleState) {
465571
* @return true if the project create time was successfully changed, false otherwise.
466572
*/
467573
public boolean changeCreateTime(String projectId, String createTime) {
468-
com.google.api.services.cloudresourcemanager.model.Project project =
469-
projects.get(checkNotNull(projectId));
574+
Project project = projects.get(checkNotNull(projectId));
470575
if (project != null) {
471-
project.setCreateTime(createTime);
576+
project.setCreateTime(checkNotNull(createTime));
472577
return true;
473578
}
474579
return false;

0 commit comments

Comments
 (0)