diff --git a/README.md b/README.md index 0fb8d82623b5..7a2e3afc8a47 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ This client supports the following Google Cloud Platform services: - [Google Cloud Datastore] (#google-cloud-datastore) - [Google Cloud Storage] (#google-cloud-storage) +- [Google Cloud Resource Manager] (#google-cloud-resource-manager) +- [Google Cloud BigQuery] (#google-cloud-bigquery) > Note: This client is a work-in-progress, and may occasionally > make backwards-incompatible changes. @@ -182,6 +184,82 @@ if (blob == null) { } ``` +Google Cloud Resource Manager +---------------------- + +- [API Documentation][resourcemanager-api] +- [Official Documentation][cloud-resourcemanager-docs] + +#### Preview + +Here is a code snippet showing a simple usage example. Note that you must supply Google SDK credentials for this service, not other forms of authentication listed in the [Authentication section](#authentication). + +```java +import com.google.gcloud.resourcemanager.ProjectInfo; +import com.google.gcloud.resourcemanager.ResourceManager; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +import java.util.Iterator; + +ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); +ProjectInfo myProject = resourceManager.get("some-project-id"); // Use an existing project's ID +ProjectInfo newProjectInfo = resourceManager.replace(myProject.toBuilder() + .addLabel("launch-status", "in-development").build()); +System.out.println("Updated the labels of project " + newProjectInfo.projectId() + + " to be " + newProjectInfo.labels()); +// List all the projects you have permission to view. +Iterator projectIterator = resourceManager.list().iterateAll(); +System.out.println("Projects I can view:"); +while (projectIterator.hasNext()) { + System.out.println(projectIterator.next().projectId()); +} +``` + +Google Cloud BigQuery +---------------------- + +- [API Documentation][bigquery-api] +- [Official Documentation][cloud-bigquery-docs] + +#### Preview + +Here is a code snippet showing a simple usage example from within Compute/App Engine. Note that you +must [supply credentials](#authentication) and a project ID if running this snippet elsewhere. + +```java +import com.google.gcloud.bigquery.BaseTableInfo; +import com.google.gcloud.bigquery.BigQuery; +import com.google.gcloud.bigquery.BigQueryOptions; +import com.google.gcloud.bigquery.Field; +import com.google.gcloud.bigquery.JobStatus; +import com.google.gcloud.bigquery.LoadJobInfo; +import com.google.gcloud.bigquery.Schema; +import com.google.gcloud.bigquery.TableId; +import com.google.gcloud.bigquery.TableInfo; + +BigQuery bigquery = BigQueryOptions.defaultInstance().service(); +TableId tableId = TableId.of("dataset", "table"); +BaseTableInfo info = bigquery.getTable(tableId); +if (info == null) { + System.out.println("Creating table " + tableId); + Field integerField = Field.of("fieldName", Field.Type.integer()); + bigquery.create(TableInfo.of(tableId, Schema.of(integerField))); +} else { + System.out.println("Loading data into table " + tableId); + LoadJobInfo loadJob = LoadJobInfo.of(tableId, "gs://bucket/path"); + loadJob = bigquery.create(loadJob); + while (loadJob.status().state() != JobStatus.State.DONE) { + Thread.sleep(1000L); + loadJob = bigquery.getJob(loadJob.jobId()); + } + if (loadJob.status().error() != null) { + System.out.println("Job completed with errors"); + } else { + System.out.println("Job succeeded"); + } +} +``` + Troubleshooting --------------- @@ -241,3 +319,10 @@ Apache 2.0 - See [LICENSE] for more information. [cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets [cloud-storage-activation]: https://cloud.google.com/storage/docs/signup [storage-api]: http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/storage/package-summary.html + +[resourcemanager-api]:http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/resourcemanager/package-summary.html +[cloud-resourcemanager-docs]:https://cloud.google.com/resource-manager/ + +[cloud-bigquery]: https://cloud.google.com/bigquery/ +[cloud-bigquery-docs]: https://cloud.google.com/bigquery/docs/overview +[bigquery-api]: http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/bigquery/package-summary.html \ No newline at end of file diff --git a/TESTING.md b/TESTING.md index 02a3d14ab0bf..3ad181310b17 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,6 +1,11 @@ ## gcloud-java tools for testing -This library provides tools to help write tests for code that uses gcloud-java services. +This library provides tools to help write tests for code that uses the following gcloud-java services: + +- [Datastore] (#testing-code-that-uses-datastore) +- [Storage] (#testing-code-that-uses-storage) +- [Resource Manager] (#testing-code-that-uses-resource-manager) +- [BigQuery] (#testing-code-that-uses-bigquery) ### Testing code that uses Datastore @@ -51,7 +56,8 @@ Currently, there isn't an emulator for Google Cloud Storage, so an alternative i 3. Create a `RemoteGcsHelper` object using your project ID and JSON key. Here is an example that uses the `RemoteGcsHelper` to create a bucket. ```java - RemoteGcsHelper gcsHelper = RemoteGcsHelper.create(PROJECT_ID, "/path/to/my/JSON/key.json"); + RemoteGcsHelper gcsHelper = + RemoteGcsHelper.create(PROJECT_ID, new FileInputStream("/path/to/my/JSON/key.json")); Storage storage = gcsHelper.options().service(); String bucket = RemoteGcsHelper.generateBucketName(); storage.create(BucketInfo.of(bucket)); @@ -65,5 +71,67 @@ Here is an example that clears the bucket created in Step 3 with a timeout of 5 RemoteGcsHelper.forceDelete(storage, bucket, 5, TimeUnit.SECONDS); ``` +### Testing code that uses Resource Manager + +#### On your machine + +You can test against a temporary local Resource Manager by following these steps: + +1. Before running your testing code, start the Resource Manager emulator `LocalResourceManagerHelper`. This can be done as follows: + + ```java + import com.google.gcloud.resourcemanager.testing.LocalResourceManagerHelper; + + LocalResourceManagerHelper helper = LocalResourceManagerHelper.create(); + helper.start(); + ``` + + This will spawn a server thread that listens to `localhost` at an ephemeral port for Resource Manager requests. + +2. In your program, create and use a Resource Manager service object whose host is set to `localhost` at the appropriate port. For example: + + ```java + ResourceManager resourceManager = LocalResourceManagerHelper.options().service(); + ``` + +3. Run your tests. + +4. Stop the Resource Manager emulator. + + ```java + helper.stop(); + ``` + + This method will block until the server thread has been terminated. + +### Testing code that uses BigQuery + +Currently, there isn't an emulator for Google BigQuery, so an alternative is to create a test +project. `RemoteBigQueryHelper` contains convenience methods to make setting up and cleaning up the +test project easier. To use this class, follow the steps below: + +1. Create a test Google Cloud project. + +2. Download a [JSON service account credentials file][create-service-account] from the Google +Developer's Console. + +3. Create a `RemoteBigQueryHelper` object using your project ID and JSON key. +Here is an example that uses the `RemoteBigQueryHelper` to create a dataset. + ```java + RemoteBigQueryHelper bigqueryHelper = + RemoteBigQueryHelper.create(PROJECT_ID, new FileInputStream("/path/to/my/JSON/key.json")); + BigQuery bigquery = bigqueryHelper.options().service(); + String dataset = RemoteBigQueryHelper.generateDatasetName(); + bigquery.create(DatasetInfo.builder(dataset).build()); + ``` + +4. Run your tests. + +5. Clean up the test project by using `forceDelete` to clear any datasets used. +Here is an example that clears the dataset created in Step 3. + ```java + RemoteBigQueryHelper.forceDelete(bigquery, dataset); + ``` [cloud-platform-storage-authentication]:https://cloud.google.com/storage/docs/authentication?hl=en#service_accounts +[create-service-account]:https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount \ No newline at end of file diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseDatastoreBatchWriter.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseDatastoreBatchWriter.java index 7eaf5c535f26..b42c07d62320 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseDatastoreBatchWriter.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseDatastoreBatchWriter.java @@ -199,7 +199,7 @@ protected DatastoreException newInvalidRequest(String msg, Object... params) { return DatastoreException.throwInvalidRequest(String.format(msg, params)); } - protected DatastoreV1.Mutation.Builder toMutationPb() { + DatastoreV1.Mutation.Builder toMutationPb() { DatastoreV1.Mutation.Builder mutationPb = DatastoreV1.Mutation.newBuilder(); for (FullEntity entity : toAddAutoId()) { mutationPb.addInsertAutoId(entity.toPb()); diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java index 3a79f3053a1e..ea8c6f5af6ff 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java @@ -90,7 +90,7 @@ private B self() { } @SuppressWarnings("unchecked") - protected B fill(DatastoreV1.Entity entityPb) { + B fill(DatastoreV1.Entity entityPb) { Map> copiedProperties = Maps.newHashMap(); for (DatastoreV1.Property property : entityPb.getPropertyList()) { copiedProperties.put(property.getName(), Value.fromPb(property.getValue())); @@ -375,7 +375,7 @@ ImmutableSortedMap> properties() { } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { Builder builder = emptyBuilder(); builder.fill(DatastoreV1.Entity.parseFrom(bytesPb)); return builder.build(); @@ -384,7 +384,7 @@ protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { protected abstract Builder emptyBuilder(); @Override - protected final DatastoreV1.Entity toPb() { + final DatastoreV1.Entity toPb() { DatastoreV1.Entity.Builder entityPb = DatastoreV1.Entity.newBuilder(); for (Map.Entry> entry : properties.entrySet()) { DatastoreV1.Property.Builder propertyPb = DatastoreV1.Property.newBuilder(); diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java index 865b95ed8518..3add6bae67c4 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java @@ -172,7 +172,7 @@ public boolean equals(Object obj) { } @Override - protected DatastoreV1.Key toPb() { + DatastoreV1.Key toPb() { DatastoreV1.Key.Builder keyPb = DatastoreV1.Key.newBuilder(); DatastoreV1.PartitionId.Builder partitionIdPb = DatastoreV1.PartitionId.newBuilder(); if (projectId != null) { diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Blob.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Blob.java index 0c4d6c26d9fa..299deab3e964 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Blob.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Blob.java @@ -147,12 +147,12 @@ public static Blob copyFrom(InputStream input) throws IOException { } @Override - protected Value toPb() { + Value toPb() { return DatastoreV1.Value.newBuilder().setBlobValue(byteString).build(); } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return new Blob(DatastoreV1.Value.parseFrom(bytesPb).getBlobValue()); } } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Cursor.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Cursor.java index 42a8cee8e5a2..667f3cc5e427 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Cursor.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Cursor.java @@ -102,12 +102,12 @@ public static Cursor copyFrom(byte[] bytes) { } @Override - protected Value toPb() { + Value toPb() { return DatastoreV1.Value.newBuilder().setBlobValue(byteString).build(); } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(DatastoreV1.Value.parseFrom(bytesPb)); } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DateTime.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DateTime.java index af5a17ef7ef3..d22edd9697e4 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DateTime.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DateTime.java @@ -98,12 +98,12 @@ public static DateTime copyFrom(Calendar calendar) { } @Override - protected Value toPb() { + Value toPb() { return DatastoreV1.Value.newBuilder().setIntegerValue(timestampMicroseconds).build(); } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return new DateTime(DatastoreV1.Value.parseFrom(bytesPb).getIntegerValue()); } } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/GqlQuery.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/GqlQuery.java index e9bd8e12cfd8..e6ae166dbf07 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/GqlQuery.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/GqlQuery.java @@ -126,7 +126,7 @@ public boolean equals(Object obj) { } @Override - protected DatastoreV1.GqlQueryArg toPb() { + DatastoreV1.GqlQueryArg toPb() { DatastoreV1.GqlQueryArg.Builder argPb = DatastoreV1.GqlQueryArg.newBuilder(); if (name != null) { argPb.setName(name); @@ -141,7 +141,7 @@ protected DatastoreV1.GqlQueryArg toPb() { } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(DatastoreV1.GqlQueryArg.parseFrom(bytesPb)); } @@ -370,7 +370,7 @@ public boolean equals(Object obj) { } @Override - protected DatastoreV1.GqlQuery toPb() { + DatastoreV1.GqlQuery toPb() { DatastoreV1.GqlQuery.Builder queryPb = DatastoreV1.GqlQuery.newBuilder(); queryPb.setQueryString(queryString); queryPb.setAllowLiteral(allowLiteral); @@ -384,18 +384,18 @@ protected DatastoreV1.GqlQuery toPb() { } @Override - protected void populatePb(DatastoreV1.RunQueryRequest.Builder requestPb) { + void populatePb(DatastoreV1.RunQueryRequest.Builder requestPb) { requestPb.setGqlQuery(toPb()); } @Override - protected GqlQuery nextQuery(DatastoreV1.QueryResultBatch responsePb) { + GqlQuery nextQuery(DatastoreV1.QueryResultBatch responsePb) { // See issue #17 throw new UnsupportedOperationException("paging for this query is not implemented yet"); } @Override - protected Object fromPb(ResultType resultType, String namespace, byte[] bytesPb) + Object fromPb(ResultType resultType, String namespace, byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(resultType, namespace, DatastoreV1.GqlQuery.parseFrom(bytesPb)); } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java index 6134eed2905b..2ccd59e725a8 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java @@ -54,7 +54,7 @@ public IncompleteKey build() { } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(DatastoreV1.Key.parseFrom(bytesPb)); } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Key.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Key.java index c625c067f6c2..c6cdc0fa6142 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Key.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Key.java @@ -164,7 +164,7 @@ public static Key fromUrlSafe(String urlSafe) { } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(DatastoreV1.Key.parseFrom(bytesPb)); } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/PathElement.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/PathElement.java index 186ed97adcde..6b76eb70ea5b 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/PathElement.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/PathElement.java @@ -86,7 +86,7 @@ public boolean equals(Object obj) { } @Override - protected DatastoreV1.Key.PathElement toPb() { + DatastoreV1.Key.PathElement toPb() { DatastoreV1.Key.PathElement.Builder pathElementPb = DatastoreV1.Key.PathElement.newBuilder(); pathElementPb.setKind(kind); if (id != null) { @@ -98,7 +98,7 @@ protected DatastoreV1.Key.PathElement toPb() { } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(DatastoreV1.Key.PathElement.parseFrom(bytesPb)); } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Query.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Query.java index 5791d37e9426..0dbd1633928e 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Query.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Query.java @@ -65,7 +65,8 @@ public abstract static class ResultType implements java.io.Serializable { private static final long serialVersionUID = 1602329532153860907L; - @Override protected Object convert(DatastoreV1.Entity entityPb) { + @Override + Object convert(DatastoreV1.Entity entityPb) { if (entityPb.getPropertyCount() == 0) { if (!entityPb.hasKey()) { return null; @@ -81,7 +82,8 @@ public abstract static class ResultType implements java.io.Serializable { private static final long serialVersionUID = 7712959777507168274L; - @Override protected Entity convert(DatastoreV1.Entity entityPb) { + @Override + Entity convert(DatastoreV1.Entity entityPb) { return Entity.fromPb(entityPb); } }; @@ -91,7 +93,8 @@ public abstract static class ResultType implements java.io.Serializable { private static final long serialVersionUID = -8514289244104446252L; - @Override protected Key convert(DatastoreV1.Entity entityPb) { + @Override + Key convert(DatastoreV1.Entity entityPb) { return Key.fromPb(entityPb.getKey()); } }; @@ -102,7 +105,8 @@ public abstract static class ResultType implements java.io.Serializable { private static final long serialVersionUID = -7591409419690650246L; - @Override protected ProjectionEntity convert(DatastoreV1.Entity entityPb) { + @Override + ProjectionEntity convert(DatastoreV1.Entity entityPb) { return ProjectionEntity.fromPb(entityPb); } }; @@ -151,7 +155,7 @@ boolean isAssignableFrom(ResultType otherResultType) { return resultClass.isAssignableFrom(otherResultType.resultClass); } - protected abstract V convert(DatastoreV1.Entity entityPb); + abstract V convert(DatastoreV1.Entity entityPb); static ResultType fromPb(DatastoreV1.EntityResult.ResultType typePb) { return MoreObjects.firstNonNull(PB_TO_INSTANCE.get(typePb), UNKNOWN); @@ -181,16 +185,16 @@ public String toString() { } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(resultType, namespace, bytesPb); } - protected abstract Object fromPb(ResultType resultType, String namespace, byte[] bytesPb) + abstract Object fromPb(ResultType resultType, String namespace, byte[] bytesPb) throws InvalidProtocolBufferException; - protected abstract void populatePb(DatastoreV1.RunQueryRequest.Builder requestPb); + abstract void populatePb(DatastoreV1.RunQueryRequest.Builder requestPb); - protected abstract Query nextQuery(DatastoreV1.QueryResultBatch responsePb); + abstract Query nextQuery(DatastoreV1.QueryResultBatch responsePb); /** * Returns a new {@link GqlQuery} builder. diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Serializable.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Serializable.java index ff62fe89195f..89d19bcfd892 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Serializable.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Serializable.java @@ -46,7 +46,7 @@ private void readObject(ObjectInputStream input) throws IOException, ClassNotFou bytesPb = (byte[]) input.readObject(); } - protected Object readResolve() throws ObjectStreamException { + Object readResolve() throws ObjectStreamException { try { return fromPb(bytesPb); } catch (InvalidProtocolBufferException ex) { @@ -58,7 +58,7 @@ protected Object readResolve() throws ObjectStreamException { } } - protected abstract M toPb(); + abstract M toPb(); - protected abstract Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException; + abstract Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException; } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java index b592dc7b600f..7b2312c85fc8 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java @@ -103,7 +103,7 @@ public abstract static class Filter implements Serializable { Filter() { } - protected abstract DatastoreV1.Filter toPb(); + abstract DatastoreV1.Filter toPb(); static Filter fromPb(DatastoreV1.Filter filterPb) { if (filterPb.hasCompositeFilter()) { @@ -186,7 +186,7 @@ public static CompositeFilter and(Filter first, Filter... other) { } @Override - protected DatastoreV1.Filter toPb() { + DatastoreV1.Filter toPb() { DatastoreV1.Filter.Builder filterPb = DatastoreV1.Filter.newBuilder(); DatastoreV1.CompositeFilter.Builder compositeFilterPb = filterPb.getCompositeFilterBuilder(); compositeFilterPb.setOperator(operator.toPb()); @@ -231,7 +231,7 @@ private PropertyFilter(String property, Operator operator, Value value) { this.value = checkNotNull(value); } - public static PropertyFilter fromPb(DatastoreV1.PropertyFilter propertyFilterPb) { + static PropertyFilter fromPb(DatastoreV1.PropertyFilter propertyFilterPb) { String property = propertyFilterPb.getProperty().getName(); Operator operator = Operator.fromPb(propertyFilterPb.getOperator()); Value value = Value.fromPb(propertyFilterPb.getValue()); @@ -435,7 +435,7 @@ public static PropertyFilter isNull(String property) { } @Override - protected DatastoreV1.Filter toPb() { + DatastoreV1.Filter toPb() { DatastoreV1.Filter.Builder filterPb = DatastoreV1.Filter.newBuilder(); DatastoreV1.PropertyFilter.Builder propertyFilterPb = filterPb.getPropertyFilterBuilder(); propertyFilterPb.getPropertyBuilder().setName(property); @@ -587,7 +587,7 @@ DatastoreV1.PropertyExpression toPb() { return expressionPb.build(); } - public static Projection fromPb(DatastoreV1.PropertyExpression propertyExpressionPb) { + static Projection fromPb(DatastoreV1.PropertyExpression propertyExpressionPb) { String property = propertyExpressionPb.getProperty().getName(); Aggregate aggregate = null; if (propertyExpressionPb.hasAggregationFunction()) { @@ -795,7 +795,7 @@ public static final class KeyQueryBuilder extends BaseBuilder nextQuery(DatastoreV1.QueryResultBatch responsePb) { + StructuredQuery nextQuery(DatastoreV1.QueryResultBatch responsePb) { Builder builder = new Builder<>(type()); builder.mergeFrom(toPb()); builder.startCursor(new Cursor(responsePb.getEndCursor())); @@ -969,7 +969,7 @@ protected StructuredQuery nextQuery(DatastoreV1.QueryResultBatch responsePb) } @Override - protected DatastoreV1.Query toPb() { + DatastoreV1.Query toPb() { DatastoreV1.Query.Builder queryPb = DatastoreV1.Query.newBuilder(); if (kind != null) { queryPb.addKindBuilder().setName(kind); @@ -1002,7 +1002,7 @@ protected DatastoreV1.Query toPb() { } @Override - protected Object fromPb(ResultType resultType, String namespace, byte[] bytesPb) + Object fromPb(ResultType resultType, String namespace, byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(resultType, namespace, DatastoreV1.Query.parseFrom(bytesPb)); } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Value.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Value.java index c5fc63a960b1..4d8aaec086f1 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Value.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Value.java @@ -211,7 +211,7 @@ public boolean equals(Object obj) { @Override @SuppressWarnings("unchecked") - protected DatastoreV1.Value toPb() { + DatastoreV1.Value toPb() { return type().getMarshaller().toProto(this); } @@ -231,7 +231,7 @@ static Value fromPb(DatastoreV1.Value proto) { } @Override - protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { + Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return fromPb(DatastoreV1.Value.parseFrom(bytesPb)); } } diff --git a/gcloud-java-examples/README.md b/gcloud-java-examples/README.md index 2a451db33036..7dfcd13db755 100644 --- a/gcloud-java-examples/README.md +++ b/gcloud-java-examples/README.md @@ -33,7 +33,7 @@ To run examples from your command line: 1. Login using gcloud SDK (`gcloud auth login` in command line) -2. Set your current project using `gcloud config set project PROJECT_ID` +2. Set your current project using `gcloud config set project PROJECT_ID`. This step is not necessary for `ResourceManagerExample`. 3. Compile using Maven (`mvn compile` in command line from your base project directory) @@ -56,7 +56,16 @@ To run examples from your command line: $mvn exec:java -Dexec.mainClass="com.google.gcloud.examples.StorageExample" -Dexec.args="list " $mvn exec:java -Dexec.mainClass="com.google.gcloud.examples.StorageExample" -Dexec.args="download test.txt" $mvn exec:java -Dexec.mainClass="com.google.gcloud.examples.StorageExample" -Dexec.args="delete test.txt" -``` + ``` + + Here's an example run of `ResourceManagerExample`. + + Be sure to change the placeholder project ID "my-project-id" with your own globally unique project ID. + ``` + $mvn exec:java -Dexec.mainClass="com.google.gcloud.examples.ResourceManagerExample" -Dexec.args="create my-project-id" + $mvn exec:java -Dexec.mainClass="com.google.gcloud.examples.ResourceManagerExample" -Dexec.args="list" + $mvn exec:java -Dexec.mainClass="com.google.gcloud.examples.ResourceManagerExample" -Dexec.args="get my-project-id" + ``` Troubleshooting --------------- diff --git a/gcloud-java-examples/src/main/java/com/google/gcloud/examples/ResourceManagerExample.java b/gcloud-java-examples/src/main/java/com/google/gcloud/examples/ResourceManagerExample.java new file mode 100644 index 000000000000..049ed35368db --- /dev/null +++ b/gcloud-java-examples/src/main/java/com/google/gcloud/examples/ResourceManagerExample.java @@ -0,0 +1,223 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.examples; + +import com.google.common.base.Joiner; +import com.google.gcloud.resourcemanager.ProjectInfo; +import com.google.gcloud.resourcemanager.ResourceManager; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * An example of using the Google Cloud Resource Manager. + * + *

This example creates, deletes, gets, and lists projects. + * + *

Steps needed for running the example:

    + *
  1. login using gcloud SDK - {@code gcloud auth login}.
  2. + *
  3. compile using maven - {@code mvn compile}
  4. + *
  5. run using maven - {@code mvn exec:java + * -Dexec.mainClass="com.google.gcloud.examples.ResourceManagerExample" + * -Dexec.args="[list | [create | delete | get] projectId]"}
  6. + *
+ */ +public class ResourceManagerExample { + + private static final String DEFAULT_ACTION = "list"; + private static final Map ACTIONS = new HashMap<>(); + + private interface ResourceManagerAction { + void run(ResourceManager resourceManager, String... args); + + String[] getRequiredParams(); + + String[] getOptionalParams(); + } + + private static class CreateAction implements ResourceManagerAction { + @Override + public void run(ResourceManager resourceManager, String... args) { + String projectId = args[0]; + Map labels = new HashMap<>(); + for (int i = 1; i < args.length; i += 2) { + if (i + 1 < args.length) { + labels.put(args[i], args[i + 1]); + } else { + labels.put(args[i], ""); + } + } + ProjectInfo project = + resourceManager.create(ProjectInfo.builder(projectId).labels(labels).build()); + System.out.printf( + "Successfully created project '%s': %s.%n", projectId, projectDetails(project)); + } + + @Override + public String[] getRequiredParams() { + return new String[] {"project-id"}; + } + + @Override + public String[] getOptionalParams() { + return new String[] {"label-key-1", "label-value-1", "label-key-2", "label-value-2", "..."}; + } + } + + private static class DeleteAction implements ResourceManagerAction { + @Override + public void run(ResourceManager resourceManager, String... args) { + String projectId = args[0]; + System.out.printf("Going to delete project \"%s\". Are you sure [y/N]: ", projectId); + Scanner scanner = new Scanner(System.in); + if (scanner.nextLine().toLowerCase().equals("y")) { + resourceManager.delete(projectId); + System.out.println("Successfully deleted project " + projectId + "."); + } else { + System.out.println("Will not delete project " + projectId + "."); + } + scanner.close(); + } + + @Override + public String[] getRequiredParams() { + return new String[] {"project-id"}; + } + + @Override + public String[] getOptionalParams() { + return new String[] {}; + } + } + + private static class GetAction implements ResourceManagerAction { + @Override + public void run(ResourceManager resourceManager, String... args) { + String projectId = args[0]; + ProjectInfo project = resourceManager.get(projectId); + if (project != null) { + System.out.printf( + "Successfully got project '%s': %s.%n", projectId, projectDetails(project)); + } else { + System.out.printf("Could not find project '%s'.%n", projectId); + } + } + + @Override + public String[] getRequiredParams() { + return new String[] {"project-id"}; + } + + @Override + public String[] getOptionalParams() { + return new String[] {}; + } + } + + private static class ListAction implements ResourceManagerAction { + @Override + public void run(ResourceManager resourceManager, String... args) { + System.out.println("Projects you can view:"); + for (ProjectInfo project : resourceManager.list().values()) { + System.out.println(projectDetails(project)); + } + } + + @Override + public String[] getRequiredParams() { + return new String[] {}; + } + + @Override + public String[] getOptionalParams() { + return new String[] {}; + } + } + + static { + ACTIONS.put("create", new CreateAction()); + ACTIONS.put("delete", new DeleteAction()); + ACTIONS.put("get", new GetAction()); + ACTIONS.put("list", new ListAction()); + } + + private static String projectDetails(ProjectInfo project) { + return new StringBuilder() + .append("{projectId:") + .append(project.projectId()) + .append(", projectNumber:") + .append(project.projectNumber()) + .append(", createTimeMillis:") + .append(project.createTimeMillis()) + .append(", state:") + .append(project.state()) + .append(", labels:") + .append(project.labels()) + .append("}") + .toString(); + } + + private static void addUsage( + String actionName, ResourceManagerAction action, StringBuilder usage) { + usage.append(actionName); + Joiner joiner = Joiner.on(" "); + String[] requiredParams = action.getRequiredParams(); + if (requiredParams.length > 0) { + usage.append(' '); + joiner.appendTo(usage, requiredParams); + } + String[] optionalParams = action.getOptionalParams(); + if (optionalParams.length > 0) { + usage.append(" ["); + joiner.appendTo(usage, optionalParams); + usage.append(']'); + } + } + + public static void main(String... args) { + String actionName = args.length > 0 ? args[0].toLowerCase() : DEFAULT_ACTION; + ResourceManagerAction action = ACTIONS.get(actionName); + if (action == null) { + StringBuilder actionAndParams = new StringBuilder(); + for (Map.Entry entry : ACTIONS.entrySet()) { + addUsage(entry.getKey(), entry.getValue(), actionAndParams); + actionAndParams.append('|'); + } + actionAndParams.setLength(actionAndParams.length() - 1); + System.out.printf( + "Usage: %s [%s]%n", ResourceManagerExample.class.getSimpleName(), actionAndParams); + return; + } + + // If you want to access a local Resource Manager emulator (after creating and starting the + // LocalResourceManagerHelper), use the following code instead: + // ResourceManager resourceManager = LocalResourceManagerHelper.options().service(); + ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); + args = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[] {}; + if (args.length < action.getRequiredParams().length) { + StringBuilder usage = new StringBuilder(); + usage.append("Usage: "); + addUsage(actionName, action, usage); + System.out.println(usage); + } else { + action.run(resourceManager, args); + } + } +} diff --git a/gcloud-java-resourcemanager/README.md b/gcloud-java-resourcemanager/README.md new file mode 100644 index 000000000000..fc8b6e0893c1 --- /dev/null +++ b/gcloud-java-resourcemanager/README.md @@ -0,0 +1,216 @@ +Google Cloud Java Client for Resource Manager +============================================= + +Java idiomatic client for [Google Cloud Resource Manager] (https://cloud.google.com/resource-manager/). + +[![Build Status](https://travis-ci.org/GoogleCloudPlatform/gcloud-java.svg?branch=master)](https://travis-ci.org/GoogleCloudPlatform/gcloud-java) +[![Coverage Status](https://coveralls.io/repos/GoogleCloudPlatform/gcloud-java/badge.svg?branch=master)](https://coveralls.io/r/GoogleCloudPlatform/gcloud-java?branch=master) +[![Maven](https://img.shields.io/maven-central/v/com.google.gcloud/gcloud-java-resourcemanager.svg)]( https://img.shields.io/maven-central/v/com.google.gcloud/gcloud-java-resourcemanager.svg) + +- [Homepage] (https://googlecloudplatform.github.io/gcloud-java/) +- [API Documentation] (http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/resourcemanager/package-summary.html) + +> Note: This client is a work-in-progress, and may occasionally +> make backwards-incompatible changes. + +Quickstart +---------- +If you are using Maven, add this to your pom.xml file +```xml + + com.google.gcloud + gcloud-java-resourcemanager + 0.1.0 + +``` +If you are using Gradle, add this to your dependencies +```Groovy +compile 'com.google.gcloud:gcloud-java-resourcemanager:jar:0.1.0' +``` +If you are using SBT, add this to your dependencies +```Scala +libraryDependencies += "com.google.gcloud" % "gcloud-java-resourcemanager" % "0.1.0" +``` + +Example Application +-------------------- +[`ResourceManagerExample`](https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/gcloud-java-examples/src/main/java/com/google/gcloud/examples/ResourceManagerExample.java) is a simple command line interface for the Cloud Resource Manager. Read more about using the application on the [`gcloud-java-examples` docs page](http://googlecloudplatform.github.io/gcloud-java/apidocs/?com/google/gcloud/examples/ResourceManagerExample.html). + +Authentication +-------------- + +Unlike other `gcloud-java` service libraries, `gcloud-java-resourcemanager` only accepts Google Cloud SDK credentials at this time. If you are having trouble authenticating, it may be that you have other types of credentials that override your Google Cloud SDK credentials. See more about Google Cloud SDK credentials and credential precedence in the global README's [Authentication section](https://github.com/GoogleCloudPlatform/gcloud-java#authentication). + +About Google Cloud Resource Manager +----------------------------------- + +Google [Cloud Resource Manager][cloud-resourcemanager] provides a programmatic way to manage your Google Cloud Platform projects. With this API, you can do the following: + +* Get a list of all projects associated with an account. +* Create new projects. +* Update existing projects. +* Delete projects. +* Undelete projects that you don't want to delete. + +Google Cloud Resource Manager is currently in beta and may occasionally make backwards incompatible changes. + +Be sure to activate the Google Cloud Resource Manager API on the Developer's Console to use Resource Manager from your project. + +See the ``gcloud-java`` API [Resource Manager documentation][resourcemanager-api] to learn how to interact +with the Cloud Resource Manager using this client Library. + +Getting Started +--------------- +#### Prerequisites +You will need to set up the local development environment by [installing the Google Cloud SDK](https://cloud.google.com/sdk/) and running the following command in command line: `gcloud auth login`. + +> Note: You don't need a project ID to use this service. If you have a project ID set in the Google Cloud SDK, you can unset it by typing `gcloud config unset project` in command line. + +#### Installation and setup +You'll need to obtain the `gcloud-java-resourcemanager` library. See the [Quickstart](#quickstart) section to add `gcloud-java-resourcemanager` as a dependency in your code. + +#### Creating an authorized service object +To make authenticated requests to Google Cloud Resource Manager, you must create a service object with Google Cloud SDK credentials. You can then make API calls by calling methods on the Resource Manager service object. The simplest way to authenticate is to use [Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials). These credentials are automatically inferred from your environment, so you only need the following code to create your service object: + +```java +import com.google.gcloud.resourcemanager.ResourceManager; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); +``` + +#### Creating a project +All you need to create a project is a globally unique project ID. You can also optionally attach a non-unique name and labels to your project. Read more about naming guidelines for project IDs, names, and labels [here](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). To create a project, add the following import at the top of your file: + +```java +import com.google.gcloud.resourcemanager.ProjectInfo; +``` + +Then add the following code to create a project (be sure to change `myProjectId` to your own unique project ID). + +```java +String myProjectId = "my-globally-unique-project-id"; // Change to a unique project ID. +ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build()); +``` + +Note that the return value from `create` is a `ProjectInfo` that includes additional read-only information, like creation time, project number, and lifecycle state. Read more about these fields on the [Projects page](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). + +#### Getting a specific project +You can load a project if you know it's project ID and have read permissions to the project. For example, to get the project we just created we can do the following: + +```java +ProjectInfo projectFromServer = resourceManager.get(myProjectId); +``` + +#### Editing a project +To edit a project, create a new `ProjectInfo` object and pass it in to the `ResourceManager.replace` method. + +For example, to add a label for the newly created project to denote that it's launch status is "in development", add the following code: + +```java +ProjectInfo newProjectInfo = resourceManager.replace(projectFromServer.toBuilder() + .addLabel("launch-status", "in-development").build()); +``` + +Note that the values of the project you pass in to `replace` overwrite the server's values for non-read-only fields, namely `projectName` and `labels`. For example, if you create a project with `projectName` "some-project-name" and subsequently call replace using a `ProjectInfo` object that didn't set the `projectName`, then the server will unset the project's name. The server ignores any attempted changes to the read-only fields `projectNumber`, `lifecycleState`, and `createTime`. The `projectId` cannot change. + +#### Listing all projects +Suppose that we want a list of all projects for which we have read permissions. Add the following import: + +```java +import java.util.Iterator; +``` + +Then add the following code to print a list of projects you can view: + +```java +Iterator projectIterator = resourceManager.list().iterateAll(); +System.out.println("Projects I can view:"); +while (projectIterator.hasNext()) { + System.out.println(projectIterator.next().projectId()); +} +``` + +#### Complete source code + +Here we put together all the code shown above into one program. This program assumes that you are running from your own desktop and used the Google Cloud SDK to authenticate yourself. + +```java +import com.google.gcloud.resourcemanager.ProjectInfo; +import com.google.gcloud.resourcemanager.ResourceManager; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +import java.util.Iterator; + +public class GcloudJavaResourceManagerExample { + + public static void main(String[] args) { + // Create Resource Manager service object. + // By default, credentials are inferred from the runtime environment. + ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); + + // Create a project. + String myProjectId = "my-globally-unique-project-id"; // Change to a unique project ID. + ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build()); + + // Get a project from the server. + ProjectInfo projectFromServer = resourceManager.get(myProjectId); + System.out.println("Got project " + projectFromServer.projectId() + " from the server."); + + // Update a project + ProjectInfo newProjectInfo = resourceManager.replace(myProject.toBuilder() + .addLabel("launch-status", "in-development").build()); + System.out.println("Updated the labels of project " + newProjectInfo.projectId() + + " to be " + newProjectInfo.labels()); + + // List all the projects you have permission to view. + Iterator projectIterator = resourceManager.list().iterateAll(); + System.out.println("Projects I can view:"); + while (projectIterator.hasNext()) { + System.out.println(projectIterator.next().projectId()); + } + } +} +``` + +Java Versions +------------- + +Java 7 or above is required for using this client. + +Versioning +---------- + +This library follows [Semantic Versioning] (http://semver.org/). + +It is currently in major version zero (``0.y.z``), which means that anything +may change at any time and the public API should not be considered +stable. + +Testing +------- + +This library has tools to help write tests for code that uses Resource Manager. + +See [TESTING] to read more about testing. + +Contributing +------------ + +Contributions to this library are always welcome and highly encouraged. + +See [CONTRIBUTING] for more information on how to get started. + +License +------- + +Apache 2.0 - See [LICENSE] for more information. + + +[CONTRIBUTING]:https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/CONTRIBUTING.md +[LICENSE]: https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/LICENSE +[TESTING]: https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/TESTING.md#testing-code-that-uses-resource-manager +[cloud-platform]: https://cloud.google.com/ +[cloud-resourcemanager]: https://cloud.google.com/resource-manager/docs +[resourcemanager-api]: http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/resourcemanager/package-summary.html + diff --git a/gcloud-java-resourcemanager/pom.xml b/gcloud-java-resourcemanager/pom.xml new file mode 100644 index 000000000000..ced6f4edfccf --- /dev/null +++ b/gcloud-java-resourcemanager/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + com.google.gcloud + gcloud-java-resourcemanager + jar + GCloud Java resource manager + + Java idiomatic client for Google Cloud Resource Manager. + + + com.google.gcloud + gcloud-java-pom + 0.1.1-SNAPSHOT + + + gcloud-java-resourcemanager + + + + ${project.groupId} + gcloud-java-core + ${project.version} + + + com.google.apis + google-api-services-cloudresourcemanager + v1beta1-rev6-1.19.0 + compile + + + com.google.guava + guava-jdk5 + + + + + junit + junit + 4.12 + test + + + org.easymock + easymock + 3.3 + test + + + diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Option.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Option.java new file mode 100644 index 000000000000..f48c057ba049 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Option.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.gcloud.spi.ResourceManagerRpc; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Base class for Resource Manager operation options. + */ +class Option implements Serializable { + + private static final long serialVersionUID = 2655177550880762967L; + + private final ResourceManagerRpc.Option rpcOption; + private final Object value; + + Option(ResourceManagerRpc.Option rpcOption, Object value) { + this.rpcOption = checkNotNull(rpcOption); + this.value = value; + } + + ResourceManagerRpc.Option rpcOption() { + return rpcOption; + } + + Object value() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Option)) { + return false; + } + Option other = (Option) obj; + return Objects.equals(rpcOption, other.rpcOption) + && Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(rpcOption, value); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", rpcOption.value()) + .add("value", value) + .toString(); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java new file mode 100644 index 000000000000..1b79eb973da3 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A Google Cloud Resource Manager project object. + * + *

A Project is a high-level Google Cloud Platform entity. It is a container for ACLs, APIs, + * AppEngine Apps, VMs, and other Google Cloud Platform resources. This class' member variables are + * immutable. Methods that change or update the underlying Project information return a new Project + * instance. + */ +public class Project { + + private final ResourceManager resourceManager; + private final ProjectInfo info; + + /** + * Constructs a Project object that contains the ProjectInfo given. + */ + public Project(ResourceManager resourceManager, ProjectInfo projectInfo) { + this.resourceManager = checkNotNull(resourceManager); + this.info = checkNotNull(projectInfo); + } + + /** + * Constructs a Project object that contains project information loaded from the server. + * + * @return Project object containing the project's metadata + * @throws ResourceManagerException upon failure + */ + public static Project load(ResourceManager resourceManager, String projectId) { + ProjectInfo projectInfo = resourceManager.get(projectId); + return new Project(resourceManager, projectInfo); + } + + /** + * Returns the {@link ProjectInfo} object associated with this Project. + */ + public ProjectInfo info() { + return info; + } + + /** + * Returns the {@link ResourceManager} service object associated with this Project. + */ + public ResourceManager resourceManager() { + return resourceManager; + } + + /** + * Returns a Project object with updated project information. + * + * @return Project object containing the project's updated metadata + * @throws ResourceManagerException upon failure + */ + public Project reload() { + return Project.load(resourceManager, info.projectId()); + } + + /** + * Marks the project identified by the specified project ID for deletion. + * + *

This method will only affect the project if the following criteria are met: + *

    + *
  • The project does not have a billing account associated with it. + *
  • The project has a lifecycle state of {@link ProjectInfo.State#ACTIVE}. + *
+ * This method changes the project's lifecycle state from {@link ProjectInfo.State#ACTIVE} to + * {@link ProjectInfo.State#DELETE_REQUESTED}. The deletion starts at an unspecified time, at + * which point the lifecycle state changes to {@link ProjectInfo.State#DELETE_IN_PROGRESS}. Until + * the deletion completes, you can check the lifecycle state checked by retrieving the project + * with {@link ResourceManager#get}, and the project remains visible to + * {@link ResourceManager#list}. However, you cannot update the project. After the deletion + * completes, the project is not retrievable by the {@link ResourceManager#get} and + * {@link ResourceManager#list} methods. The caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager delete + * @throws ResourceManagerException upon failure + */ + public void delete() { + resourceManager.delete(info.projectId()); + } + + /** + * Restores the project identified by the specified project ID. + * + *

You can only use this method for a project that has a lifecycle state of + * {@link ProjectInfo.State#DELETE_REQUESTED}. After deletion starts, as indicated by a lifecycle + * state of {@link ProjectInfo.State#DELETE_IN_PROGRESS}, the project cannot be restored. The + * caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager undelete + * @throws ResourceManagerException upon failure (including when the project can't be restored) + */ + public void undelete() { + resourceManager.undelete(info.projectId()); + } + + /** + * Replaces the attributes of the project. + * + *

The caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager update + * @return the ProjectInfo representing the new project metadata + * @throws ResourceManagerException upon failure + */ + public Project replace(ProjectInfo projectInfo) { + return new Project(resourceManager, resourceManager.replace(checkNotNull(projectInfo))); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java new file mode 100644 index 000000000000..2cb8a2d93ad2 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java @@ -0,0 +1,353 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.gcloud.resourcemanager; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.util.Data; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import org.joda.time.DateTime; +import org.joda.time.format.ISODateTimeFormat; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * A Google Cloud Resource Manager project metadata object. + * A Project is a high-level Google Cloud Platform entity. It is a container for ACLs, APIs, + * AppEngine Apps, VMs, and other Google Cloud Platform resources. + */ +public class ProjectInfo implements Serializable { + + private static final long serialVersionUID = 9148970963697734236L; + private final String name; + private final String projectId; + private final Map labels; + private final Long projectNumber; + private final State state; + private final Long createTimeMillis; + private final ResourceId parent; + + /** + * The project lifecycle states. + */ + public enum State { + /** + * Only used/useful for distinguishing unset values. + */ + LIFECYCLE_STATE_UNSPECIFIED, + + /** + * The normal and active state. + */ + ACTIVE, + + /** + * The project has been marked for deletion by the user or by the system (Google Cloud + * Platform). This can generally be reversed by calling {@link ResourceManager#undelete}. + */ + DELETE_REQUESTED, + + /** + * The process of deleting the project has begun. Reversing the deletion is no longer possible. + */ + DELETE_IN_PROGRESS + } + + static class ResourceId implements Serializable { + + private static final long serialVersionUID = -325199985993344726L; + + private final String id; + private final String type; + + ResourceId(String id, String type) { + this.id = checkNotNull(id); + this.type = checkNotNull(type); + } + + String id() { + return id; + } + + String type() { + return type; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ResourceId && Objects.equals(toPb(), ((ResourceId) obj).toPb()); + } + + @Override + public int hashCode() { + return Objects.hash(id, type); + } + + com.google.api.services.cloudresourcemanager.model.ResourceId toPb() { + com.google.api.services.cloudresourcemanager.model.ResourceId resourceIdPb = + new com.google.api.services.cloudresourcemanager.model.ResourceId(); + resourceIdPb.setId(id); + resourceIdPb.setType(type.toLowerCase()); + return resourceIdPb; + } + + static ResourceId fromPb( + com.google.api.services.cloudresourcemanager.model.ResourceId resourceIdPb) { + return new ResourceId(resourceIdPb.getId(), resourceIdPb.getType()); + } + } + + public static class Builder { + + private String name; + private String projectId; + private Map labels = new HashMap<>(); + private Long projectNumber; + private State state; + private Long createTimeMillis; + private ResourceId parent; + + private Builder() { + } + + Builder(ProjectInfo info) { + this.name = info.name; + this.projectId = info.projectId; + this.labels.putAll(info.labels); + this.projectNumber = info.projectNumber; + this.state = info.state; + this.createTimeMillis = info.createTimeMillis; + this.parent = info.parent; + } + + /** + * Set the user-assigned name of the project. + * + *

This field is optional and can remain unset. Allowed characters are: lowercase and + * uppercase letters, numbers, hyphen, single-quote, double-quote, space, and exclamation point. + * This field can be changed after project creation. + */ + public Builder name(String name) { + this.name = firstNonNull(name, Data.nullOf(String.class)); + return this; + } + + /** + * Set the unique, user-assigned ID of the project. + * + *

The ID must be 6 to 30 lowercase letters, digits, or hyphens. It must start with a letter. + * Trailing hyphens are prohibited. This field cannot be changed after the server creates the + * project. + */ + public Builder projectId(String projectId) { + this.projectId = checkNotNull(projectId); + return this; + } + + /** + * Add a label associated with this project. + * + *

See {@link #labels} for label restrictions. + */ + public Builder addLabel(String key, String value) { + this.labels.put(key, value); + return this; + } + + /** + * Remove a label associated with this project. + */ + public Builder removeLabel(String key) { + this.labels.remove(key); + return this; + } + + /** + * Clear the labels associated with this project. + */ + public Builder clearLabels() { + this.labels.clear(); + return this; + } + + /** + * Set the labels associated with this project. + * + *

Label keys must be between 1 and 63 characters long and must conform to the following + * regular expression: [a-z]([-a-z0-9]*[a-z0-9])?. Label values must be between 0 and 63 + * characters long and must conform to the regular expression ([a-z]([-a-z0-9]*[a-z0-9])?)?. No + * more than 256 labels can be associated with a given resource. This field can be changed after + * project creation. + */ + public Builder labels(Map labels) { + this.labels = Maps.newHashMap(checkNotNull(labels)); + return this; + } + + Builder projectNumber(Long projectNumber) { + this.projectNumber = projectNumber; + return this; + } + + Builder state(State state) { + this.state = state; + return this; + } + + Builder createTimeMillis(Long createTimeMillis) { + this.createTimeMillis = createTimeMillis; + return this; + } + + Builder parent(ResourceId parent) { + this.parent = parent; + return this; + } + + public ProjectInfo build() { + return new ProjectInfo(this); + } + } + + ProjectInfo(Builder builder) { + this.name = builder.name; + this.projectId = builder.projectId; + this.labels = ImmutableMap.copyOf(builder.labels); + this.projectNumber = builder.projectNumber; + this.state = builder.state; + this.createTimeMillis = builder.createTimeMillis; + this.parent = builder.parent; + } + + /** + * Get the unique, user-assigned ID of the project. + * + *

This field cannot be changed after the server creates the project. + */ + public String projectId() { + return projectId; + } + + /** + * Get the user-assigned name of the project. + * + *

This field is optional, can remain unset, and can be changed after project creation. + */ + public String name() { + return Data.isNull(name) ? null : name; + } + + /** + * Get number uniquely identifying the project. + * + *

This field is set by the server and is read-only. + */ + public Long projectNumber() { + return projectNumber; + } + + /** + * Get the immutable map of labels associated with this project. + */ + public Map labels() { + return labels; + } + + /** + * Get the project's lifecycle state. + * + *

This is a read-only field. To change the lifecycle state of your project, use the + * {@code delete} or {@code undelete} method. + */ + public State state() { + return state; + } + + ResourceId parent() { + return parent; + } + + /** + * Get the project's creation time (in milliseconds). + * + *

This field is set by the server and is read-only. + */ + public Long createTimeMillis() { + return createTimeMillis; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ProjectInfo && Objects.equals(toPb(), ((ProjectInfo) obj).toPb()); + } + + @Override + public int hashCode() { + return Objects.hash(name, projectId, labels, projectNumber, state, createTimeMillis, parent); + } + + public static Builder builder(String id) { + return new Builder().projectId(id); + } + + public Builder toBuilder() { + return new Builder(this); + } + + com.google.api.services.cloudresourcemanager.model.Project toPb() { + com.google.api.services.cloudresourcemanager.model.Project projectPb = + new com.google.api.services.cloudresourcemanager.model.Project(); + projectPb.setName(name); + projectPb.setProjectId(projectId); + projectPb.setLabels(labels); + projectPb.setProjectNumber(projectNumber); + if (state != null) { + projectPb.setLifecycleState(state.toString()); + } + if (createTimeMillis != null) { + projectPb.setCreateTime(ISODateTimeFormat.dateTime().withZoneUTC().print(createTimeMillis)); + } + if (parent != null) { + projectPb.setParent(parent.toPb()); + } + return projectPb; + } + + static ProjectInfo fromPb(com.google.api.services.cloudresourcemanager.model.Project projectPb) { + Builder builder = builder(projectPb.getProjectId()).projectNumber(projectPb.getProjectNumber()); + if (projectPb.getName() != null && !projectPb.getName().equals("Unnamed")) { + builder.name(projectPb.getName()); + } + if (projectPb.getLabels() != null) { + builder.labels(projectPb.getLabels()); + } + if (projectPb.getLifecycleState() != null) { + builder.state(State.valueOf(projectPb.getLifecycleState())); + } + if (projectPb.getCreateTime() != null) { + builder.createTimeMillis(DateTime.parse(projectPb.getCreateTime()).getMillis()); + } + if (projectPb.getParent() != null) { + builder.parent(ResourceId.fromPb(projectPb.getParent())); + } + return builder.build(); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java new file mode 100644 index 000000000000..5d9840362037 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java @@ -0,0 +1,269 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import com.google.common.base.Joiner; +import com.google.common.collect.Sets; +import com.google.gcloud.Page; +import com.google.gcloud.Service; +import com.google.gcloud.spi.ResourceManagerRpc; + +import java.util.Set; + +/** + * An interface for Google Cloud Resource Manager. + * + * @see Google Cloud Resource Manager + */ +public interface ResourceManager extends Service { + + String DEFAULT_CONTENT_TYPE = "application/octet-stream"; + + /** + * The fields of a project. + * + *

These values can be used to specify the fields to include in a partial response when calling + * {@link ResourceManager#get} or {@link ResourceManager#list}. Project ID is always returned, + * even if not specified. + */ + enum ProjectField { + PROJECT_ID("projectId"), + NAME("name"), + LABELS("labels"), + PROJECT_NUMBER("projectNumber"), + STATE("lifecycleState"), + CREATE_TIME("createTime"); + + private final String selector; + + ProjectField(String selector) { + this.selector = selector; + } + + public String selector() { + return selector; + } + + static String selector(ProjectField... fields) { + Set fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1); + fieldStrings.add(PROJECT_ID.selector()); + for (ProjectField field : fields) { + fieldStrings.add(field.selector()); + } + return Joiner.on(',').join(fieldStrings); + } + } + + /** + * Class for specifying project get options. + */ + class ProjectGetOption extends Option { + + private static final long serialVersionUID = 270185129961146874L; + + private ProjectGetOption(ResourceManagerRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify the project's fields to be returned by the RPC call. + * + *

If this option is not provided all project fields are returned. + * {@code ProjectGetOption.fields} can be used to specify only the fields of interest. Project + * ID is always returned, even if not specified. {@link ProjectField} provides a list of fields + * that can be used. + */ + public static ProjectGetOption fields(ProjectField... fields) { + return new ProjectGetOption(ResourceManagerRpc.Option.FIELDS, ProjectField.selector(fields)); + } + } + + /** + * Class for specifying project list options. + */ + class ProjectListOption extends Option { + + private static final long serialVersionUID = 7888768979702012328L; + + private ProjectListOption(ResourceManagerRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify a filter. + * + *

Filter rules are case insensitive. The fields eligible for filtering are: + *

    + *
  • name + *
  • project ID + *
  • labels.key, where key is the name of a label + *
+ * + *

You can specify multiple filters by adding a space between each filter. Multiple filters + * are composed using "and". + * + *

Some examples of filters: + *

    + *
  • name:* The project has a name. + *
  • name:Howl The project's name is Howl or howl. + *
  • name:HOWL Equivalent to above. + *
  • NAME:howl Equivalent to above. + *
  • labels.color:* The project has the label color. + *
  • labels.color:red The project's label color has the value red. + *
  • labels.color:red label.size:big The project's label color has the value red and its + * label size has the value big. + *
+ */ + public static ProjectListOption filter(String filter) { + return new ProjectListOption(ResourceManagerRpc.Option.FILTER, filter); + } + + /** + * Returns an option to specify a page token. + * + *

The page token (returned from a previous call to list) indicates from where listing should + * continue. + */ + public static ProjectListOption pageToken(String pageToken) { + return new ProjectListOption(ResourceManagerRpc.Option.PAGE_TOKEN, pageToken); + } + + /** + * The maximum number of projects to return per RPC. + * + *

The server can return fewer projects than requested. When there are more results than the + * page size, the server will return a page token that can be used to fetch other results. + * Note: pagination is not yet supported; the server currently ignores this field and returns + * all results. + */ + public static ProjectListOption pageSize(int pageSize) { + return new ProjectListOption(ResourceManagerRpc.Option.PAGE_SIZE, pageSize); + } + + /** + * Returns an option to specify the project's fields to be returned by the RPC call. + * + *

If this option is not provided all project fields are returned. + * {@code ProjectListOption.fields} can be used to specify only the fields of interest. Project + * ID is always returned, even if not specified. {@link ProjectField} provides a list of fields + * that can be used. + */ + public static ProjectListOption fields(ProjectField... fields) { + StringBuilder builder = new StringBuilder(); + builder.append("projects(").append(ProjectField.selector(fields)).append(")"); + return new ProjectListOption(ResourceManagerRpc.Option.FIELDS, builder.toString()); + } + } + + /** + * Create a new project. + * + *

Initially, the project resource is owned by its creator exclusively. The creator can later + * grant permission to others to read or update the project. Several APIs are activated + * automatically for the project, including Google Cloud Storage. + * + * @see Cloud + * Resource Manager create + * @return ProjectInfo object representing the new project's metadata. The returned object will + * include the following read-only fields supplied by the server: project number, lifecycle + * state, and creation time. + * @throws ResourceManagerException upon failure + */ + ProjectInfo create(ProjectInfo project); + + /** + * Marks the project identified by the specified project ID for deletion. + * + *

This method will only affect the project if the following criteria are met: + *

    + *
  • The project does not have a billing account associated with it. + *
  • The project has a lifecycle state of {@link ProjectInfo.State#ACTIVE}. + *
+ * This method changes the project's lifecycle state from {@link ProjectInfo.State#ACTIVE} to + * {@link ProjectInfo.State#DELETE_REQUESTED}. The deletion starts at an unspecified time, at + * which point the lifecycle state changes to {@link ProjectInfo.State#DELETE_IN_PROGRESS}. Until + * the deletion completes, you can check the lifecycle state checked by retrieving the project + * with {@link ResourceManager#get}, and the project remains visible to + * {@link ResourceManager#list}. However, you cannot update the project. After the deletion + * completes, the project is not retrievable by the {@link ResourceManager#get} and + * {@link ResourceManager#list} methods. The caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager delete + * @throws ResourceManagerException upon failure + */ + void delete(String projectId); + + /** + * Retrieves the project identified by the specified project ID. + * + *

Returns {@code null} if the project is not found or if the user doesn't have read + * permissions for the project. + * + * @see Cloud + * Resource Manager get + * @throws ResourceManagerException upon failure + */ + ProjectInfo get(String projectId, ProjectGetOption... options); + + /** + * Lists the projects visible to the current user. + * + *

This method returns projects in an unspecified order. New projects do not necessarily appear + * at the end of the list. Use {@link ProjectListOption} to filter this list, set page size, and + * set page tokens. Note that pagination is currently not implemented by the Cloud Resource + * Manager API. + * + * @see Cloud + * Resource Manager list + * @return {@code Page}, a page of projects. + * @throws ResourceManagerException upon failure + */ + Page list(ProjectListOption... options); + + /** + * Replaces the attributes of the project. + * + *

The caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager update + * @return the ProjectInfo representing the new project metadata + * @throws ResourceManagerException upon failure + */ + ProjectInfo replace(ProjectInfo newProject); + + /** + * Restores the project identified by the specified project ID. + * + *

You can only use this method for a project that has a lifecycle state of + * {@link ProjectInfo.State#DELETE_REQUESTED}. After deletion starts, as indicated by a lifecycle + * state of {@link ProjectInfo.State#DELETE_IN_PROGRESS}, the project cannot be restored. The + * caller must have modify permissions for this project. + * + * @see Cloud + * Resource Manager undelete + * @throws ResourceManagerException upon failure + */ + void undelete(String projectId); +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java new file mode 100644 index 000000000000..22b5e8bfed7c --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerException.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import com.google.gcloud.BaseServiceException; +import com.google.gcloud.RetryHelper; +import com.google.gcloud.RetryHelper.RetryHelperException; +import com.google.gcloud.RetryHelper.RetryInterruptedException; + +/** + * Resource Manager service exception. + * + * @see Google Cloud + * Resource Manager error codes + */ +public class ResourceManagerException extends BaseServiceException { + + private static final long serialVersionUID = 6841689911565501705L; + private static final int UNKNOWN_CODE = -1; + + public ResourceManagerException(int code, String message, boolean retryable) { + super(code, message, retryable); + } + + /** + * Translate RetryHelperException to the ResourceManagerException that caused the error. This + * method will always throw an exception. + * + * @throws ResourceManagerException when {@code ex} was caused by a {@code + * ResourceManagerException} + * @throws RetryInterruptedException when {@code ex} is a {@code RetryInterruptedException} + */ + static ResourceManagerException translateAndThrow(RetryHelperException ex) { + if (ex.getCause() instanceof ResourceManagerException) { + throw (ResourceManagerException) ex.getCause(); + } + if (ex instanceof RetryHelper.RetryInterruptedException) { + RetryHelper.RetryInterruptedException.propagate(); + } + throw new ResourceManagerException(UNKNOWN_CODE, ex.getMessage(), false); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerFactory.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerFactory.java new file mode 100644 index 000000000000..256fc321e4e1 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerFactory.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import com.google.gcloud.ServiceFactory; + +/** + * An interface for ResourceManager factories. + */ +public interface ResourceManagerFactory + extends ServiceFactory {} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java new file mode 100644 index 000000000000..1ee247861d59 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java @@ -0,0 +1,235 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.gcloud.RetryHelper.runWithRetries; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.gcloud.BaseService; +import com.google.gcloud.ExceptionHandler; +import com.google.gcloud.ExceptionHandler.Interceptor; +import com.google.gcloud.Page; +import com.google.gcloud.PageImpl; +import com.google.gcloud.RetryHelper.RetryHelperException; +import com.google.gcloud.spi.ResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpc.Tuple; + +import java.io.Serializable; +import java.util.Map; +import java.util.concurrent.Callable; + +public class ResourceManagerImpl + extends BaseService implements ResourceManager { + + private static final Interceptor EXCEPTION_HANDLER_INTERCEPTOR = new Interceptor() { + + private static final long serialVersionUID = 2091576149969931704L; + + @Override + public RetryResult afterEval(Exception exception, RetryResult retryResult) { + return Interceptor.RetryResult.CONTINUE_EVALUATION; + } + + @Override + public RetryResult beforeEval(Exception exception) { + if (exception instanceof ResourceManagerException) { + boolean retriable = ((ResourceManagerException) exception).retryable(); + return retriable ? Interceptor.RetryResult.RETRY : Interceptor.RetryResult.NO_RETRY; + } + return Interceptor.RetryResult.CONTINUE_EVALUATION; + } + }; + static final ExceptionHandler EXCEPTION_HANDLER = ExceptionHandler.builder() + .abortOn(RuntimeException.class) + .interceptor(EXCEPTION_HANDLER_INTERCEPTOR) + .build(); + + private final ResourceManagerRpc resourceManagerRpc; + + ResourceManagerImpl(ResourceManagerOptions options) { + super(options); + resourceManagerRpc = options.rpc(); + } + + @Override + public ProjectInfo create(final ProjectInfo project) { + try { + return ProjectInfo.fromPb(runWithRetries( + new Callable() { + @Override + public com.google.api.services.cloudresourcemanager.model.Project call() { + return resourceManagerRpc.create(project.toPb()); + } + }, options().retryParams(), EXCEPTION_HANDLER)); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + @Override + public void delete(final String projectId) { + try { + runWithRetries(new Callable() { + @Override + public Void call() { + resourceManagerRpc.delete(projectId); + return null; + } + }, options().retryParams(), EXCEPTION_HANDLER); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + @Override + public ProjectInfo get(final String projectId, ProjectGetOption... options) { + final Map optionsMap = optionMap(options); + try { + com.google.api.services.cloudresourcemanager.model.Project answer = runWithRetries( + new Callable() { + @Override + public com.google.api.services.cloudresourcemanager.model.Project call() { + return resourceManagerRpc.get(projectId, optionsMap); + } + }, options().retryParams(), EXCEPTION_HANDLER); + return answer == null ? null : ProjectInfo.fromPb(answer); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + private abstract static class BasePageFetcher + implements PageImpl.NextPageFetcher { + + private static final long serialVersionUID = -5560906434575940205L; + + protected final Map requestOptions; + protected final ResourceManagerOptions serviceOptions; + + BasePageFetcher(ResourceManagerOptions serviceOptions, String cursor, + Map optionMap) { + this.serviceOptions = serviceOptions; + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (cursor != null) { + builder.put(ResourceManagerRpc.Option.PAGE_TOKEN, cursor); + } + for (Map.Entry option : optionMap.entrySet()) { + if (option.getKey() != ResourceManagerRpc.Option.PAGE_TOKEN) { + builder.put(option.getKey(), option.getValue()); + } + } + this.requestOptions = builder.build(); + } + } + + private static class ProjectPageFetcher extends BasePageFetcher { + + private static final long serialVersionUID = -533306655445189098L; + + ProjectPageFetcher(ResourceManagerOptions serviceOptions, String cursor, + Map optionMap) { + super(serviceOptions, cursor, optionMap); + } + + @Override + public Page nextPage() { + return listProjects(serviceOptions, requestOptions); + } + } + + @Override + public Page list(ProjectListOption... options) { + return listProjects(options(), optionMap(options)); + } + + private static Page listProjects(final ResourceManagerOptions serviceOptions, + final Map optionsMap) { + try { + Tuple> result = + runWithRetries(new Callable>>() { + @Override + public Tuple> call() { + return serviceOptions.rpc().list(optionsMap); + } + }, + serviceOptions.retryParams(), EXCEPTION_HANDLER); + String cursor = result.x(); + Iterable projects = + result.y() == null + ? ImmutableList.of() : Iterables.transform( + result.y(), + new Function() { + @Override + public ProjectInfo apply( + com.google.api.services.cloudresourcemanager.model.Project projectPb) { + return ProjectInfo.fromPb(projectPb); + } + }); + return new PageImpl<>( + new ProjectPageFetcher(serviceOptions, cursor, optionsMap), cursor, projects); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + @Override + public ProjectInfo replace(final ProjectInfo newProject) { + try { + return ProjectInfo.fromPb(runWithRetries( + new Callable() { + @Override + public com.google.api.services.cloudresourcemanager.model.Project call() { + return resourceManagerRpc.replace(newProject.toPb()); + } + }, options().retryParams(), EXCEPTION_HANDLER)); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + @Override + public void undelete(final String projectId) { + try { + runWithRetries(new Callable() { + @Override + public Void call() { + resourceManagerRpc.undelete(projectId); + return null; + } + }, options().retryParams(), EXCEPTION_HANDLER); + } catch (RetryHelperException e) { + throw ResourceManagerException.translateAndThrow(e); + } + } + + private Map optionMap(Option... options) { + Map temp = Maps.newEnumMap(ResourceManagerRpc.Option.class); + for (Option option : options) { + Object prev = temp.put(option.rpcOption(), option.value()); + checkArgument(prev == null, "Duplicate option %s", option); + } + return ImmutableMap.copyOf(temp); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java new file mode 100644 index 000000000000..5c0c4baf1ecb --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerOptions.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import com.google.common.collect.ImmutableSet; +import com.google.gcloud.ServiceOptions; +import com.google.gcloud.spi.DefaultResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpcFactory; + +import java.util.Set; + +public class ResourceManagerOptions + extends ServiceOptions { + + private static final long serialVersionUID = 538303101192527452L; + private static final String GCRM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; + private static final Set SCOPES = ImmutableSet.of(GCRM_SCOPE); + private static final String DEFAULT_HOST = "https://cloudresourcemanager.googleapis.com"; + + public static class DefaultResourceManagerFactory implements ResourceManagerFactory { + private static final ResourceManagerFactory INSTANCE = new DefaultResourceManagerFactory(); + + @Override + public ResourceManager create(ResourceManagerOptions options) { + return new ResourceManagerImpl(options); + } + } + + /** + * Returns a default {@code ResourceManagerOptions} instance. + */ + public static ResourceManagerOptions defaultInstance() { + return builder().build(); + } + + public static class DefaultResourceManagerRpcFactory implements ResourceManagerRpcFactory { + private static final ResourceManagerRpcFactory INSTANCE = + new DefaultResourceManagerRpcFactory(); + + @Override + public ResourceManagerRpc create(ResourceManagerOptions options) { + return new DefaultResourceManagerRpc(options); + } + } + + @Override + protected String defaultHost() { + return DEFAULT_HOST; + } + + public static class Builder extends ServiceOptions.Builder { + + private Builder() {} + + private Builder(ResourceManagerOptions options) { + super(options); + } + + @Override + public ResourceManagerOptions build() { + return new ResourceManagerOptions(this); + } + } + + private ResourceManagerOptions(Builder builder) { + super(ResourceManagerFactory.class, ResourceManagerRpcFactory.class, builder); + } + + @Override + protected boolean projectIdRequired() { + return false; + } + + @Override + protected ResourceManagerFactory defaultServiceFactory() { + return DefaultResourceManagerFactory.INSTANCE; + } + + @Override + protected ResourceManagerRpcFactory defaultRpcFactory() { + return DefaultResourceManagerRpcFactory.INSTANCE; + } + + @Override + protected Set scopes() { + return SCOPES; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ResourceManagerOptions && baseEquals((ResourceManagerOptions) obj); + } + + @Override + public int hashCode() { + return baseHashCode(); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + public static Builder builder() { + return new Builder(); + } +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java new file mode 100644 index 000000000000..0c9b0e5a4059 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java @@ -0,0 +1,40 @@ +/* + * 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 client to Google Cloud Resource Manager. + * + *

Here's a simple usage example for using gcloud-java-resourcemanager: + *

{@code
+ * ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service();
+ * String myProjectId = "my-globally-unique-project-id"; // Change to a unique project ID.
+ * ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build());
+ * ProjectInfo newProjectInfo = resourceManager.replace(myProject.toBuilder()
+ *     .addLabel("launch-status", "in-development").build());
+ * Iterator projectIterator = resourceManager.list().iterateAll();
+ * System.out.println("Projects I can view:");
+ * while (projectIterator.hasNext()) {
+ *   System.out.println(projectIterator.next().projectId());
+ * }}
+ * + *

Remember that you must authenticate using the Google Cloud SDK. See more about + * providing + * credentials here. + * + * @see Google Cloud Resource Manager + */ + +package com.google.gcloud.resourcemanager; 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..51c0d5ee28e5 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java @@ -0,0 +1,569 @@ +package com.google.gcloud.resourcemanager.testing; + +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; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.ByteStreams; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +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.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +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.Level; +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 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"; + 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) { + 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 = + ImmutableSet.of('-', '\'', '"', ' ', '!'); + + private final HttpServer server; + private final ConcurrentHashMap projects = new ConcurrentHashMap<>(); + private final int port; + + private 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; + } + } + + private enum Error { + ALREADY_EXISTS(409, "global", "alreadyExists", "ALREADY_EXISTS"), + PERMISSION_DENIED(403, "global", "forbidden", "PERMISSION_DENIED"), + FAILED_PRECONDITION(400, "global", "failedPrecondition", "FAILED_PRECONDITION"), + INVALID_ARGUMENT(400, "global", "badRequest", "INVALID_ARGUMENT"), + BAD_REQUEST(400, "global", "badRequest", "BAD_REQUEST"), + INTERNAL_ERROR(500, "global", "internalError", "INTERNAL_ERROR"); + + 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; + } + + 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 String toJson(String message) throws IOException { + 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); + args.put("status", status); + return jsonFactory.toString(ImmutableMap.of("error", args)); + } + } + + private class RequestHandler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) { + // see https://cloud.google.com/resource-manager/reference/rest/ + Response response; + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String requestMethod = exchange.getRequestMethod(); + try { + switch (requestMethod) { + case "POST": + if (path.endsWith(":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 following request URI: " + requestMethod + " " + + path); + } + } catch (IOException e) { + response = Error.BAD_REQUEST.response(e.getMessage()); + } + 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.write(response.body().getBytes(StandardCharsets.UTF_8)); + outputStream.close(); + } catch (IOException e) { + log.log(Level.WARNING, "IOException encountered when sending response.", e); + } + } + + private static String decodeContent(Headers headers, InputStream inputStream) throws IOException { + List contentEncoding = headers.get("Content-encoding"); + InputStream input = inputStream; + try { + if (contentEncoding != null && !contentEncoding.isEmpty()) { + String encoding = contentEncoding.get(0); + if (SUPPORTED_COMPRESSION_ENCODINGS.contains(encoding)) { + input = new GZIPInputStream(inputStream); + } 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); + } catch (IOException e) { + throw new IOException("Exception encountered when decoding request content.", e); + } + } + + private static String projectIdFromUri(String path) throws IOException { + if (path.isEmpty()) { + throw new IOException("The URI path '" + path + "' doesn't have a project ID."); + } + return path.split(":")[0]; + } + + private static String[] parseFields(String query) { + 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) { + Map options = new HashMap<>(); + if (query != null) { + String[] args = query.split("&"); + for (String arg : args) { + String[] argEntry = arg.split("="); + switch (argEntry[0]) { + case "fields": + // List fields are in the form "projects(field1, field2, ...)" + options.put( + "fields", + argEntry[1].substring("projects(".length(), argEntry[1].length() - 1).split(",")); + break; + case "filter": + options.put("filter", argEntry[1].split(" ")); + 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; + } + + private static String checkForProjectErrors(Project project) { + if (project.getProjectId() == null) { + return "Project ID cannot be empty."; + } + if (!isValidIdOrLabel(project.getProjectId(), 6, 30)) { + 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)) { + 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) { + 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)) { + return "Project " + project.getProjectId() + " has an invalid label entry." + + " See https://cloud.google.com/resource-manager/reference/rest/" + VERSION + + "/projects for more information."; + } + } + } + return null; + } + + private static boolean isValidIdOrLabel(String value, int minLength, int maxLength) { + for (char c : value.toCharArray()) { + if (c != '-' && !Character.isDigit(c) && !Character.isLowerCase(c)) { + return false; + } + } + if (!value.isEmpty() && (!Character.isLetter(value.charAt(0)) || value.endsWith("-"))) { + return false; + } + return value.length() >= minLength && value.length() <= maxLength; + } + + Response create(Project project) { + String customErrorMessage = checkForProjectErrors(project); + if (customErrorMessage != null) { + return Error.INVALID_ARGUMENT.response(customErrorMessage); + } else { + 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); + return new Response(HTTP_OK, createdProjectStr); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response("Error serializing project " + project.getProjectId()); + } + } + } + + synchronized Response delete(String projectId) { + Project project = projects.get(projectId); + if (project == null) { + return Error.PERMISSION_DENIED.response( + "Error when deleting " + projectId + " because the project was not found."); + } + 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"); + return new Response(HTTP_OK, "{}"); + } + } + + Response get(String projectId, String[] fields) { + Project project = projects.get(projectId); + if (project != null) { + try { + return new Response(HTTP_OK, jsonFactory.toString(extractFields(project, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response( + "Error when serializing project " + project.getProjectId()); + } + } else { + return Error.PERMISSION_DENIED.response("Project " + projectId + " not found."); + } + } + + 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, filters); + if (includeProject) { + try { + projectsSerialized.add(jsonFactory.toString(extractFields(p, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response( + "Error when serializing project " + p.getProjectId()); + } + } + } + StringBuilder responseBody = new StringBuilder(); + responseBody.append("{\"projects\": ["); + Joiner.on(",").appendTo(responseBody, projectsSerialized); + responseBody.append("]}"); + return new Response(HTTP_OK, responseBody.toString()); + } + + 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; + } + for (String filter : filters) { + String[] filterEntry = filter.toLowerCase().split(":"); + String filterType = filterEntry[0]; + if ("id".equals(filterType)) { + if (!satisfiesFilter(project.getProjectId(), filterEntry[1])) { + return false; + } + } else if ("name".equals(filterType)) { + if (!satisfiesFilter(project.getName(), filterEntry[1])) { + return false; + } + } 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])) { + return false; + } + } + } + } + return true; + } + + private static boolean satisfiesFilter(String projectValue, String filterValue) { + if (projectValue == null) { + return false; + } + return "*".equals(filterValue) || filterValue.equals(projectValue.toLowerCase()); + } + + 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; + } + + synchronized Response replace(String projectId, Project project) { + Project originalProject = projects.get(projectId); + if (originalProject == null) { + return Error.PERMISSION_DENIED.response( + "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.setProjectId(projectId); + project.setLifecycleState(originalProject.getLifecycleState()); + project.setCreateTime(originalProject.getCreateTime()); + project.setProjectNumber(originalProject.getProjectNumber()); + // replace cannot fail because both this method and removeProject are synchronized + projects.replace(projectId, project); + try { + return new Response(HTTP_OK, jsonFactory.toString(project)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response("Error when serializing project " + projectId); + } + } + + synchronized Response undelete(String projectId) { + Project project = projects.get(projectId); + Response response; + if (project == null) { + 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("Error when undeleting " + projectId + + " because the lifecycle state was not DELETE_REQUESTED."); + } else { + project.setLifecycleState("ACTIVE"); + response = new Response(HTTP_OK, "{}"); + } + return response; + } + + private LocalResourceManagerHelper() { + try { + server = HttpServer.create(new InetSocketAddress(0), 0); + port = server.getAddress().getPort(); + server.createContext(CONTEXT, new RequestHandler()); + } catch (IOException e) { + throw new RuntimeException("Could not bind the mock Resource Manager server.", e); + } + } + + /** + * Creates a LocalResourceManagerHelper object that listens to requests on the local machine. + */ + public static LocalResourceManagerHelper create() { + return new LocalResourceManagerHelper(); + } + + /** + * Returns a ResourceManagerOptions instance that sets the host to use the mock server. + */ + public ResourceManagerOptions options() { + return ResourceManagerOptions.builder().host("http://localhost:" + port).build(); + } + + /** + * 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 change the lifecycle state of the specified project. + * + * @return true if the lifecycle state was successfully updated, false otherwise. + */ + public synchronized 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"); + Project project = projects.get(checkNotNull(projectId)); + if (project != null) { + project.setLifecycleState(lifecycleState); + return true; + } + return false; + } + + /** + * 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 if the project didn't exist. + */ + public synchronized boolean removeProject(String projectId) { + // Because this method is synchronized, any code that relies on non-atomic read/write operations + // should not fail if that code is also synchronized. + return projects.remove(checkNotNull(projectId)) != null; + } +} 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..7e5519f7d085 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/package-info.java @@ -0,0 +1,32 @@ +/* + * 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();
+ * ResourceManager resourceManager = resourceManagerHelper.options().service();
+ * } 
+ * + *

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 new file mode 100644 index 000000000000..ec95207c2e7b --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java @@ -0,0 +1,136 @@ +package com.google.gcloud.spi; + +import static com.google.gcloud.spi.ResourceManagerRpc.Option.FIELDS; +import static com.google.gcloud.spi.ResourceManagerRpc.Option.FILTER; +import static com.google.gcloud.spi.ResourceManagerRpc.Option.PAGE_SIZE; +import static com.google.gcloud.spi.ResourceManagerRpc.Option.PAGE_TOKEN; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; + +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; +import com.google.api.client.json.jackson.JacksonFactory; +import com.google.api.services.cloudresourcemanager.Cloudresourcemanager; +import com.google.api.services.cloudresourcemanager.model.ListProjectsResponse; +import com.google.api.services.cloudresourcemanager.model.Project; +import com.google.common.collect.ImmutableSet; +import com.google.gcloud.resourcemanager.ResourceManagerException; +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +public class DefaultResourceManagerRpc implements ResourceManagerRpc { + + // see https://cloud.google.com/resource-manager/v1/errors/core_errors + private static final Set RETRYABLE_CODES = ImmutableSet.of(503, 500, 429); + private static final Set RETRYABLE_REASONS = ImmutableSet.of("concurrentLimitExceeded", + "limitExceeded", "rateLimitExceeded", "rateLimitExceededUnreg", "servingLimitExceeded", + "userRateLimitExceeded", "userRateLimitExceededUnreg", "variableTermLimitExceeded"); + + private final Cloudresourcemanager resourceManager; + + public DefaultResourceManagerRpc(ResourceManagerOptions options) { + HttpTransport transport = options.httpTransportFactory().create(); + HttpRequestInitializer initializer = options.httpRequestInitializer(); + resourceManager = + new Cloudresourcemanager.Builder(transport, new JacksonFactory(), initializer) + .setRootUrl(options.host()) + .setApplicationName(options.applicationName()) + .build(); + } + + private static ResourceManagerException translate(IOException exception) { + ResourceManagerException translated; + if (exception instanceof GoogleJsonResponseException) { + translated = translate(((GoogleJsonResponseException) exception).getDetails()); + } else { + translated = new ResourceManagerException(0, exception.getMessage(), false); + } + translated.initCause(exception); + return translated; + } + + private static ResourceManagerException translate(GoogleJsonError exception) { + boolean retryable = + RETRYABLE_CODES.contains(exception.getCode()) || (!exception.getErrors().isEmpty() + && RETRYABLE_REASONS.contains(exception.getErrors().get(0).getReason())); + return new ResourceManagerException(exception.getCode(), exception.getMessage(), retryable); + } + + @Override + public Project create(Project project) throws ResourceManagerException { + try { + return resourceManager.projects().create(project).execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public void delete(String projectId) throws ResourceManagerException { + try { + resourceManager.projects().delete(projectId).execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public Project get(String projectId, Map options) throws ResourceManagerException { + try { + return resourceManager.projects() + .get(projectId) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + ResourceManagerException translated = translate(ex); + if (translated.code() == HTTP_FORBIDDEN || translated.code() == HTTP_NOT_FOUND) { + // Service can return either 403 or 404 to signify that the project doesn't exist. + return null; + } else { + throw translated; + } + } + } + + @Override + public Tuple> list(Map options) + throws ResourceManagerException { + try { + ListProjectsResponse response = resourceManager.projects() + .list() + .setFields(FIELDS.getString(options)) + .setFilter(FILTER.getString(options)) + .setPageSize(PAGE_SIZE.getInt(options)) + .setPageToken(PAGE_TOKEN.getString(options)) + .execute(); + return Tuple.>of( + response.getNextPageToken(), response.getProjects()); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public void undelete(String projectId) throws ResourceManagerException { + try { + resourceManager.projects().undelete(projectId).execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public Project replace(Project project) throws ResourceManagerException { + try { + return resourceManager.projects().update(project.getProjectId(), project).execute(); + } catch (IOException ex) { + throw translate(ex); + } + } +} + diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java new file mode 100644 index 000000000000..52dfc2d2368e --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.spi; + +import com.google.api.services.cloudresourcemanager.model.Project; +import com.google.gcloud.resourcemanager.ResourceManagerException; + +import java.util.Map; + +public interface ResourceManagerRpc { + + enum Option { + FILTER("filter"), + FIELDS("fields"), + PAGE_SIZE("pageSize"), + PAGE_TOKEN("pageToken"); + + private final String value; + + Option(String value) { + this.value = value; + } + + public String value() { + return value; + } + + @SuppressWarnings("unchecked") + T get(Map options) { + return (T) options.get(this); + } + + String getString(Map options) { + return get(options); + } + + Integer getInt(Map options) { + return get(options); + } + } + + class Tuple { + private final X x; + private final Y y; + + private Tuple(X x, Y y) { + this.x = x; + this.y = y; + } + + public static Tuple of(X x, Y y) { + return new Tuple<>(x, y); + } + + public X x() { + return x; + } + + public Y y() { + return y; + } + } + + Project create(Project project) throws ResourceManagerException; + + void delete(String projectId) throws ResourceManagerException; + + Project get(String projectId, Map options) throws ResourceManagerException; + + Tuple> list(Map options) throws ResourceManagerException; + + void undelete(String projectId) throws ResourceManagerException; + + Project replace(Project project) throws ResourceManagerException; + + // TODO(ajaykannan): implement "Organization" functionality when available (issue #319) +} diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpcFactory.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpcFactory.java new file mode 100644 index 000000000000..c2c607c0c205 --- /dev/null +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpcFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.spi; + +import com.google.gcloud.resourcemanager.ResourceManagerOptions; + +/** + * An interface for Resource Manager RPC factory. + * Implementation will be loaded via {@link java.util.ServiceLoader}. + */ +public interface ResourceManagerRpcFactory + extends ServiceRpcFactory { +} 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..7eb0156d4e56 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java @@ -0,0 +1,539 @@ +package com.google.gcloud.resourcemanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.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.Test; + +import java.util.HashMap; +import java.util.Map; + +public class LocalResourceManagerHelperTest { + + 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(); + private static final ResourceManagerRpc rpc = + new DefaultResourceManagerRpc(RESOURCE_MANAGER_HELPER.options()); + private static final com.google.api.services.cloudresourcemanager.model.Project PARTIAL_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() + .setProjectId("complete-project") + .setName("full project") + .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); + + @BeforeClass + public static void beforeClass() { + RESOURCE_MANAGER_HELPER.start(); + } + + 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()) + .setCreateTime(from.getCreateTime()) + .setLifecycleState(from.getLifecycleState()) + .setParent(from.getParent() != null ? from.getParent().clone() : null); + } + + private void clearProjects() { + for (com.google.api.services.cloudresourcemanager.model.Project project : + rpc.list(EMPTY_RPC_OPTIONS).y()) { + RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); + } + } + + @Before + public void setUp() { + 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); + compareReadWriteFields(PARTIAL_PROJECT, returnedProject); + assertEquals("ACTIVE", returnedProject.getLifecycleState()); + assertNull(returnedProject.getLabels()); + assertNull(returnedProject.getName()); + assertNull(returnedProject.getParent()); + assertNotNull(returnedProject.getProjectNumber()); + assertNotNull(returnedProject.getCreateTime()); + 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); + compareReadWriteFields(PROJECT_WITH_PARENT, returnedProject); + assertEquals("ACTIVE", returnedProject.getLifecycleState()); + assertNotNull(returnedProject.getProjectNumber()); + assertNotNull(returnedProject.getCreateTime()); + } + + @Test + public void testIsInvalidProjectId() { + com.google.api.services.cloudresourcemanager.model.Project project = + new com.google.api.services.cloudresourcemanager.model.Project(); + String invalidIDMessageSubstring = "invalid ID"; + expectInvalidArgumentException(project, "Project ID cannot be empty."); + project.setProjectId("abcde"); + expectInvalidArgumentException(project, invalidIDMessageSubstring); + project.setProjectId("this-project-id-is-more-than-thirty-characters-long"); + expectInvalidArgumentException(project, invalidIDMessageSubstring); + project.setProjectId("project-id-with-invalid-character-?"); + expectInvalidArgumentException(project, invalidIDMessageSubstring); + project.setProjectId("-invalid-start-character"); + expectInvalidArgumentException(project, invalidIDMessageSubstring); + project.setProjectId("invalid-ending-character-"); + expectInvalidArgumentException(project, invalidIDMessageSubstring); + project.setProjectId("some-valid-project-id-12345"); + rpc.create(project); + assertNotNull(rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS)); + } + + private void expectInvalidArgumentException( + 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(400, e.code()); + assertTrue(e.getMessage().contains(errorMessageSubstring)); + } + } + + @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(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(), rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS).getName()); + RESOURCE_MANAGER_HELPER.removeProject(project.getProjectId()); + project.setName("invalid-character-,"); + 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 + public void testIsInvalidProjectLabels() { + com.google.api.services.cloudresourcemanager.model.Project project = + new com.google.api.services.cloudresourcemanager.model.Project().setProjectId( + "some-valid-project-id"); + String invalidLabelMessageSubstring = "invalid label entry"; + project.setLabels(ImmutableMap.of("", "v1")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(ImmutableMap.of( + "this-project-label-is-more-than-sixty-three-characters-long-so-it-should-fail", "v1")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(ImmutableMap.of( + "k1", "this-project-label-is-more-than-sixty-three-characters-long-so-it-should-fail")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(ImmutableMap.of("k1?", "v1")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(ImmutableMap.of("k1", "v1*")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(ImmutableMap.of("-k1", "v1")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(ImmutableMap.of("k1", "-v1")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(ImmutableMap.of("k1-", "v1")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + project.setLabels(ImmutableMap.of("k1", "v1-")); + expectInvalidArgumentException(project, invalidLabelMessageSubstring); + Map tooManyLabels = new HashMap<>(); + for (int i = 0; i < 257; i++) { + tooManyLabels.put("k" + Integer.toString(i), "v" + Integer.toString(i)); + } + 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)); + assertTrue(rpc.get(project.getProjectId(), EMPTY_RPC_OPTIONS) + .getLabels() + .get("k-1") + .isEmpty()); + } + + @Test + public void testDelete() { + 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 doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("not found.")); + } + } + + @Test + public void testDeleteWhenDeleteInProgress() { + rpc.create(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS"); + 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 + public void testDeleteWhenDeleteRequested() { + rpc.create(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_REQUESTED"); + 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 + public void testGet() { + rpc.create(COMPLETE_PROJECT); + com.google.api.services.cloudresourcemanager.model.Project returnedProject = + rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS); + compareReadWriteFields(COMPLETE_PROJECT, returnedProject); + RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId()); + assertNull(rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS)); + } + + @Test + public void testGetWithOptions() { + 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 = + rpc.get(COMPLETE_PROJECT.getProjectId(), rpcOptions); + assertFalse(COMPLETE_PROJECT.equals(returnedProject)); + assertEquals(COMPLETE_PROJECT.getProjectId(), returnedProject.getProjectId()); + assertEquals(COMPLETE_PROJECT.getName(), returnedProject.getName()); + assertEquals(originalProject.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()); + rpc.create(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_REQUESTED"); + rpc.create(PROJECT_WITH_PARENT); + projects = rpc.list(EMPTY_RPC_OPTIONS); + for (com.google.api.services.cloudresourcemanager.model.Project p : projects.y()) { + if (p.getProjectId().equals(COMPLETE_PROJECT.getProjectId())) { + compareReadWriteFields(COMPLETE_PROJECT, p); + } else if (p.getProjectId().equals(PROJECT_WITH_PARENT.getProjectId())) { + compareReadWriteFields(PROJECT_WITH_PARENT, p); + } else { + fail("Unexpected project in list."); + } + } + } + + @Test + public void testListFieldOptions() { + Map rpcOptions = new HashMap<>(); + rpcOptions.put(ResourceManagerRpc.Option.FIELDS, "projects(projectId,name,labels)"); + rpcOptions.put(ResourceManagerRpc.Option.PAGE_TOKEN, "somePageToken"); + rpcOptions.put(ResourceManagerRpc.Option.PAGE_SIZE, 1); + rpc.create(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 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") + .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"); + 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") + .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"); + 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)); + compareReadWriteFields(matchingProject, p); + } + } + + @Test + public void testReplace() { + 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 = + 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"); + com.google.api.services.cloudresourcemanager.model.Project returnedProject = + 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"); + 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() { + 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( + COMPLETE_PROJECT.getProjectId()); + 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() { + 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( + COMPLETE_PROJECT.getProjectId()); + 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 testReplaceAddingParent() { + rpc.create(COMPLETE_PROJECT); + com.google.api.services.cloudresourcemanager.model.Project anotherProject = + new com.google.api.services.cloudresourcemanager.model.Project() + .setProjectId(COMPLETE_PROJECT.getProjectId()) + .setParent(PARENT); + 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 + public void testReplaceRemovingParent() { + 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()); + 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 + public void testUndelete() { + 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."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("the project was not found")); + } + } + + @Test + public void testUndeleteWhenActive() { + rpc.create(COMPLETE_PROJECT); + 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() { + rpc.create(COMPLETE_PROJECT); + RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS"); + try { + 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")); + } + } + + @Test + public void testChangeLifecycleStatus() { + assertFalse(RESOURCE_MANAGER_HELPER.changeLifecycleState( + COMPLETE_PROJECT.getProjectId(), "DELETE_IN_PROGRESS")); + 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"); + fail("Should fail because of an invalid lifecycle state"); + } catch (IllegalArgumentException e) { + // ignore + } + } + + @Test + public void testRemoveProject() { + assertFalse(RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId())); + rpc.create(COMPLETE_PROJECT); + assertTrue(RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId())); + assertNull(rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS)); + } + + 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()); + } +} diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java new file mode 100644 index 000000000000..3aaef8047322 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectInfoTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableMap; + +import org.junit.Test; + +import java.util.Map; + +public class ProjectInfoTest { + + private static final String PROJECT_ID = "project-id"; + private static final String NAME = "myProj"; + private static final Map LABELS = ImmutableMap.of("k1", "v1", "k2", "v2"); + private static final Long PROJECT_NUMBER = 123L; + private static final Long CREATE_TIME_MILLIS = 123456789L; + private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; + private static final ProjectInfo.ResourceId PARENT = + new ProjectInfo.ResourceId("id", "organization"); + private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder(PROJECT_ID) + .name(NAME) + .labels(LABELS) + .projectNumber(PROJECT_NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .parent(PARENT) + .build(); + private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder(PROJECT_ID).build(); + private static final ProjectInfo UNNAMED_PROJECT_FROM_LIST = + PARTIAL_PROJECT_INFO.toBuilder().name("Unnamed").build(); + + @Test + public void testBuilder() { + assertEquals(PROJECT_ID, FULL_PROJECT_INFO.projectId()); + assertEquals(NAME, FULL_PROJECT_INFO.name()); + assertEquals(LABELS, FULL_PROJECT_INFO.labels()); + assertEquals(PROJECT_NUMBER, FULL_PROJECT_INFO.projectNumber()); + assertEquals(CREATE_TIME_MILLIS, FULL_PROJECT_INFO.createTimeMillis()); + assertEquals(STATE, FULL_PROJECT_INFO.state()); + + assertEquals(PROJECT_ID, PARTIAL_PROJECT_INFO.projectId()); + assertEquals(null, PARTIAL_PROJECT_INFO.name()); + assertTrue(PARTIAL_PROJECT_INFO.labels().isEmpty()); + assertEquals(null, PARTIAL_PROJECT_INFO.projectNumber()); + assertEquals(null, PARTIAL_PROJECT_INFO.createTimeMillis()); + assertEquals(null, PARTIAL_PROJECT_INFO.state()); + } + + @Test + public void testToBuilder() { + compareProjects(FULL_PROJECT_INFO, FULL_PROJECT_INFO.toBuilder().build()); + compareProjects(PARTIAL_PROJECT_INFO, PARTIAL_PROJECT_INFO.toBuilder().build()); + } + + @Test + public void testToAndFromPb() { + assertTrue(FULL_PROJECT_INFO.toPb().getCreateTime().endsWith("Z")); + compareProjects(FULL_PROJECT_INFO, ProjectInfo.fromPb(FULL_PROJECT_INFO.toPb())); + compareProjects(PARTIAL_PROJECT_INFO, ProjectInfo.fromPb(PARTIAL_PROJECT_INFO.toPb())); + compareProjects(PARTIAL_PROJECT_INFO, ProjectInfo.fromPb(UNNAMED_PROJECT_FROM_LIST.toPb())); + } + + @Test + public void testEquals() { + compareProjects( + FULL_PROJECT_INFO, + ProjectInfo.builder(PROJECT_ID) + .name(NAME) + .labels(LABELS) + .projectNumber(PROJECT_NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .parent(PARENT) + .build()); + compareProjects(PARTIAL_PROJECT_INFO, ProjectInfo.builder(PROJECT_ID).build()); + assertNotEquals(FULL_PROJECT_INFO, PARTIAL_PROJECT_INFO); + } + + private void compareProjects(ProjectInfo expected, ProjectInfo value) { + assertEquals(expected, value); + assertEquals(expected.projectId(), value.projectId()); + assertEquals(expected.name(), value.name()); + assertEquals(expected.labels(), value.labels()); + assertEquals(expected.projectNumber(), value.projectNumber()); + assertEquals(expected.createTimeMillis(), value.createTimeMillis()); + assertEquals(expected.state(), value.state()); + assertEquals(expected.parent(), value.parent()); + } +} + diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java new file mode 100644 index 000000000000..65bb37dbccf9 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import com.google.common.collect.ImmutableMap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Map; + +public class ProjectTest { + private static final String PROJECT_ID = "project-id"; + private static final String NAME = "myProj"; + private static final Map LABELS = ImmutableMap.of("k1", "v1", "k2", "v2"); + private static final Long PROJECT_NUMBER = 123L; + private static final Long CREATE_TIME_MILLIS = 123456789L; + private static final ProjectInfo.State STATE = ProjectInfo.State.DELETE_REQUESTED; + private static final ProjectInfo PROJECT_INFO = ProjectInfo.builder(PROJECT_ID) + .name(NAME) + .labels(LABELS) + .projectNumber(PROJECT_NUMBER) + .createTimeMillis(CREATE_TIME_MILLIS) + .state(STATE) + .build(); + + private ResourceManager resourceManager; + private Project project; + + @Before + public void setUp() throws Exception { + resourceManager = createStrictMock(ResourceManager.class); + project = new Project(resourceManager, PROJECT_INFO); + } + + @After + public void tearDown() throws Exception { + verify(resourceManager); + } + + @Test + public void testLoad() { + expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(PROJECT_INFO); + replay(resourceManager); + Project loadedProject = Project.load(resourceManager, PROJECT_INFO.projectId()); + assertEquals(PROJECT_INFO, loadedProject.info()); + } + + @Test + public void testReload() { + ProjectInfo newInfo = PROJECT_INFO.toBuilder().addLabel("k3", "v3").build(); + expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(newInfo); + replay(resourceManager); + Project newProject = project.reload(); + assertSame(resourceManager, newProject.resourceManager()); + assertEquals(newInfo, newProject.info()); + } + + @Test + public void testInfo() { + replay(resourceManager); + assertEquals(PROJECT_INFO, project.info()); + } + + @Test + public void testResourceManager() { + replay(resourceManager); + assertEquals(resourceManager, project.resourceManager()); + } + + @Test + public void testDelete() { + resourceManager.delete(PROJECT_INFO.projectId()); + replay(resourceManager); + project.delete(); + } + + @Test + public void testUndelete() { + resourceManager.undelete(PROJECT_INFO.projectId()); + replay(resourceManager); + project.undelete(); + } + + @Test + public void testReplace() { + ProjectInfo newInfo = PROJECT_INFO.toBuilder().addLabel("k3", "v3").build(); + expect(resourceManager.replace(newInfo)).andReturn(newInfo); + replay(resourceManager); + Project newProject = project.replace(newInfo); + assertSame(resourceManager, newProject.resourceManager()); + assertEquals(newInfo, newProject.info()); + } +} diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java new file mode 100644 index 000000000000..1210e4ec81a7 --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java @@ -0,0 +1,324 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.Page; +import com.google.gcloud.RetryParams; +import com.google.gcloud.resourcemanager.ProjectInfo.ResourceId; +import com.google.gcloud.resourcemanager.ResourceManager.ProjectField; +import com.google.gcloud.resourcemanager.ResourceManager.ProjectGetOption; +import com.google.gcloud.resourcemanager.ResourceManager.ProjectListOption; +import com.google.gcloud.resourcemanager.testing.LocalResourceManagerHelper; +import com.google.gcloud.spi.ResourceManagerRpc; +import com.google.gcloud.spi.ResourceManagerRpcFactory; + +import org.easymock.EasyMock; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.Map; + +public class ResourceManagerImplTest { + + private static final LocalResourceManagerHelper RESOURCE_MANAGER_HELPER = + LocalResourceManagerHelper.create(); + private static final ResourceManager RESOURCE_MANAGER = + RESOURCE_MANAGER_HELPER.options().service(); + private static final ProjectGetOption GET_FIELDS = + ProjectGetOption.fields(ProjectField.NAME, ProjectField.CREATE_TIME); + private static final ProjectListOption LIST_FIELDS = + ProjectListOption.fields(ProjectField.NAME, ProjectField.LABELS); + private static final ProjectListOption LIST_FILTER = + ProjectListOption.filter("id:* name:myProject labels.color:blue LABELS.SIZE:*"); + private static final ProjectInfo PARTIAL_PROJECT = ProjectInfo.builder("partial-project").build(); + private static final ResourceId PARENT = new ResourceId("id", "type"); + private static final ProjectInfo COMPLETE_PROJECT = ProjectInfo.builder("complete-project") + .name("name") + .labels(ImmutableMap.of("k1", "v1")) + .parent(PARENT) + .build(); + private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @BeforeClass + public static void beforeClass() { + RESOURCE_MANAGER_HELPER.start(); + } + + @Before + public void setUp() { + clearProjects(); + } + + private void clearProjects() { + for (ProjectInfo project : RESOURCE_MANAGER.list().values()) { + RESOURCE_MANAGER_HELPER.removeProject(project.projectId()); + } + } + + @AfterClass + public static void afterClass() { + RESOURCE_MANAGER_HELPER.stop(); + } + + private void compareReadWriteFields(ProjectInfo expected, ProjectInfo actual) { + assertEquals(expected.projectId(), actual.projectId()); + assertEquals(expected.name(), actual.name()); + assertEquals(expected.labels(), actual.labels()); + assertEquals(expected.parent(), actual.parent()); + } + + @Test + public void testCreate() { + ProjectInfo returnedProject = RESOURCE_MANAGER.create(PARTIAL_PROJECT); + compareReadWriteFields(PARTIAL_PROJECT, returnedProject); + assertEquals(ProjectInfo.State.ACTIVE, returnedProject.state()); + assertNull(returnedProject.name()); + assertNull(returnedProject.parent()); + assertNotNull(returnedProject.projectNumber()); + assertNotNull(returnedProject.createTimeMillis()); + try { + RESOURCE_MANAGER.create(PARTIAL_PROJECT); + fail("Should fail, project already exists."); + } catch (ResourceManagerException e) { + assertEquals(409, e.code()); + assertTrue(e.getMessage().startsWith("A project with the same project ID") + && e.getMessage().endsWith("already exists.")); + } + returnedProject = RESOURCE_MANAGER.create(COMPLETE_PROJECT); + compareReadWriteFields(COMPLETE_PROJECT, returnedProject); + assertEquals(ProjectInfo.State.ACTIVE, returnedProject.state()); + assertNotNull(returnedProject.projectNumber()); + assertNotNull(returnedProject.createTimeMillis()); + } + + @Test + public void testDelete() { + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + RESOURCE_MANAGER.delete(COMPLETE_PROJECT.projectId()); + assertEquals(ProjectInfo.State.DELETE_REQUESTED, + RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()).state()); + try { + RESOURCE_MANAGER.delete("some-nonexistant-project-id"); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("not found.")); + } + } + + @Test + public void testGet() { + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + ProjectInfo returnedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()); + compareReadWriteFields(COMPLETE_PROJECT, returnedProject); + RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.projectId()); + assertNull(RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId())); + } + + @Test + public void testGetWithOptions() { + ProjectInfo originalProject = RESOURCE_MANAGER.create(COMPLETE_PROJECT); + ProjectInfo returnedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId(), GET_FIELDS); + assertFalse(COMPLETE_PROJECT.equals(returnedProject)); + assertEquals(COMPLETE_PROJECT.projectId(), returnedProject.projectId()); + assertEquals(COMPLETE_PROJECT.name(), returnedProject.name()); + assertEquals(originalProject.createTimeMillis(), returnedProject.createTimeMillis()); + assertNull(returnedProject.parent()); + assertNull(returnedProject.projectNumber()); + assertNull(returnedProject.state()); + assertTrue(returnedProject.labels().isEmpty()); + } + + @Test + public void testList() { + Page projects = RESOURCE_MANAGER.list(); + assertFalse(projects.values().iterator().hasNext()); // TODO: change this when #421 is resolved + RESOURCE_MANAGER.create(PARTIAL_PROJECT); + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + for (ProjectInfo p : RESOURCE_MANAGER.list().values()) { + if (p.projectId().equals(PARTIAL_PROJECT.projectId())) { + compareReadWriteFields(PARTIAL_PROJECT, p); + } else if (p.projectId().equals(COMPLETE_PROJECT.projectId())) { + compareReadWriteFields(COMPLETE_PROJECT, p); + } else { + fail("Some unexpected project returned by list."); + } + } + } + + @Test + public void testListFieldOptions() { + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + Page projects = RESOURCE_MANAGER.list(LIST_FIELDS); + ProjectInfo returnedProject = projects.iterateAll().next(); + assertEquals(COMPLETE_PROJECT.projectId(), returnedProject.projectId()); + assertEquals(COMPLETE_PROJECT.name(), returnedProject.name()); + assertEquals(COMPLETE_PROJECT.labels(), returnedProject.labels()); + assertNull(returnedProject.parent()); + assertNull(returnedProject.projectNumber()); + assertNull(returnedProject.state()); + assertNull(returnedProject.createTimeMillis()); + } + + @Test + public void testListFilterOptions() { + ProjectInfo matchingProject = ProjectInfo.builder("matching-project") + .name("MyProject") + .labels(ImmutableMap.of("color", "blue", "size", "big")) + .build(); + ProjectInfo nonMatchingProject1 = ProjectInfo.builder("non-matching-project1") + .name("myProject") + .labels(ImmutableMap.of("color", "blue")) + .build(); + ProjectInfo nonMatchingProject2 = ProjectInfo.builder("non-matching-project2") + .name("myProj") + .labels(ImmutableMap.of("color", "blue", "size", "big")) + .build(); + ProjectInfo nonMatchingProject3 = ProjectInfo.builder("non-matching-project3").build(); + RESOURCE_MANAGER.create(matchingProject); + RESOURCE_MANAGER.create(nonMatchingProject1); + RESOURCE_MANAGER.create(nonMatchingProject2); + RESOURCE_MANAGER.create(nonMatchingProject3); + for (ProjectInfo p : RESOURCE_MANAGER.list(LIST_FILTER).values()) { + assertFalse(p.equals(nonMatchingProject1)); + assertFalse(p.equals(nonMatchingProject2)); + compareReadWriteFields(matchingProject, p); + } + } + + @Test + public void testReplace() { + ProjectInfo createdProject = RESOURCE_MANAGER.create(COMPLETE_PROJECT); + Map newLabels = ImmutableMap.of("new k1", "new v1"); + ProjectInfo anotherCompleteProject = ProjectInfo.builder(COMPLETE_PROJECT.projectId()) + .labels(newLabels) + .projectNumber(987654321L) + .createTimeMillis(230682061315L) + .state(ProjectInfo.State.DELETE_REQUESTED) + .parent(createdProject.parent()) + .build(); + ProjectInfo returnedProject = RESOURCE_MANAGER.replace(anotherCompleteProject); + compareReadWriteFields(anotherCompleteProject, returnedProject); + assertEquals(createdProject.projectNumber(), returnedProject.projectNumber()); + assertEquals(createdProject.createTimeMillis(), returnedProject.createTimeMillis()); + assertEquals(createdProject.state(), returnedProject.state()); + ProjectInfo nonexistantProject = + ProjectInfo.builder("some-project-id-that-does-not-exist").build(); + try { + RESOURCE_MANAGER.replace(nonexistantProject); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("the project was not found")); + } + } + + @Test + public void testUndelete() { + RESOURCE_MANAGER.create(COMPLETE_PROJECT); + RESOURCE_MANAGER.delete(COMPLETE_PROJECT.projectId()); + assertEquals( + ProjectInfo.State.DELETE_REQUESTED, + RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()).state()); + RESOURCE_MANAGER.undelete(COMPLETE_PROJECT.projectId()); + ProjectInfo revivedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()); + compareReadWriteFields(COMPLETE_PROJECT, revivedProject); + assertEquals(ProjectInfo.State.ACTIVE, revivedProject.state()); + try { + RESOURCE_MANAGER.undelete("invalid-project-id"); + fail("Should fail because the project doesn't exist."); + } catch (ResourceManagerException e) { + assertEquals(403, e.code()); + assertTrue(e.getMessage().contains("the project was not found")); + } + } + + @Test + public void testRetryableException() { + ResourceManagerRpcFactory rpcFactoryMock = EasyMock.createMock(ResourceManagerRpcFactory.class); + ResourceManagerRpc resourceManagerRpcMock = EasyMock.createMock(ResourceManagerRpc.class); + EasyMock.expect(rpcFactoryMock.create(EasyMock.anyObject(ResourceManagerOptions.class))) + .andReturn(resourceManagerRpcMock); + EasyMock.replay(rpcFactoryMock); + ResourceManager resourceManagerMock = ResourceManagerOptions.builder() + .serviceRpcFactory(rpcFactoryMock) + .retryParams(RetryParams.defaultInstance()) + .build() + .service(); + EasyMock.expect(resourceManagerRpcMock.get(PARTIAL_PROJECT.projectId(), EMPTY_RPC_OPTIONS)) + .andThrow(new ResourceManagerException(500, "Internal Error", true)) + .andReturn(PARTIAL_PROJECT.toPb()); + EasyMock.replay(resourceManagerRpcMock); + ProjectInfo returnedProject = resourceManagerMock.get(PARTIAL_PROJECT.projectId()); + assertEquals(PARTIAL_PROJECT, returnedProject); + } + + @Test + public void testNonRetryableException() { + ResourceManagerRpcFactory rpcFactoryMock = EasyMock.createMock(ResourceManagerRpcFactory.class); + ResourceManagerRpc resourceManagerRpcMock = EasyMock.createMock(ResourceManagerRpc.class); + EasyMock.expect(rpcFactoryMock.create(EasyMock.anyObject(ResourceManagerOptions.class))) + .andReturn(resourceManagerRpcMock); + EasyMock.replay(rpcFactoryMock); + ResourceManager resourceManagerMock = ResourceManagerOptions.builder() + .serviceRpcFactory(rpcFactoryMock) + .retryParams(RetryParams.defaultInstance()) + .build() + .service(); + EasyMock.expect(resourceManagerRpcMock.get(PARTIAL_PROJECT.projectId(), EMPTY_RPC_OPTIONS)) + .andThrow(new ResourceManagerException( + 403, "Project " + PARTIAL_PROJECT.projectId() + " not found.", false)) + .once(); + EasyMock.replay(resourceManagerRpcMock); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage("Project " + PARTIAL_PROJECT.projectId() + " not found."); + resourceManagerMock.get(PARTIAL_PROJECT.projectId()); + } + + @Test + public void testRuntimeException() { + ResourceManagerRpcFactory rpcFactoryMock = EasyMock.createMock(ResourceManagerRpcFactory.class); + ResourceManagerRpc resourceManagerRpcMock = EasyMock.createMock(ResourceManagerRpc.class); + EasyMock.expect(rpcFactoryMock.create(EasyMock.anyObject(ResourceManagerOptions.class))) + .andReturn(resourceManagerRpcMock); + EasyMock.replay(rpcFactoryMock); + ResourceManager resourceManagerMock = + ResourceManagerOptions.builder().serviceRpcFactory(rpcFactoryMock).build().service(); + String exceptionMessage = "Artificial runtime exception"; + EasyMock.expect(resourceManagerRpcMock.get(PARTIAL_PROJECT.projectId(), EMPTY_RPC_OPTIONS)) + .andThrow(new RuntimeException(exceptionMessage)); + EasyMock.replay(resourceManagerRpcMock); + thrown.expect(ResourceManagerException.class); + thrown.expectMessage(exceptionMessage); + resourceManagerMock.get(PARTIAL_PROJECT.projectId()); + } +} diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java new file mode 100644 index 000000000000..64e09449149b --- /dev/null +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.resourcemanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; + +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.PageImpl; +import com.google.gcloud.RetryParams; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collections; + +public class SerializationTest { + + private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder("id1").build(); + private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder("id") + .name("name") + .labels(ImmutableMap.of("key", "value")) + .projectNumber(123L) + .state(ProjectInfo.State.ACTIVE) + .createTimeMillis(1234L) + .build(); + private static final PageImpl PAGE_RESULT = + new PageImpl<>(null, "c", Collections.singletonList(PARTIAL_PROJECT_INFO)); + private static final ResourceManager.ProjectGetOption PROJECT_GET_OPTION = + ResourceManager.ProjectGetOption.fields(ResourceManager.ProjectField.NAME); + private static final ResourceManager.ProjectListOption PROJECT_LIST_OPTION = + ResourceManager.ProjectListOption.filter("name:*"); + + @Test + public void testServiceOptions() throws Exception { + ResourceManagerOptions options = ResourceManagerOptions.builder().build(); + ResourceManagerOptions serializedCopy = serializeAndDeserialize(options); + assertEquals(options, serializedCopy); + options = options.toBuilder() + .projectId("some-unnecessary-project-ID") + .retryParams(RetryParams.defaultInstance()) + .build(); + serializedCopy = serializeAndDeserialize(options); + assertEquals(options, serializedCopy); + } + + @Test + public void testModelAndRequests() throws Exception { + Serializable[] objects = {PARTIAL_PROJECT_INFO, FULL_PROJECT_INFO, PAGE_RESULT, + PROJECT_GET_OPTION, PROJECT_LIST_OPTION}; + for (Serializable obj : objects) { + Object copy = serializeAndDeserialize(obj); + assertEquals(obj, obj); + assertEquals(obj, copy); + assertNotSame(obj, copy); + assertEquals(copy, copy); + } + } + + @SuppressWarnings("unchecked") + private T serializeAndDeserialize(T obj) throws IOException, ClassNotFoundException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream output = new ObjectOutputStream(bytes)) { + output.writeObject(obj); + } + try (ObjectInputStream input = + new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) { + return (T) input.readObject(); + } + } +} diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java b/gcloud-java-storage/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java index 5e39bce5ed94..29fdc651af9f 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java @@ -479,8 +479,8 @@ public Tuple read(StorageObject from, Map options, lo } @Override - public void write(String uploadId, byte[] toWrite, int toWriteOffset, StorageObject dest, - long destOffset, int length, boolean last) throws StorageException { + public void write(String uploadId, byte[] toWrite, int toWriteOffset, long destOffset, int length, + boolean last) throws StorageException { try { GenericUrl url = new GenericUrl(uploadId); HttpRequest httpRequest = storage.getRequestFactory().buildPutRequest(url, @@ -571,7 +571,7 @@ private RewriteResponse rewrite(RewriteRequest req, String token) throws Storage try { Long maxBytesRewrittenPerCall = req.megabytesRewrittenPerCall != null ? req.megabytesRewrittenPerCall * MEGABYTE : null; - com.google.api.services.storage.model.RewriteResponse rewriteReponse = storage.objects() + com.google.api.services.storage.model.RewriteResponse rewriteResponse = storage.objects() .rewrite(req.source.getBucket(), req.source.getName(), req.target.getBucket(), req.target.getName(), req.target.getContentType() != null ? req.target : null) .setSourceGeneration(req.source.getGeneration()) @@ -590,11 +590,11 @@ private RewriteResponse rewrite(RewriteRequest req, String token) throws Storage .execute(); return new RewriteResponse( req, - rewriteReponse.getResource(), - rewriteReponse.getObjectSize().longValue(), - rewriteReponse.getDone(), - rewriteReponse.getRewriteToken(), - rewriteReponse.getTotalBytesRewritten().longValue()); + rewriteResponse.getResource(), + rewriteResponse.getObjectSize().longValue(), + rewriteResponse.getDone(), + rewriteResponse.getRewriteToken(), + rewriteResponse.getTotalBytesRewritten().longValue()); } catch (IOException ex) { throw translate(ex); } diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/spi/StorageRpc.java b/gcloud-java-storage/src/main/java/com/google/gcloud/spi/StorageRpc.java index c5fd1b3e2250..e15a27114810 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/spi/StorageRpc.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/spi/StorageRpc.java @@ -264,8 +264,8 @@ Tuple read(StorageObject from, Map options, long posi String open(StorageObject object, Map options) throws StorageException; - void write(String uploadId, byte[] toWrite, int toWriteOffset, StorageObject dest, - long destOffset, int length, boolean last) throws StorageException; + void write(String uploadId, byte[] toWrite, int toWriteOffset, long destOffset, int length, + boolean last) throws StorageException; RewriteResponse openRewrite(RewriteRequest rewriteRequest) throws StorageException; diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Acl.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Acl.java index fd75e10d92fa..0eca39f3b3e5 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Acl.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Acl.java @@ -73,14 +73,14 @@ protected String value() { } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object obj) { + if (this == obj) { return true; } - if (o == null || getClass() != o.getClass()) { + if (obj == null || getClass() != obj.getClass()) { return false; } - Entity entity = (Entity) o; + Entity entity = (Entity) obj; return Objects.equals(type, entity.type) && Objects.equals(value, entity.value); } @@ -226,7 +226,7 @@ public static final class Project extends Entity { private static final long serialVersionUID = 7933776866530023027L; - private final ProjectRole pRole; + private final ProjectRole projectRole; private final String projectId; public enum ProjectRole { @@ -236,12 +236,12 @@ public enum ProjectRole { /** * Creates a project entity. * - * @param pRole a role in the project, used to select project's teams + * @param projectRole a role in the project, used to select project's teams * @param projectId id of the project */ - public Project(ProjectRole pRole, String projectId) { - super(Type.PROJECT, pRole.name().toLowerCase() + "-" + projectId); - this.pRole = pRole; + public Project(ProjectRole projectRole, String projectId) { + super(Type.PROJECT, projectRole.name().toLowerCase() + "-" + projectId); + this.projectRole = projectRole; this.projectId = projectId; } @@ -249,7 +249,7 @@ public Project(ProjectRole pRole, String projectId) { * Returns the role in the project for this entity. */ public ProjectRole projectRole() { - return pRole; + return projectRole; } /** @@ -275,7 +275,7 @@ String toPb() { } /** - * Creats an ACL object. + * Creates an ACL object. * * @param entity the entity for this ACL object * @param role the role to associate to the {@code entity} object diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BatchResponse.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BatchResponse.java index 9ac799e74a15..98e7ce09cef0 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BatchResponse.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BatchResponse.java @@ -113,7 +113,7 @@ static Result empty() { } } - public BatchResponse(List> deleteResult, List> updateResult, + BatchResponse(List> deleteResult, List> updateResult, List> getResult) { this.deleteResult = ImmutableList.copyOf(deleteResult); this.updateResult = ImmutableList.copyOf(updateResult); diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java index 6bbc3843f97e..c39a0aa73871 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java @@ -39,8 +39,7 @@ /** * A Google cloud storage object. * - *

- * Objects of this class are immutable. Operations that modify the blob like {@link #update} and + *

Objects of this class are immutable. Operations that modify the blob like {@link #update} and * {@link #copyTo} return a new object. To get a {@code Blob} object with the most recent * information use {@link #reload}. *

@@ -239,13 +238,13 @@ public Blob reload(BlobSourceOption... options) { * made on the metadata generation of the current blob. If you want to update the information only * if the current blob metadata are at their latest version use the {@code metagenerationMatch} * option: {@code blob.update(newInfo, BlobTargetOption.metagenerationMatch())}. - *

- * Original metadata are merged with metadata in the provided {@code blobInfo}. To replace + * + *

Original metadata are merged with metadata in the provided {@code blobInfo}. To replace * metadata instead you first have to unset them. Unsetting metadata can be done by setting the * provided {@code blobInfo}'s metadata to {@code null}. *

- *

- * Example usage of replacing blob's metadata: + * + *

Example usage of replacing blob's metadata: *

    {@code blob.update(blob.info().toBuilder().metadata(null).build());}
    *    {@code blob.update(blob.info().toBuilder().metadata(newMetadata).build());}
    * 
@@ -261,6 +260,17 @@ public Blob update(BlobInfo blobInfo, BlobTargetOption... options) { return new Blob(storage, storage.update(blobInfo, options)); } + /** + * Deletes this blob. + * + * @param options blob delete options + * @return {@code true} if blob was deleted, {@code false} if it was not found + * @throws StorageException upon failure + */ + public boolean delete(BlobSourceOption... options) { + return storage.delete(info.blobId(), toSourceOptions(info, options)); + } + /** * Sends a copy request for the current blob to the target blob. Possibly also some of the * metadata are copied (e.g. content-type). @@ -277,17 +287,6 @@ public CopyWriter copyTo(BlobId targetBlob, BlobSourceOption... options) { return storage.copy(copyRequest); } - /** - * Deletes this blob. - * - * @param options blob delete options - * @return {@code true} if blob was deleted, {@code false} if it was not found - * @throws StorageException upon failure - */ - public boolean delete(BlobSourceOption... options) { - return storage.delete(info.blobId(), toSourceOptions(info, options)); - } - /** * Sends a copy request for the current blob to the target bucket, preserving its name. Possibly * copying also some of the metadata (e.g. content-type). @@ -381,8 +380,8 @@ public static List get(final Storage storage, BlobId... blobs) { return Collections.unmodifiableList(Lists.transform(storage.get(blobs), new Function() { @Override - public Blob apply(BlobInfo f) { - return f != null ? new Blob(storage, f) : null; + public Blob apply(BlobInfo blobInfo) { + return blobInfo != null ? new Blob(storage, blobInfo) : null; } })); } @@ -410,8 +409,8 @@ public static List update(final Storage storage, BlobInfo... infos) { return Collections.unmodifiableList(Lists.transform(storage.update(infos), new Function() { @Override - public Blob apply(BlobInfo f) { - return f != null ? new Blob(storage, f) : null; + public Blob apply(BlobInfo blobInfo) { + return blobInfo != null ? new Blob(storage, blobInfo) : null; } })); } diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java index 9d1fd4f5e25c..b27d00d68a16 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java @@ -277,7 +277,7 @@ Builder mediaLink(String mediaLink) { */ public Builder metadata(Map metadata) { this.metadata = metadata != null - ? new HashMap(metadata) : Data.nullOf(ImmutableEmptyMap.class); + ? new HashMap<>(metadata) : Data.>nullOf(ImmutableEmptyMap.class); return this; } @@ -576,8 +576,9 @@ public ObjectAccessControl apply(Acl acl) { Map pbMetadata = metadata; if (metadata != null && !Data.isNull(metadata)) { pbMetadata = Maps.newHashMapWithExpectedSize(metadata.size()); - for (String key : metadata.keySet()) { - pbMetadata.put(key, firstNonNull(metadata.get(key), Data.nullOf(String.class))); + for (Map.Entry entry : metadata.entrySet()) { + pbMetadata.put(entry.getKey(), + firstNonNull(entry.getValue(), Data.nullOf(String.class))); } } storageObject.setMetadata(pbMetadata); diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobReadChannel.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobReadChannel.java index 54d39649cb70..106d18466dac 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobReadChannel.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobReadChannel.java @@ -26,8 +26,10 @@ /** * A channel for reading data from a Google Cloud Storage object. * - * Implementations of this class may buffer data internally to reduce remote calls. This interface - * implements {@link Restorable} to allow saving the reader's state to continue reading afterwards. + *

Implementations of this class may buffer data internally to reduce remote calls. This + * interface implements {@link Restorable} to allow saving the reader's state to continue reading + * afterwards. + *

*/ public interface BlobReadChannel extends ReadableByteChannel, Closeable, Restorable { diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobWriteChannel.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobWriteChannel.java index fe9164532120..9682c6345659 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobWriteChannel.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobWriteChannel.java @@ -25,9 +25,10 @@ /** * A channel for writing data to a Google Cloud Storage object. * - * Implementations of this class may further buffer data internally to reduce remote calls. Written - * data will only be visible after calling {@link #close()}. This interface implements + *

Implementations of this class may further buffer data internally to reduce remote calls. + * Written data will only be visible after calling {@link #close()}. This interface implements * {@link Restorable} to allow saving the writer's state to continue writing afterwards. + *

*/ public interface BlobWriteChannel extends WritableByteChannel, Closeable, Restorable { diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobWriteChannelImpl.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobWriteChannelImpl.java index 95656985043f..acde4178533c 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobWriteChannelImpl.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobWriteChannelImpl.java @@ -91,7 +91,7 @@ private void flush() { runWithRetries(callable(new Runnable() { @Override public void run() { - storageRpc.write(uploadId, buffer, 0, storageObject, position, length, false); + storageRpc.write(uploadId, buffer, 0, position, length, false); } }), options.retryParams(), StorageImpl.EXCEPTION_HANDLER); } catch (RetryHelper.RetryHelperException e) { @@ -139,7 +139,7 @@ public void close() throws IOException { runWithRetries(callable(new Runnable() { @Override public void run() { - storageRpc.write(uploadId, buffer, 0, storageObject, position, limit, true); + storageRpc.write(uploadId, buffer, 0, position, limit, true); } }), options.retryParams(), StorageImpl.EXCEPTION_HANDLER); } catch (RetryHelper.RetryHelperException e) { diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java index 38a767508356..8a90de143100 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java @@ -46,8 +46,7 @@ /** * A Google cloud storage bucket. * - *

- * Objects of this class are immutable. Operations that modify the bucket like {@link #update} + *

Objects of this class are immutable. Operations that modify the bucket like {@link #update} * return a new object. To get a {@code Bucket} object with the most recent information use * {@link #reload}. *

@@ -72,7 +71,7 @@ private static class BlobPageFetcher implements PageImpl.NextPageFetcher { @Override public Page nextPage() { Page nextInfoPage = infoPage.nextPage(); - return new PageImpl(new BlobPageFetcher(options, nextInfoPage), + return new PageImpl<>(new BlobPageFetcher(options, nextInfoPage), nextInfoPage.nextPageCursor(), new LazyBlobIterable(options, nextInfoPage.values())); } } diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/CopyWriter.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/CopyWriter.java index 5f2632b2acde..1e5427a847d4 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/CopyWriter.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/CopyWriter.java @@ -55,8 +55,8 @@ public class CopyWriter implements Restorable { /** * Returns the updated information for the written blob. Calling this method when {@code isDone()} * is {@code false} will block until all pending chunks are copied. - *

- * This method has the same effect of doing: + * + *

This method has the same effect of doing: *

    {@code while (!copyWriter.isDone()) {
    *        copyWriter.copyChunk();
    *    }}
diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Option.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Option.java
index 798db688c8ec..2ec8426bfa9f 100644
--- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Option.java
+++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Option.java
@@ -25,7 +25,7 @@
 import java.util.Objects;
 
 /**
- * Base class for Storage operation option
+ * Base class for Storage operation option.
  */
 class Option implements Serializable {
 
diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java
index 23c3c19a6676..a6c04e45e6c6 100644
--- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java
+++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java
@@ -50,7 +50,7 @@
  */
 public interface Storage extends Service {
 
-  public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
+  String DEFAULT_CONTENT_TYPE = "application/octet-stream";
 
   enum PredefinedAcl {
     AUTHENTICATED_READ("authenticatedRead"),
@@ -488,6 +488,14 @@ public static BlobSourceOption generationMatch() {
       return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_MATCH, null);
     }
 
+    /**
+     * Returns an option for blob's data generation match. If this option is used the request will
+     * fail if blob's generation does not match the provided value.
+     */
+    public static BlobSourceOption generationMatch(long generation) {
+      return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_MATCH, generation);
+    }
+
     /**
      * Returns an option for blob's data generation mismatch. If this option is used the request
      * will fail if blob's generation matches. The generation value to compare with the actual
@@ -499,10 +507,6 @@ public static BlobSourceOption generationNotMatch() {
       return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_NOT_MATCH, null);
     }
 
-    public static BlobSourceOption generationMatch(long generation) {
-      return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_MATCH, generation);
-    }
-
     /**
      * Returns an option for blob's data generation mismatch. If this option is used the request
      * will fail if blob's generation matches the provided value.
@@ -554,6 +558,14 @@ public static BlobGetOption generationMatch() {
       return new BlobGetOption(StorageRpc.Option.IF_GENERATION_MATCH, (Long) null);
     }
 
+    /**
+     * Returns an option for blob's data generation match. If this option is used the request will
+     * fail if blob's generation does not match the provided value.
+     */
+    public static BlobGetOption generationMatch(long generation) {
+      return new BlobGetOption(StorageRpc.Option.IF_GENERATION_MATCH, generation);
+    }
+
     /**
      * Returns an option for blob's data generation mismatch. If this option is used the request
      * will fail if blob's generation matches. The generation value to compare with the actual
@@ -565,10 +577,6 @@ public static BlobGetOption generationNotMatch() {
       return new BlobGetOption(StorageRpc.Option.IF_GENERATION_NOT_MATCH, (Long) null);
     }
 
-    public static BlobGetOption generationMatch(long generation) {
-      return new BlobGetOption(StorageRpc.Option.IF_GENERATION_MATCH, generation);
-    }
-
     /**
      * Returns an option for blob's data generation mismatch. If this option is used the request
      * will fail if blob's generation matches the provided value.
@@ -1287,8 +1295,8 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx
    * Update blob information. Original metadata are merged with metadata in the provided
    * {@code blobInfo}. To replace metadata instead you first have to unset them. Unsetting metadata
    * can be done by setting the provided {@code blobInfo}'s metadata to {@code null}.
-   * 

- * Example usage of replacing blob's metadata: + * + *

Example usage of replacing blob's metadata: *

    {@code service.update(BlobInfo.builder("bucket", "name").metadata(null).build());}
    *    {@code service.update(BlobInfo.builder("bucket", "name").metadata(newMetadata).build());}
    * 
@@ -1302,8 +1310,8 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * Update blob information. Original metadata are merged with metadata in the provided * {@code blobInfo}. To replace metadata instead you first have to unset them. Unsetting metadata * can be done by setting the provided {@code blobInfo}'s metadata to {@code null}. - *

- * Example usage of replacing blob's metadata: + * + *

Example usage of replacing blob's metadata: *

    {@code service.update(BlobInfo.builder("bucket", "name").metadata(null).build());}
    *    {@code service.update(BlobInfo.builder("bucket", "name").metadata(newMetadata).build());}
    * 
@@ -1360,8 +1368,8 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * returns, regardless of the {@link CopyRequest#megabytesCopiedPerChunk} parameter. * If source and destination have different location or storage class {@link CopyWriter#result()} * might issue multiple RPC calls depending on blob's size. - *

- * Example usage of copy: + * + *

Example usage of copy: *

    {@code BlobInfo blob = service.copy(copyRequest).result();}
    * 
* To explicitly issue chunk copy requests use {@link CopyWriter#copyChunk()} instead: @@ -1449,8 +1457,8 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * is only valid within a certain time period. * This is particularly useful if you don't want publicly * accessible blobs, but don't want to require users to explicitly log in. - *

- * Example usage of creating a signed URL that is valid for 2 weeks: + * + *

Example usage of creating a signed URL that is valid for 2 weeks: *

   {@code
    *     service.signUrl(BlobInfo.builder("bucket", "name").build(), 14, TimeUnit.DAYS);
    * }
diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java index d1535c92dfdb..85e0b02025af 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java @@ -269,6 +269,11 @@ public Page list(BucketListOption... options) { return listBuckets(options(), optionMap(options)); } + @Override + public Page list(final String bucket, BlobListOption... options) { + return listBlobs(bucket, options(), optionMap(options)); + } + private static Page listBuckets(final StorageOptions serviceOptions, final Map optionsMap) { try { @@ -295,11 +300,6 @@ public BucketInfo apply(com.google.api.services.storage.model.Bucket bucketPb) { } } - @Override - public Page list(final String bucket, BlobListOption... options) { - return listBlobs(bucket, options(), optionMap(options)); - } - private static Page listBlobs(final String bucket, final StorageOptions serviceOptions, final Map optionsMap) { try { @@ -554,8 +554,6 @@ private BlobWriteChannel writer(BlobInfo blobInfo, BlobTargetOption... options) @Override public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options) { - long expiration = TimeUnit.SECONDS.convert( - options().clock().millis() + unit.toMillis(duration), TimeUnit.MILLISECONDS); EnumMap optionMap = Maps.newEnumMap(SignUrlOption.Option.class); for (SignUrlOption option : options) { optionMap.put(option.option(), option.value()); @@ -588,6 +586,8 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio stBuilder.append(blobInfo.contentType()); } stBuilder.append('\n'); + long expiration = TimeUnit.SECONDS.convert( + options().clock().millis() + unit.toMillis(duration), TimeUnit.MILLISECONDS); stBuilder.append(expiration).append('\n'); StringBuilder path = new StringBuilder(); if (!blobInfo.bucket().startsWith("/")) { @@ -606,9 +606,9 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio Signature signer = Signature.getInstance("SHA256withRSA"); signer.initSign(cred.getPrivateKey()); signer.update(stBuilder.toString().getBytes(UTF_8)); + stBuilder = new StringBuilder("https://storage.googleapis.com").append(path); String signature = URLEncoder.encode(BaseEncoding.base64().encode(signer.sign()), UTF_8.name()); - stBuilder = new StringBuilder("https://storage.googleapis.com").append(path); stBuilder.append("?GoogleAccessId=").append(cred.getClientEmail()); stBuilder.append("&Expires=").append(expiration); stBuilder.append("&Signature=").append(signature); @@ -654,12 +654,29 @@ private static List transformResultList( List> results, final T errorValue) { return Lists.transform(results, new Function, T>() { @Override - public T apply(BatchResponse.Result f) { - return f.failed() ? errorValue : f.get(); + public T apply(BatchResponse.Result result) { + return result.failed() ? errorValue : result.get(); } }); } + private static void addToOptionMap(StorageRpc.Option option, T defaultValue, + Map map) { + addToOptionMap(option, option, defaultValue, map); + } + + private static void addToOptionMap(StorageRpc.Option getOption, StorageRpc.Option putOption, + T defaultValue, Map map) { + if (map.containsKey(getOption)) { + @SuppressWarnings("unchecked") + T value = (T) map.remove(getOption); + checkArgument(value != null || defaultValue != null, + "Option " + getOption.value() + " is missing a value"); + value = firstNonNull(value, defaultValue); + map.put(putOption, value); + } + } + private Map optionMap(Long generation, Long metaGeneration, Iterable options) { return optionMap(generation, metaGeneration, options, false); @@ -691,23 +708,6 @@ public T apply(BatchResponse.Result f) { return ImmutableMap.copyOf(temp); } - private static void addToOptionMap(StorageRpc.Option option, T defaultValue, - Map map) { - addToOptionMap(option, option, defaultValue, map); - } - - private static void addToOptionMap(StorageRpc.Option getOption, StorageRpc.Option putOption, - T defaultValue, Map map) { - if (map.containsKey(getOption)) { - @SuppressWarnings("unchecked") - T value = (T) map.remove(getOption); - checkArgument(value != null || defaultValue != null, - "Option " + getOption.value() + " is missing a value"); - value = firstNonNull(value, defaultValue); - map.put(putOption, value); - } - } - private Map optionMap(Option... options) { return optionMap(null, null, Arrays.asList(options)); } diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java index 137afd38b6ae..a55b23c3666c 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java @@ -34,7 +34,7 @@ * channel.close(); * }}
* - * When using gcloud-java from outside of App/Compute Engine, you have to When using gcloud-java from outside of App/Compute Engine, you have to specify a * project ID and * provide diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java index f5cdae83f999..77cb5661a614 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java @@ -23,8 +23,6 @@ import com.google.gcloud.storage.StorageException; import com.google.gcloud.storage.StorageOptions; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.UUID; @@ -39,7 +37,14 @@ import java.util.logging.Logger; /** - * Utility to create a remote storage configuration for testing + * Utility to create a remote storage configuration for testing. Storage options can be obtained via + * the {@link #options()} method. Returned options have custom {@link StorageOptions#retryParams()}: + * {@link RetryParams#retryMaxAttempts()} is {@code 10}, {@link RetryParams#retryMinAttempts()} is + * {@code 6}, {@link RetryParams#maxRetryDelayMillis()} is {@code 30000}, + * {@link RetryParams#totalRetryPeriodMillis()} is {@code 120000} and + * {@link RetryParams#initialRetryDelayMillis()} is {@code 250}. + * {@link StorageOptions#connectTimeout()} and {@link StorageOptions#readTimeout()} are both set + * to {@code 60000}. */ public class RemoteGcsHelper { @@ -118,28 +123,6 @@ public static RemoteGcsHelper create(String projectId, InputStream keyStream) } } - /** - * Creates a {@code RemoteGcsHelper} object for the given project id and JSON key path. - * - * @param projectId id of the project to be used for running the tests - * @param keyPath path to the JSON key to be used for running the tests - * @return A {@code RemoteGcsHelper} object for the provided options. - * @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if the file - * pointed by {@code keyPath} does not exist - */ - public static RemoteGcsHelper create(String projectId, String keyPath) - throws GcsHelperException { - try { - InputStream keyFileStream = new FileInputStream(keyPath); - return create(projectId, keyFileStream); - } catch (FileNotFoundException ex) { - if (log.isLoggable(Level.WARNING)) { - log.log(Level.WARNING, ex.getMessage()); - } - throw GcsHelperException.translate(ex); - } - } - /** * Creates a {@code RemoteGcsHelper} object using default project id and authentication * credentials. diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/package-info.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/package-info.java index 82b3578284dc..8afdd8a9660d 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/package-info.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/package-info.java @@ -18,9 +18,10 @@ * A testing helper for Google Cloud Storage. * *

A simple usage example: + * *

Before the test: *

 {@code
- * RemoteGcsHelper gcsHelper = RemoteGcsHelper.create(PROJECT_ID, "/path/to/JSON/key.json");
+ * RemoteGcsHelper gcsHelper = RemoteGcsHelper.create();
  * Storage storage = gcsHelper.options().service();
  * String bucket = RemoteGcsHelper.generateBucketName();
  * storage.create(BucketInfo.of(bucket));
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java
index 3998ae554327..4f5eb4022744 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertArrayEquals;
 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.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -100,7 +101,7 @@ public void testReload() throws Exception {
     expect(storage.get(BLOB_INFO.blobId(), new Storage.BlobGetOption[0])).andReturn(updatedInfo);
     replay(storage);
     Blob updatedBlob = blob.reload();
-    assertSame(storage, blob.storage());
+    assertSame(storage, updatedBlob.storage());
     assertEquals(updatedInfo, updatedBlob.info());
   }
 
@@ -272,13 +273,13 @@ public void testDeleteNone() throws Exception {
 
   @Test
   public void testDeleteSome() throws Exception {
-    List deleleResultList = Arrays.asList(true, true, true);
-    expect(storage.delete(BLOB_ID_ARRAY)).andReturn(deleleResultList);
+    List deleteResult = Arrays.asList(true, true, true);
+    expect(storage.delete(BLOB_ID_ARRAY)).andReturn(deleteResult);
     replay(storage);
     List result = Blob.delete(storage, BLOB_ID_ARRAY);
-    assertEquals(deleleResultList.size(), result.size());
-    for (int i = 0; i < deleleResultList.size(); i++) {
-      assertEquals(deleleResultList.get(i), result.get(i));
+    assertEquals(deleteResult.size(), result.size());
+    for (int i = 0; i < deleteResult.size(); i++) {
+      assertEquals(deleteResult.get(i), result.get(i));
     }
   }
 
@@ -295,6 +296,7 @@ public void testLoadFromId() throws Exception {
     expect(storage.get(BLOB_INFO.blobId())).andReturn(BLOB_INFO);
     replay(storage);
     Blob loadedBlob = Blob.load(storage, BLOB_INFO.blobId());
+    assertNotNull(loadedBlob);
     assertEquals(BLOB_INFO, loadedBlob.info());
   }
 }
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobWriteChannelImplTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobWriteChannelImplTest.java
index c2107ff13998..952be543a913 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobWriteChannelImplTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobWriteChannelImplTest.java
@@ -102,8 +102,8 @@ public void testWriteWithoutFlush() throws IOException {
   public void testWriteWithFlush() throws IOException {
     expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
     Capture capturedBuffer = Capture.newInstance();
-    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0),
-        eq(BLOB_INFO.toPb()), eq(0L), eq(CUSTOM_CHUNK_SIZE), eq(false));
+    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L),
+        eq(CUSTOM_CHUNK_SIZE), eq(false));
     replay(storageRpcMock);
     writer = new BlobWriteChannelImpl(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
     writer.chunkSize(CUSTOM_CHUNK_SIZE);
@@ -116,9 +116,8 @@ public void testWriteWithFlush() throws IOException {
   public void testWritesAndFlush() throws IOException {
     expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
     Capture capturedBuffer = Capture.newInstance();
-    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0),
-        eq(BLOB_INFO.toPb()), eq(0L), eq(DEFAULT_CHUNK_SIZE),
-        eq(false));
+    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L),
+        eq(DEFAULT_CHUNK_SIZE), eq(false));
     replay(storageRpcMock);
     writer = new BlobWriteChannelImpl(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
     ByteBuffer[] buffers = new ByteBuffer[DEFAULT_CHUNK_SIZE / MIN_CHUNK_SIZE];
@@ -138,8 +137,7 @@ public void testWritesAndFlush() throws IOException {
   public void testCloseWithoutFlush() throws IOException {
     expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
     Capture capturedBuffer = Capture.newInstance();
-    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0),
-        eq(BLOB_INFO.toPb()), eq(0L), eq(0), eq(true));
+    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(0), eq(true));
     replay(storageRpcMock);
     writer = new BlobWriteChannelImpl(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
     assertTrue(writer.isOpen());
@@ -153,8 +151,7 @@ public void testCloseWithFlush() throws IOException {
     expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
     Capture capturedBuffer = Capture.newInstance();
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
-    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0),
-        eq(BLOB_INFO.toPb()), eq(0L), eq(MIN_CHUNK_SIZE),
+    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(MIN_CHUNK_SIZE),
         eq(true));
     replay(storageRpcMock);
     writer = new BlobWriteChannelImpl(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
@@ -170,8 +167,7 @@ public void testCloseWithFlush() throws IOException {
   public void testWriteClosed() throws IOException {
     expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
     Capture capturedBuffer = Capture.newInstance();
-    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0),
-        eq(BLOB_INFO.toPb()), eq(0L), eq(0), eq(true));
+    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(0), eq(true));
     replay(storageRpcMock);
     writer = new BlobWriteChannelImpl(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
     writer.close();
@@ -189,8 +185,7 @@ public void testSaveAndRestore() throws IOException {
     Capture capturedBuffer = Capture.newInstance(CaptureType.ALL);
     Capture capturedPosition = Capture.newInstance(CaptureType.ALL);
     storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0),
-        eq(BLOB_INFO.toPb()), captureLong(capturedPosition),
-        eq(DEFAULT_CHUNK_SIZE), eq(false));
+        captureLong(capturedPosition), eq(DEFAULT_CHUNK_SIZE), eq(false));
     expectLastCall().times(2);
     replay(storageRpcMock);
     ByteBuffer buffer1 = randomBuffer(DEFAULT_CHUNK_SIZE);
@@ -210,8 +205,7 @@ public void testSaveAndRestore() throws IOException {
   public void testSaveAndRestoreClosed() throws IOException {
     expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
     Capture capturedBuffer = Capture.newInstance();
-    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0),
-        eq(BLOB_INFO.toPb()), eq(0L), eq(0), eq(true));
+    storageRpcMock.write(eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(0), eq(true));
     replay(storageRpcMock);
     writer = new BlobWriteChannelImpl(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
     writer.close();
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java
index 596f43a3c87a..81e7a68b2465 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java
@@ -23,6 +23,7 @@
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -95,7 +96,7 @@ public void testReload() throws Exception {
     expect(storage.get(updatedInfo.name())).andReturn(updatedInfo);
     replay(storage);
     Bucket updatedBucket = bucket.reload();
-    assertSame(storage, bucket.storage());
+    assertSame(storage, updatedBucket.storage());
     assertEquals(updatedInfo, updatedBucket.info());
   }
 
@@ -219,6 +220,7 @@ public void testLoad() throws Exception {
     expect(storage.get(BUCKET_INFO.name())).andReturn(BUCKET_INFO);
     replay(storage);
     Bucket loadedBucket = Bucket.load(storage, BUCKET_INFO.name());
+    assertNotNull(loadedBucket);
     assertEquals(BUCKET_INFO, loadedBucket.info());
   }
 }
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java
index 22b0a35c0620..30ce858dc20e 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java
@@ -271,7 +271,7 @@ public void testGetBlobFailNonExistingGeneration() {
   public void testListBlobsSelectedFields() {
     String[] blobNames = {"test-list-blobs-selected-fields-blob1",
         "test-list-blobs-selected-fields-blob2"};
-    ImmutableMap metadata = ImmutableMap.of("k", "v");
+    ImmutableMap metadata = ImmutableMap.of("k", "v");
     BlobInfo blob1 = BlobInfo.builder(BUCKET, blobNames[0])
         .contentType(CONTENT_TYPE)
         .metadata(metadata)
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java
index 3c3d1aebb3df..6b67a9576dc6 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java
@@ -24,18 +24,14 @@
 import com.google.gcloud.storage.testing.RemoteGcsHelper;
 
 import org.easymock.EasyMock;
-import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.util.Iterator;
 import java.util.List;
-import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
@@ -97,18 +93,10 @@ public Iterator iterateAll() {
       return BLOB_LIST.iterator();
     }
   };
-  private static String keyPath = "/does/not/exist/key." + UUID.randomUUID().toString() + ".json";
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  @BeforeClass
-  public static void beforeClass() {
-    while (Files.exists(Paths.get(JSON_KEY))) {
-      keyPath = "/does/not/exist/key." + UUID.randomUUID().toString() + ".json";
-    }
-  }
-
   @Test
   public void testForceDelete() throws InterruptedException, ExecutionException {
     Storage storageMock = EasyMock.createMock(Storage.class);
@@ -165,11 +153,4 @@ public void testCreateFromStream() {
     assertEquals(120000, options.retryParams().totalRetryPeriodMillis());
     assertEquals(250, options.retryParams().initialRetryDelayMillis());
   }
-
-  @Test
-  public void testCreateNoKey() {
-    thrown.expect(RemoteGcsHelper.GcsHelperException.class);
-    thrown.expectMessage(keyPath + " (No such file or directory)");
-    RemoteGcsHelper.create(PROJECT_ID, keyPath);
-  }
 }
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java
index dea635c3d264..007ac4b2a15a 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java
@@ -398,8 +398,7 @@ public void testGetBucketWithOptions() {
 
   @Test
   public void testGetBucketWithSelectedFields() {
-    Capture> capturedOptions =
-        Capture.>newInstance();
+    Capture> capturedOptions = Capture.newInstance();
     EasyMock.expect(storageRpcMock.get(EasyMock.eq(BucketInfo.of(BUCKET_NAME1).toPb()),
         EasyMock.capture(capturedOptions))).andReturn(BUCKET_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
@@ -417,8 +416,7 @@ public void testGetBucketWithSelectedFields() {
 
   @Test
   public void testGetBucketWithEmptyFields() {
-    Capture> capturedOptions =
-        Capture.>newInstance();
+    Capture> capturedOptions = Capture.newInstance();
     EasyMock.expect(storageRpcMock.get(EasyMock.eq(BucketInfo.of(BUCKET_NAME1).toPb()),
         EasyMock.capture(capturedOptions))).andReturn(BUCKET_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
@@ -470,8 +468,7 @@ public void testGetBlobWithOptionsFromBlobId() {
 
   @Test
   public void testGetBlobWithSelectedFields() {
-    Capture> capturedOptions =
-        Capture.>newInstance();
+    Capture> capturedOptions = Capture.newInstance();
     EasyMock.expect(storageRpcMock.get(EasyMock.eq(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb()),
         EasyMock.capture(capturedOptions))).andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
@@ -493,8 +490,7 @@ public void testGetBlobWithSelectedFields() {
 
   @Test
   public void testGetBlobWithEmptyFields() {
-    Capture> capturedOptions =
-        Capture.>newInstance();
+    Capture> capturedOptions = Capture.newInstance();
     EasyMock.expect(storageRpcMock.get(EasyMock.eq(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb()),
         EasyMock.capture(capturedOptions))).andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
@@ -555,8 +551,7 @@ public void testListBucketsWithOptions() {
   @Test
   public void testListBucketsWithSelectedFields() {
     String cursor = "cursor";
-    Capture> capturedOptions =
-        Capture.>newInstance();
+    Capture> capturedOptions = Capture.newInstance();
     ImmutableList bucketList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple> result =
         Tuple.of(cursor, Iterables.transform(bucketList, BucketInfo.TO_PB_FUNCTION));
@@ -577,8 +572,7 @@ public void testListBucketsWithSelectedFields() {
   @Test
   public void testListBucketsWithEmptyFields() {
     String cursor = "cursor";
-    Capture> capturedOptions =
-        Capture.>newInstance();
+    Capture> capturedOptions = Capture.newInstance();
     ImmutableList bucketList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple> result =
         Tuple.of(cursor, Iterables.transform(bucketList, BucketInfo.TO_PB_FUNCTION));
@@ -638,8 +632,7 @@ public void testListBlobsWithOptions() {
   @Test
   public void testListBlobsWithSelectedFields() {
     String cursor = "cursor";
-    Capture> capturedOptions =
-        Capture.>newInstance();
+    Capture> capturedOptions = Capture.newInstance();
     ImmutableList blobList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple> result =
         Tuple.of(cursor, Iterables.transform(blobList, BlobInfo.TO_PB_FUNCTION));
@@ -668,8 +661,7 @@ public void testListBlobsWithSelectedFields() {
   @Test
   public void testListBlobsWithEmptyFields() {
     String cursor = "cursor";
-    Capture> capturedOptions =
-        Capture.>newInstance();
+    Capture> capturedOptions = Capture.newInstance();
     ImmutableList blobList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple> result =
         Tuple.of(cursor, Iterables.transform(blobList, BlobInfo.TO_PB_FUNCTION));
diff --git a/gcloud-java/pom.xml b/gcloud-java/pom.xml
index 7d8e251b54fb..655ef8f70e62 100644
--- a/gcloud-java/pom.xml
+++ b/gcloud-java/pom.xml
@@ -24,6 +24,11 @@
       gcloud-java-datastore
       ${project.version}
     
+    
+      ${project.groupId}
+      gcloud-java-resourcemanager
+      ${project.version}
+    
     
       ${project.groupId}
       gcloud-java-storage
diff --git a/pom.xml b/pom.xml
index 8aedae4ec3b3..d6c043cb23b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,7 @@
   
     gcloud-java-core
     gcloud-java-datastore
+    gcloud-java-resourcemanager
     gcloud-java-storage
     gcloud-java
     gcloud-java-examples