diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java
index eb96df0eaba9..8d707a598c32 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java
@@ -219,6 +219,62 @@ static String selector(LicenseField... fields) {
}
}
+ /**
+ * Fields of a Compute Engine Operation resource.
+ *
+ * @see
+ * GlobalOperation Resource
+ * @see
+ * RegionOperation Resource
+ * @see
+ * ZoneOperation Resource
+ */
+ enum OperationField {
+ CLIENT_OPERATION_ID("clientOperationId"),
+ CREATION_TIMESTAMP("creationTimestamp"),
+ DESCRIPTION("description"),
+ END_TIME("endTime"),
+ ERROR("error"),
+ HTTP_ERROR_MESSAGE("httpErrorMessage"),
+ HTTP_ERROR_STATUS_CODE("httpErrorStatusCode"),
+ ID("id"),
+ INSERT_TIME("insertTime"),
+ NAME("name"),
+ OPERATION_TYPE("operationType"),
+ PROGRESS("progress"),
+ REGION("region"),
+ SELF_LINK("selfLink"),
+ START_TIME("startTime"),
+ STATUS("status"),
+ STATUS_MESSAGE("statusMessage"),
+ TARGET_ID("targetId"),
+ TARGET_LINK("targetLink"),
+ USER("user"),
+ WARNINGS("warnings"),
+ ZONE("zone");
+
+ private final String selector;
+
+ OperationField(String selector) {
+ this.selector = selector;
+ }
+
+ public String selector() {
+ return selector;
+ }
+
+ static String selector(OperationField... fields) {
+ Set fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1);
+ fieldStrings.add(SELF_LINK.selector());
+ for (OperationField field : fields) {
+ fieldStrings.add(field.selector());
+ }
+ return Joiner.on(',').join(fieldStrings);
+ }
+ }
+
/**
* Base class for list filters.
*/
@@ -232,12 +288,12 @@ abstract class ListFilter implements Serializable {
enum ComparisonOperator {
/**
- * Defines an equality filter.
+ * Defines an equals filter.
*/
EQ,
/**
- * Defines an inequality filter.
+ * Defines a not-equals filter.
*/
NE
}
@@ -284,36 +340,36 @@ class DiskTypeFilter extends ListFilter {
}
/**
- * Returns an equality filter for the given field and string value. For string fields,
+ * Returns an equals filter for the given field and string value. For string fields,
* {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
* match the entire field.
*
- * @see RE2
+ * @see RE2
*/
public static DiskTypeFilter equals(DiskTypeField field, String value) {
return new DiskTypeFilter(checkNotNull(field), ComparisonOperator.EQ, checkNotNull(value));
}
/**
- * Returns an equality filter for the given field and string value. For string fields,
+ * Returns a not-equals filter for the given field and string value. For string fields,
* {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
* match the entire field.
*
- * @see RE2
+ * @see RE2
*/
public static DiskTypeFilter notEquals(DiskTypeField field, String value) {
return new DiskTypeFilter(checkNotNull(field), ComparisonOperator.NE, checkNotNull(value));
}
/**
- * Returns an equality filter for the given field and long value.
+ * Returns an equals filter for the given field and long value.
*/
public static DiskTypeFilter equals(DiskTypeField field, long value) {
return new DiskTypeFilter(checkNotNull(field), ComparisonOperator.EQ, value);
}
/**
- * Returns an inequality filter for the given field and long value.
+ * Returns a not-equals filter for the given field and long value.
*/
public static DiskTypeFilter notEquals(DiskTypeField field, long value) {
return new DiskTypeFilter(checkNotNull(field), ComparisonOperator.NE, value);
@@ -332,36 +388,36 @@ class MachineTypeFilter extends ListFilter {
}
/**
- * Returns an equality filter for the given field and string value. For string fields,
+ * Returns an equals filter for the given field and string value. For string fields,
* {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
* match the entire field.
*
- * @see RE2
+ * @see RE2
*/
public static MachineTypeFilter equals(MachineTypeField field, String value) {
return new MachineTypeFilter(checkNotNull(field), ComparisonOperator.EQ, checkNotNull(value));
}
/**
- * Returns an equality filter for the given field and string value. For string fields,
+ * Returns a not-equals filter for the given field and string value. For string fields,
* {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
* match the entire field.
*
- * @see RE2
+ * @see RE2
*/
public static MachineTypeFilter notEquals(MachineTypeField field, String value) {
return new MachineTypeFilter(checkNotNull(field), ComparisonOperator.NE, checkNotNull(value));
}
/**
- * Returns an equality filter for the given field and long value.
+ * Returns an equals filter for the given field and long value.
*/
public static MachineTypeFilter equals(MachineTypeField field, long value) {
return new MachineTypeFilter(checkNotNull(field), ComparisonOperator.EQ, value);
}
/**
- * Returns an inequality filter for the given field and long value.
+ * Returns a not-equals filter for the given field and long value.
*/
public static MachineTypeFilter notEquals(MachineTypeField field, long value) {
return new MachineTypeFilter(checkNotNull(field), ComparisonOperator.NE, value);
@@ -380,22 +436,22 @@ class RegionFilter extends ListFilter {
}
/**
- * Returns an equality filter for the given field and string value. For string fields,
+ * Returns an equals filter for the given field and string value. For string fields,
* {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
* match the entire field.
*
- * @see RE2
+ * @see RE2
*/
public static RegionFilter equals(RegionField field, String value) {
return new RegionFilter(checkNotNull(field), ComparisonOperator.EQ, value);
}
/**
- * Returns an equality filter for the given field and string value. For string fields,
+ * Returns a not-equals filter for the given field and string value. For string fields,
* {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
* match the entire field.
*
- * @see RE2
+ * @see RE2
*/
public static RegionFilter notEquals(RegionField field, String value) {
return new RegionFilter(checkNotNull(field), ComparisonOperator.NE, checkNotNull(value));
@@ -414,28 +470,76 @@ class ZoneFilter extends ListFilter {
}
/**
- * Returns an equality filter for the given field and string value. For string fields,
+ * Returns an equals filter for the given field and string value. For string fields,
* {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
* match the entire field.
*
- * @see RE2
+ * @see RE2
*/
public static ZoneFilter equals(ZoneField field, String value) {
return new ZoneFilter(checkNotNull(field), ComparisonOperator.EQ, checkNotNull(value));
}
/**
- * Returns an equality filter for the given field and string value. For string fields,
+ * Returns a not-equals filter for the given field and string value. For string fields,
* {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
* match the entire field.
*
- * @see RE2
+ * @see RE2
*/
public static ZoneFilter notEquals(ZoneField field, String value) {
return new ZoneFilter(checkNotNull(field), ComparisonOperator.NE, checkNotNull(value));
}
}
+ /**
+ * Class for filtering operation lists.
+ */
+ class OperationFilter extends ListFilter {
+
+ private static final long serialVersionUID = -3202249202748346427L;
+
+ OperationFilter(OperationField field, ComparisonOperator operator, Object value) {
+ super(field.selector(), operator, value);
+ }
+
+ /**
+ * Returns an equals filter for the given field and string value. For string fields,
+ * {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
+ * match the entire field.
+ *
+ * @see RE2
+ */
+ public static OperationFilter equals(OperationField field, String value) {
+ return new OperationFilter(checkNotNull(field), ComparisonOperator.EQ, checkNotNull(value));
+ }
+
+ /**
+ * Returns a not-equals filter for the given field and string value. For string fields,
+ * {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
+ * match the entire field.
+ *
+ * @see RE2
+ */
+ public static OperationFilter notEquals(OperationField field, String value) {
+ return new OperationFilter(checkNotNull(field), ComparisonOperator.NE, checkNotNull(value));
+ }
+
+ /**
+ * Returns an equals filter for the given field and long value.
+ */
+ public static OperationFilter equals(OperationField field, long value) {
+ return new OperationFilter(checkNotNull(field), ComparisonOperator.EQ, value);
+ }
+
+ /**
+ * Returns a not-equals filter for the given field and long value.
+ */
+ public static OperationFilter notEquals(OperationField field, long value) {
+ return new OperationFilter(checkNotNull(field), ComparisonOperator.NE, value);
+ }
+ }
+
/**
* Class for specifying disk type get options.
*/
@@ -449,8 +553,8 @@ private DiskTypeOption(ComputeRpc.Option option, Object value) {
/**
* Returns an option to specify the disk type's fields to be returned by the RPC call. If this
- * option is not provided all disk type's fields are returned. {@code DiskTypeOption.fields} can
- * be used to specify only the fields of interest. {@link DiskType#diskTypeId()} is always
+ * option is not provided, all disk type's fields are returned. {@code DiskTypeOption.fields}
+ * can be used to specify only the fields of interest. {@link DiskType#diskTypeId()} is always
* returned, even if not specified.
*/
public static DiskTypeOption fields(DiskTypeField... fields) {
@@ -470,17 +574,17 @@ private DiskTypeListOption(ComputeRpc.Option option, Object value) {
}
/**
- * Returns an option to specify a filter to the disk types being listed.
+ * Returns an option to specify a filter on the disk types being listed.
*/
public static DiskTypeListOption filter(DiskTypeFilter filter) {
return new DiskTypeListOption(ComputeRpc.Option.FILTER, filter.toPb());
}
/**
- * Returns an option to specify the maximum number of disk types to be returned.
+ * Returns an option to specify the maximum number of disk types returned per page.
*/
- public static DiskTypeListOption maxResults(long maxResults) {
- return new DiskTypeListOption(ComputeRpc.Option.MAX_RESULTS, maxResults);
+ public static DiskTypeListOption pageSize(long pageSize) {
+ return new DiskTypeListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
}
/**
@@ -492,9 +596,9 @@ public static DiskTypeListOption startPageToken(String pageToken) {
/**
* Returns an option to specify the disk type's fields to be returned by the RPC call. If this
- * option is not provided all disk type's fields are returned. {@code DiskTypeListOption.fields}
- * can be used to specify only the fields of interest. {@link DiskType#diskTypeId()} is always
- * returned, even if not specified.
+ * option is not provided, all disk type's fields are returned.
+ * {@code DiskTypeListOption.fields} can be used to specify only the fields of interest.
+ * {@link DiskType#diskTypeId()} is always returned, even if not specified.
*/
public static DiskTypeListOption fields(DiskTypeField... fields) {
StringBuilder builder = new StringBuilder();
@@ -515,17 +619,17 @@ private DiskTypeAggregatedListOption(ComputeRpc.Option option, Object value) {
}
/**
- * Returns an option to specify a filter to the disk types being listed.
+ * Returns an option to specify a filter on the disk types being listed.
*/
public static DiskTypeAggregatedListOption filter(DiskTypeFilter filter) {
return new DiskTypeAggregatedListOption(ComputeRpc.Option.FILTER, filter.toPb());
}
/**
- * Returns an option to specify the maximum number of disk types to be returned.
+ * Returns an option to specify the maximum number of disk types returned per page.
*/
- public static DiskTypeAggregatedListOption maxResults(long maxResults) {
- return new DiskTypeAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, maxResults);
+ public static DiskTypeAggregatedListOption pageSize(long pageSize) {
+ return new DiskTypeAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
}
/**
@@ -549,7 +653,7 @@ private MachineTypeOption(ComputeRpc.Option option, Object value) {
/**
* Returns an option to specify the machine type's fields to be returned by the RPC call. If
- * this option is not provided all machine type's fields are returned.
+ * this option is not provided, all machine type's fields are returned.
* {@code MachineTypeOption.fields} can be used to specify only the fields of interest.
* {@link MachineType#machineTypeId()} is always returned, even if not specified.
*/
@@ -570,17 +674,17 @@ private MachineTypeListOption(ComputeRpc.Option option, Object value) {
}
/**
- * Returns an option to specify a filter to the machine types being listed.
+ * Returns an option to specify a filter on the machine types being listed.
*/
public static MachineTypeListOption filter(MachineTypeFilter filter) {
return new MachineTypeListOption(ComputeRpc.Option.FILTER, filter.toPb());
}
/**
- * Returns an option to specify the maximum number of machine types to be returned.
+ * Returns an option to specify the maximum number of machine types returned per page.
*/
- public static MachineTypeListOption maxResults(long maxResults) {
- return new MachineTypeListOption(ComputeRpc.Option.MAX_RESULTS, maxResults);
+ public static MachineTypeListOption pageSize(long pageSize) {
+ return new MachineTypeListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
}
/**
@@ -592,7 +696,7 @@ public static MachineTypeListOption startPageToken(String pageToken) {
/**
* Returns an option to specify the machine type's fields to be returned by the RPC call. If
- * this option is not provided all machine type's fields are returned.
+ * this option is not provided, all machine type's fields are returned.
* {@code MachineTypeListOption.fields} can be used to specify only the fields of interest.
* {@link MachineType#machineTypeId()} is always returned, even if not specified.
*/
@@ -615,17 +719,17 @@ private MachineTypeAggregatedListOption(ComputeRpc.Option option, Object value)
}
/**
- * Returns an option to specify a filter to the machine types being listed.
+ * Returns an option to specify a filter on the machine types being listed.
*/
public static MachineTypeAggregatedListOption filter(MachineTypeFilter filter) {
return new MachineTypeAggregatedListOption(ComputeRpc.Option.FILTER, filter.toPb());
}
/**
- * Returns an option to specify the maximum number of machine types to be returned.
+ * Returns an option to specify the maximum number of machine types returned per page.
*/
- public static MachineTypeAggregatedListOption maxResults(long maxResults) {
- return new MachineTypeAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, maxResults);
+ public static MachineTypeAggregatedListOption pageSize(long pageSize) {
+ return new MachineTypeAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
}
/**
@@ -649,7 +753,7 @@ private RegionOption(ComputeRpc.Option option, Object value) {
/**
* Returns an option to specify the region's fields to be returned by the RPC call. If this
- * option is not provided all region's fields are returned. {@code RegionOption.fields} can be
+ * option is not provided, all region's fields are returned. {@code RegionOption.fields} can be
* used to specify only the fields of interest. {@link Region#regionId()} is always
* returned, even if not specified.
*/
@@ -670,17 +774,17 @@ private RegionListOption(ComputeRpc.Option option, Object value) {
}
/**
- * Returns an option to specify a filter to the regions being listed.
+ * Returns an option to specify a filter on the regions being listed.
*/
public static RegionListOption filter(RegionFilter filter) {
return new RegionListOption(ComputeRpc.Option.FILTER, filter.toPb());
}
/**
- * Returns an option to specify the maximum number of regions to be returned.
+ * Returns an option to specify the maximum number of regions returned per page.
*/
- public static RegionListOption maxResults(long maxResults) {
- return new RegionListOption(ComputeRpc.Option.MAX_RESULTS, maxResults);
+ public static RegionListOption pageSize(long pageSize) {
+ return new RegionListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
}
/**
@@ -692,7 +796,7 @@ public static RegionListOption startPageToken(String pageToken) {
/**
* Returns an option to specify the region's fields to be returned by the RPC call. If this
- * option is not provided all region's fields are returned. {@code RegionListOption.fields} can
+ * option is not provided, all region's fields are returned. {@code RegionListOption.fields} can
* be used to specify only the fields of interest. {@link Region#regionId()} is always
* returned, even if not specified.
*/
@@ -716,7 +820,7 @@ private ZoneOption(ComputeRpc.Option option, Object value) {
/**
* Returns an option to specify the zone's fields to be returned by the RPC call. If this option
- * is not provided all zone's fields are returned. {@code ZoneOption.fields} can be used to
+ * is not provided, all zone's fields are returned. {@code ZoneOption.fields} can be used to
* specify only the fields of interest. {@link Zone#zoneId()} is always returned, even if
* not specified.
*/
@@ -737,17 +841,17 @@ private ZoneListOption(ComputeRpc.Option option, Object value) {
}
/**
- * Returns an option to specify a filter to the zones being listed.
+ * Returns an option to specify a filter on the zones being listed.
*/
public static ZoneListOption filter(ZoneFilter filter) {
return new ZoneListOption(ComputeRpc.Option.FILTER, filter.toPb());
}
/**
- * Returns an option to specify the maximum number of zones to be returned.
+ * Returns an option to specify the maximum number of zones returned per page.
*/
- public static ZoneListOption maxResults(long maxResults) {
- return new ZoneListOption(ComputeRpc.Option.MAX_RESULTS, maxResults);
+ public static ZoneListOption pageSize(long pageSize) {
+ return new ZoneListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
}
/**
@@ -759,7 +863,7 @@ public static ZoneListOption startPageToken(String pageToken) {
/**
* Returns an option to specify the zone's fields to be returned by the RPC call. If this option
- * is not provided all zone's fields are returned. {@code ZoneListOption.fields} can be used to
+ * is not provided, all zone's fields are returned. {@code ZoneListOption.fields} can be used to
* specify only the fields of interest. {@link Zone#zoneId()} is always returned, even if
* not specified.
*/
@@ -783,15 +887,82 @@ private LicenseOption(ComputeRpc.Option option, Object value) {
/**
* Returns an option to specify the license's fields to be returned by the RPC call. If this
- * option is not provided all license's fields are returned. {@code LicenseOption.fields} can be
- * used to specify only the fields of interest. {@link License#licenseId()} is always returned,
- * even if not specified.
+ * option is not provided, all license's fields are returned. {@code LicenseOption.fields} can
+ * be used to specify only the fields of interest. {@link License#licenseId()} is always
+ * returned, even if not specified.
*/
public static LicenseOption fields(LicenseField... fields) {
return new LicenseOption(ComputeRpc.Option.FIELDS, LicenseField.selector(fields));
}
}
+ /**
+ * Class for specifying operation get options.
+ */
+ class OperationOption extends Option {
+
+ private static final long serialVersionUID = -4572636917684779912L;
+
+ private OperationOption(ComputeRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify the operation's fields to be returned by the RPC call. If this
+ * option is not provided, all operation's fields are returned. {@code OperationOption.fields}
+ * can be used to specify only the fields of interest. {@link Operation#operationId()} is
+ * always returned, even if not specified.
+ */
+ public static OperationOption fields(OperationField... fields) {
+ return new OperationOption(ComputeRpc.Option.FIELDS, OperationField.selector(fields));
+ }
+ }
+
+ /**
+ * Class for specifying operation list options.
+ */
+ class OperationListOption extends Option {
+
+ private static final long serialVersionUID = -1509532420587265823L;
+
+ private OperationListOption(ComputeRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify a filter on the operations being listed.
+ */
+ public static OperationListOption filter(OperationFilter filter) {
+ return new OperationListOption(ComputeRpc.Option.FILTER, filter.toPb());
+ }
+
+ /**
+ * Returns an option to specify the maximum number of operations returned per page.
+ */
+ public static OperationListOption pageSize(long pageSize) {
+ return new OperationListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
+ }
+
+ /**
+ * Returns an option to specify the page token from which to start listing operations.
+ */
+ public static OperationListOption startPageToken(String pageToken) {
+ return new OperationListOption(ComputeRpc.Option.PAGE_TOKEN, pageToken);
+ }
+
+ /**
+ * Returns an option to specify the operation's fields to be returned by the RPC call. If this
+ * option is not provided, all operation's fields are returned.
+ * {@code OperationListOption.fields} can be used to specify only the fields of interest.
+ * {@link Operation#operationId()} is always returned, even if not specified.
+ */
+ public static OperationListOption fields(OperationField... fields) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("items(").append(OperationField.selector(fields)).append("),nextPageToken");
+ return new OperationListOption(ComputeRpc.Option.FIELDS, builder.toString());
+ }
+ }
+
/**
* Returns the requested disk type or {@code null} if not found.
*
@@ -889,4 +1060,43 @@ public static LicenseOption fields(LicenseField... fields) {
* @throws ComputeException upon failure
*/
License getLicense(LicenseId license, LicenseOption... options);
+
+ /**
+ * Returns the requested operation or {@code null} if not found.
+ *
+ * @throws ComputeException upon failure
+ */
+ Operation get(OperationId operationId, OperationOption... options);
+
+ /**
+ * Lists the global operations.
+ *
+ * @throws ComputeException upon failure
+ */
+ Page listGlobalOperations(OperationListOption... options);
+
+ /**
+ * Lists the operations for the provided region. These are operations that create/modify/delete
+ * resources that live in a region (e.g. subnetworks).
+ *
+ * @throws ComputeException upon failure
+ */
+ Page listRegionOperations(String region, OperationListOption... options);
+
+ /**
+ * Lists the operations for the provided zone. These are operations that create/modify/delete
+ * resources that live in a zone (e.g. instances).
+ *
+ * @throws ComputeException upon failure
+ */
+ Page listZoneOperations(String zone, OperationListOption... options);
+
+ /**
+ * Deletes the requested operation. Delete is only possible for operations that have completed
+ * their execution. Any attempt to delete a running operation will fail.
+ *
+ * @return {@code true} if operation was deleted, {@code false} if it was not found
+ * @throws ComputeException upon failure
+ */
+ boolean delete(OperationId operation);
}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java
index 2087e570a349..5e6c4bc869a6 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java
@@ -35,6 +35,25 @@
final class ComputeImpl extends BaseService implements Compute {
+ private static class GlobalOperationPageFetcher implements NextPageFetcher {
+
+ private static final long serialVersionUID = -2488912172182315364L;
+ private final Map requestOptions;
+ private final ComputeOptions serviceOptions;
+
+ GlobalOperationPageFetcher(ComputeOptions serviceOptions, String cursor,
+ Map optionMap) {
+ this.requestOptions =
+ PageImpl.nextRequestOptions(ComputeRpc.Option.PAGE_TOKEN, cursor, optionMap);
+ this.serviceOptions = serviceOptions;
+ }
+
+ @Override
+ public Page nextPage() {
+ return listGlobalOperations(serviceOptions, requestOptions);
+ }
+ }
+
private static class DiskTypePageFetcher implements NextPageFetcher {
private static final long serialVersionUID = -5253916264932522976L;
@@ -153,6 +172,48 @@ public Page nextPage() {
}
}
+ private static class RegionOperationPageFetcher implements NextPageFetcher {
+
+ private static final long serialVersionUID = 4111705358926164078L;
+ private final Map requestOptions;
+ private final ComputeOptions serviceOptions;
+ private final String region;
+
+ RegionOperationPageFetcher(String region, ComputeOptions serviceOptions, String cursor,
+ Map optionMap) {
+ this.requestOptions =
+ PageImpl.nextRequestOptions(ComputeRpc.Option.PAGE_TOKEN, cursor, optionMap);
+ this.serviceOptions = serviceOptions;
+ this.region = region;
+ }
+
+ @Override
+ public Page nextPage() {
+ return listRegionOperations(region, serviceOptions, requestOptions);
+ }
+ }
+
+ private static class ZoneOperationPageFetcher implements NextPageFetcher {
+
+ private static final long serialVersionUID = -9012504536518197793L;
+ private final Map requestOptions;
+ private final ComputeOptions serviceOptions;
+ private final String zone;
+
+ ZoneOperationPageFetcher(String zone, ComputeOptions serviceOptions, String cursor,
+ Map optionMap) {
+ this.requestOptions =
+ PageImpl.nextRequestOptions(ComputeRpc.Option.PAGE_TOKEN, cursor, optionMap);
+ this.serviceOptions = serviceOptions;
+ this.zone = zone;
+ }
+
+ @Override
+ public Page nextPage() {
+ return listZoneOperations(zone, serviceOptions, requestOptions);
+ }
+ }
+
private final ComputeRpc computeRpc;
ComputeImpl(ComputeOptions options) {
@@ -464,6 +525,165 @@ public com.google.api.services.compute.model.License call() {
}
}
+ @Override
+ public Operation get(final OperationId operationId, OperationOption... options) {
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.compute.model.Operation answer =
+ runWithRetries(new Callable() {
+ @Override
+ public com.google.api.services.compute.model.Operation call() {
+ switch (operationId.type()) {
+ case REGION:
+ RegionOperationId regionOperationId = (RegionOperationId) operationId;
+ return computeRpc.getRegionOperation(regionOperationId.region(),
+ regionOperationId.operation(), optionsMap);
+ case ZONE:
+ ZoneOperationId zoneOperationId = (ZoneOperationId) operationId;
+ return computeRpc.getZoneOperation(zoneOperationId.zone(),
+ zoneOperationId.operation(), optionsMap);
+ case GLOBAL:
+ return computeRpc.getGlobalOperation(operationId.operation(), optionsMap);
+ default:
+ throw new IllegalArgumentException("Unexpected operation identity type");
+ }
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : Operation.fromPb(this, answer);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Page listGlobalOperations(OperationListOption... options) {
+ return listGlobalOperations(options(), optionMap(options));
+ }
+
+ private static Page listGlobalOperations(final ComputeOptions serviceOptions,
+ final Map optionsMap) {
+ try {
+ ComputeRpc.Tuple> result =
+ runWithRetries(new Callable>>() {
+ @Override
+ public ComputeRpc.Tuple> call() {
+ return serviceOptions.rpc().listGlobalOperations(optionsMap);
+ }
+ }, serviceOptions.retryParams(), EXCEPTION_HANDLER);
+ String cursor = result.x();
+ Iterable operations = Iterables.transform(
+ result.y() == null ? ImmutableList.of()
+ : result.y(),
+ new Function() {
+ @Override
+ public Operation apply(com.google.api.services.compute.model.Operation operation) {
+ return Operation.fromPb(serviceOptions.service(), operation);
+ }
+ });
+ return new PageImpl<>(new GlobalOperationPageFetcher(serviceOptions, cursor, optionsMap),
+ cursor, operations);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Page listRegionOperations(String region, OperationListOption... options) {
+ return listRegionOperations(region, options(), optionMap(options));
+ }
+
+ private static Page listRegionOperations(final String region,
+ final ComputeOptions serviceOptions, final Map optionsMap) {
+ try {
+ ComputeRpc.Tuple> result =
+ runWithRetries(new Callable>>() {
+ @Override
+ public ComputeRpc.Tuple> call() {
+ return serviceOptions.rpc().listRegionOperations(region, optionsMap);
+ }
+ }, serviceOptions.retryParams(), EXCEPTION_HANDLER);
+ String cursor = result.x();
+ Iterable operations = Iterables.transform(
+ result.y() == null ? ImmutableList.of()
+ : result.y(),
+ new Function() {
+ @Override
+ public Operation apply(com.google.api.services.compute.model.Operation operation) {
+ return Operation.fromPb(serviceOptions.service(), operation);
+ }
+ });
+ return new PageImpl<>(new RegionOperationPageFetcher(region, serviceOptions, cursor,
+ optionsMap), cursor, operations);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Page listZoneOperations(String zone, OperationListOption... options) {
+ return listZoneOperations(zone, options(), optionMap(options));
+ }
+
+ private static Page listZoneOperations(final String zone,
+ final ComputeOptions serviceOptions, final Map optionsMap) {
+ try {
+ ComputeRpc.Tuple> result =
+ runWithRetries(new Callable>>() {
+ @Override
+ public ComputeRpc.Tuple> call() {
+ return serviceOptions.rpc().listZoneOperations(zone, optionsMap);
+ }
+ }, serviceOptions.retryParams(), EXCEPTION_HANDLER);
+ String cursor = result.x();
+ Iterable operations = Iterables.transform(
+ result.y() == null ? ImmutableList.of()
+ : result.y(),
+ new Function() {
+ @Override
+ public Operation apply(com.google.api.services.compute.model.Operation operation) {
+ return Operation.fromPb(serviceOptions.service(), operation);
+ }
+ });
+ return new PageImpl<>(new ZoneOperationPageFetcher(zone, serviceOptions, cursor, optionsMap),
+ cursor, operations);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public boolean delete(final OperationId operation) {
+ try {
+ return runWithRetries(new Callable() {
+ @Override
+ public Boolean call() {
+ switch (operation.type()) {
+ case REGION:
+ RegionOperationId regionOperationId = (RegionOperationId) operation;
+ return computeRpc.deleteRegionOperation(regionOperationId.region(),
+ regionOperationId.operation());
+ case ZONE:
+ ZoneOperationId zoneOperationId = (ZoneOperationId) operation;
+ return computeRpc.deleteZoneOperation(zoneOperationId.zone(),
+ zoneOperationId.operation());
+ case GLOBAL:
+ return computeRpc.deleteGlobalOperation(operation.operation());
+ default:
+ throw new IllegalArgumentException("Unexpected operation identity type");
+ }
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
private Map optionMap(Option... options) {
Map optionMap = Maps.newEnumMap(ComputeRpc.Option.class);
for (Option option : options) {
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/DiskType.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/DiskType.java
index ce57067786c4..0991a9d5d666 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/DiskType.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/DiskType.java
@@ -32,7 +32,7 @@
*
* @see Disk Types
*/
-public final class DiskType implements Serializable {
+public class DiskType implements Serializable {
static final Function FROM_PB_FUNCTION =
new Function() {
@@ -186,12 +186,12 @@ public String toString() {
}
@Override
- public int hashCode() {
+ public final int hashCode() {
return Objects.hash(diskTypeId);
}
@Override
- public boolean equals(Object obj) {
+ public final boolean equals(Object obj) {
return obj instanceof DiskType && Objects.equals(toPb(), ((DiskType) obj).toPb());
}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/GlobalOperationId.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/GlobalOperationId.java
new file mode 100644
index 000000000000..c12e24c903cc
--- /dev/null
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/GlobalOperationId.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2016 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.compute;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Identity for a Google Compute Engine global operation.
+ */
+public class GlobalOperationId extends ResourceId implements OperationId {
+
+ private static final String REGEX = ResourceId.REGEX + "global/operations/([^/]+)";
+ private static final Pattern PATTERN = Pattern.compile(REGEX);
+ private static final long serialVersionUID = 3945756772641577962L;
+
+ private final String operation;
+
+ private GlobalOperationId(String project, String operation) {
+ super(project);
+ this.operation = checkNotNull(operation);
+ }
+
+ @Override
+ public Type type() {
+ return Type.GLOBAL;
+ }
+
+ @Override
+ public String operation() {
+ return operation;
+ }
+
+ @Override
+ public String selfLink() {
+ return super.selfLink() + "/global/operations/" + operation;
+ }
+
+ @Override
+ MoreObjects.ToStringHelper toStringHelper() {
+ return super.toStringHelper().add("operation", operation);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(baseHashCode(), operation);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof GlobalOperationId
+ && baseEquals((GlobalOperationId) obj)
+ && Objects.equals(operation, ((GlobalOperationId) obj).operation);
+ }
+
+ @Override
+ GlobalOperationId setProjectId(String projectId) {
+ if (project() != null) {
+ return this;
+ }
+ return GlobalOperationId.of(projectId, operation);
+ }
+
+ /**
+ * Returns a global operation identity given the operation name.
+ */
+ public static GlobalOperationId of(String operation) {
+ return new GlobalOperationId(null, operation);
+ }
+
+ /**
+ * Returns a global operation identity given project and operation names.
+ */
+ public static GlobalOperationId of(String project, String operation) {
+ return new GlobalOperationId(project, operation);
+ }
+
+ /**
+ * Returns {@code true} if the provided string matches the expected format of a global operation
+ * URL. Returns {@code false} otherwise.
+ */
+ static boolean matchesUrl(String url) {
+ return PATTERN.matcher(url).matches();
+ }
+
+ static GlobalOperationId fromUrl(String url) {
+ Matcher matcher = PATTERN.matcher(url);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(url + " is not a valid global operation URL");
+ }
+ return GlobalOperationId.of(matcher.group(1), matcher.group(2));
+ }
+}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/License.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/License.java
index 2c6cb4ac9422..d69c78d0bbf4 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/License.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/License.java
@@ -29,7 +29,7 @@
*
* @see Licenses
*/
-public final class License implements Serializable {
+public class License implements Serializable {
private static final long serialVersionUID = 6907923910319640363L;
@@ -65,12 +65,12 @@ public String toString() {
}
@Override
- public int hashCode() {
+ public final int hashCode() {
return Objects.hash(licenseId);
}
@Override
- public boolean equals(Object obj) {
+ public final boolean equals(Object obj) {
return obj instanceof License && Objects.equals(toPb(), ((License) obj).toPb());
}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/MachineType.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/MachineType.java
index ee242c0d1ef0..d9c446dddf72 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/MachineType.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/MachineType.java
@@ -36,7 +36,7 @@
*
* @see Machine Types
*/
-public final class MachineType implements Serializable {
+public class MachineType implements Serializable {
static final Function
FROM_PB_FUNCTION =
@@ -242,12 +242,12 @@ public String toString() {
}
@Override
- public int hashCode() {
+ public final int hashCode() {
return Objects.hash(machineTypeId);
}
@Override
- public boolean equals(Object obj) {
+ public final boolean equals(Object obj) {
return obj instanceof MachineType && Objects.equals(toPb(), ((MachineType) obj).toPb());
}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java
new file mode 100644
index 000000000000..7b8a888cf40c
--- /dev/null
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java
@@ -0,0 +1,806 @@
+/*
+ * Copyright 2016 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.compute;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gcloud.compute.OperationId.Type.REGION;
+import static com.google.gcloud.compute.OperationId.Type.ZONE;
+
+import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Google Compute Engine operations. Operation identity can be obtained via {@link #operationId()}.
+ * {@link #operationId()} returns {@link GlobalOperationId} for global operations,
+ * {@link RegionOperationId} for region operations, and {@link ZoneOperationId} for zone operations.
+ * To get an {@code Operation} object with the most recent information, use
+ * {@link #reload(Compute.OperationOption...)}.
+ */
+public class Operation implements Serializable {
+
+ private static final long serialVersionUID = -8979001444590023899L;
+ private static final DateTimeFormatter TIMESTAMP_FORMATTER = ISODateTimeFormat.dateTime();
+
+ private transient Compute compute;
+ private final ComputeOptions options;
+ private final String id;
+ private final OperationId operationId;
+ private final Long creationTimestamp;
+ private final String clientOperationId;
+ private final String operationType;
+ private final String targetLink;
+ private final String targetId;
+ private final Status status;
+ private final String statusMessage;
+ private final String user;
+ private final Integer progress;
+ private final Long insertTime;
+ private final Long startTime;
+ private final Long endTime;
+ private final List errors;
+ private final List warnings;
+ private final Integer httpErrorStatusCode;
+ private final String httpErrorMessage;
+ private final String description;
+
+ /**
+ * Status of an operation.
+ */
+ public enum Status {
+ PENDING,
+ RUNNING,
+ DONE
+ }
+
+ /**
+ * An error that can occur during the processing of a Google Compute Engine operation.
+ */
+ public static final class OperationError implements Serializable {
+
+ static final Function FROM_PB_FUNCTION = new Function<
+ com.google.api.services.compute.model.Operation.Error.Errors, OperationError>() {
+ @Override
+ public OperationError apply(
+ com.google.api.services.compute.model.Operation.Error.Errors pb) {
+ return OperationError.fromPb(pb);
+ }
+ };
+ static final Function TO_PB_FUNCTION =
+ new Function() {
+ @Override
+ public com.google.api.services.compute.model.Operation.Error.Errors apply(
+ OperationError operation) {
+ return operation.toPb();
+ }
+ };
+
+ private static final long serialVersionUID = -1155314394806515873L;
+
+ private final String code;
+ private final String location;
+ private final String message;
+
+ OperationError(String code, String location, String message) {
+ this.code = code;
+ this.location = location;
+ this.message = message;
+ }
+
+ /**
+ * Returns an error type identifier for this error.
+ */
+ public String code() {
+ return code;
+ }
+
+ /**
+ * Returns the field in the request which caused the error. This value is optional.
+ */
+ public String location() {
+ return location;
+ }
+
+ /**
+ * Returns an optional, human-readable error message.
+ */
+ public String message() {
+ return message;
+ }
+
+ com.google.api.services.compute.model.Operation.Error.Errors toPb() {
+ return new com.google.api.services.compute.model.Operation.Error.Errors()
+ .setCode(code)
+ .setLocation(location)
+ .setMessage(message);
+ }
+
+ static OperationError fromPb(
+ com.google.api.services.compute.model.Operation.Error.Errors errorPb) {
+ return new OperationError(errorPb.getCode(), errorPb.getLocation(), errorPb.getMessage());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof OperationError
+ && Objects.equals(code, ((OperationError) obj).code)
+ && Objects.equals(message, ((OperationError) obj).message)
+ && Objects.equals(location, ((OperationError) obj).location);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("code", code)
+ .add("location", location)
+ .add("message", message)
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(code, location, message);
+ }
+ }
+
+ /**
+ * A warning message that is generated during the processing of a Google Compute Engine operation.
+ */
+ public static final class OperationWarning implements Serializable {
+
+ static final
+ Function
+ FROM_PB_FUNCTION =
+ new Function() {
+ @Override
+ public OperationWarning apply(
+ com.google.api.services.compute.model.Operation.Warnings pb) {
+ return OperationWarning.fromPb(pb);
+ }
+ };
+ static final
+ Function
+ TO_PB_FUNCTION =
+ new Function() {
+ @Override
+ public com.google.api.services.compute.model.Operation.Warnings apply(
+ OperationWarning operation) {
+ return operation.toPb();
+ }
+ };
+
+ private static final long serialVersionUID = 4917326627380228928L;
+
+ private final String code;
+ private final String message;
+ private final Map metadata;
+
+ OperationWarning(String code, String message, Map metadata) {
+ this.code = code;
+ this.metadata = metadata != null ? ImmutableMap.copyOf(metadata) : null;
+ this.message = message;
+ }
+
+ /**
+ * Returns a warning identifier for this warning. For example, {@code NO_RESULTS_ON_PAGE} if
+ * there are no results in the response.
+ */
+ public String code() {
+ return code;
+ }
+
+ /**
+ * Returns a human-readable error message.
+ */
+ public String message() {
+ return message;
+ }
+
+ /**
+ * Returns metadata about this warning. Each key provides more detail on the warning being
+ * returned. For example, for warnings where there are no results in a list request for a
+ * particular zone, this key might be {@code scope} and the key's value might be the zone name.
+ * Other examples might be a key indicating a deprecated resource, and a suggested replacement,
+ * or a warning about invalid network settings (for example, if an instance attempts to perform
+ * IP forwarding but is not enabled for IP forwarding).
+ */
+ public Map metadata() {
+ return metadata;
+ }
+
+ com.google.api.services.compute.model.Operation.Warnings toPb() {
+ com.google.api.services.compute.model.Operation.Warnings warningPb =
+ new com.google.api.services.compute.model.Operation.Warnings()
+ .setCode(code)
+ .setMessage(message);
+ if (this.metadata != null) {
+ List metadataPb =
+ Lists.newArrayListWithCapacity(metadata.size());
+ for (Map.Entry entry : metadata.entrySet()) {
+ metadataPb.add(new com.google.api.services.compute.model.Operation.Warnings.Data()
+ .setKey(entry.getKey()).setValue(entry.getValue()));
+ }
+ warningPb.setData(metadataPb);
+ }
+ return warningPb;
+ }
+
+ static OperationWarning fromPb(
+ com.google.api.services.compute.model.Operation.Warnings warningPb) {
+ Map metadata = null;
+ if (warningPb.getData() != null) {
+ metadata = Maps.newHashMapWithExpectedSize(warningPb.getData().size());
+ for (com.google.api.services.compute.model.Operation.Warnings.Data data
+ : warningPb.getData()) {
+ metadata.put(data.getKey(), data.getValue());
+ }
+ }
+ return new OperationWarning(warningPb.getCode(), warningPb.getMessage(), metadata);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof OperationWarning
+ && Objects.equals(code, ((OperationWarning) obj).code)
+ && Objects.equals(message, ((OperationWarning) obj).message)
+ && Objects.equals(metadata, ((OperationWarning) obj).metadata);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("code", code)
+ .add("message", message)
+ .add("metadata", metadata)
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(code, message, metadata);
+ }
+ }
+
+ static final class Builder {
+
+ private Compute compute;
+ private String id;
+ private Long creationTimestamp;
+ private OperationId operationId;
+ private String clientOperationId;
+ private String operationType;
+ private String targetLink;
+ private String targetId;
+ private Status status;
+ private String statusMessage;
+ private String user;
+ private Integer progress;
+ private Long insertTime;
+ private Long startTime;
+ private Long endTime;
+ private List errors;
+ private List warnings;
+ private Integer httpErrorStatusCode;
+ private String httpErrorMessage;
+ private String description;
+
+ Builder(Compute compute) {
+ this.compute = compute;
+ }
+
+ Builder(Compute compute, com.google.api.services.compute.model.Operation operationPb) {
+ this.compute = compute;
+ if (operationPb.getId() != null) {
+ id = operationPb.getId().toString();
+ }
+ if (operationPb.getCreationTimestamp() != null) {
+ creationTimestamp = TIMESTAMP_FORMATTER.parseMillis(operationPb.getCreationTimestamp());
+ }
+ if (RegionOperationId.matchesUrl(operationPb.getSelfLink())) {
+ operationId = RegionOperationId.fromUrl(operationPb.getSelfLink());
+ } else if (ZoneOperationId.matchesUrl(operationPb.getSelfLink())) {
+ operationId = ZoneOperationId.fromUrl(operationPb.getSelfLink());
+ } else {
+ operationId = GlobalOperationId.fromUrl(operationPb.getSelfLink());
+ }
+ clientOperationId = operationPb.getClientOperationId();
+ operationType = operationPb.getOperationType();
+ targetLink = operationPb.getTargetLink();
+ if (operationPb.getTargetId() != null) {
+ targetId = operationPb.getTargetId().toString();
+ }
+ if (operationPb.getStatus() != null) {
+ status = Status.valueOf(operationPb.getStatus());
+ }
+ statusMessage = operationPb.getStatusMessage();
+ user = operationPb.getUser();
+ progress = operationPb.getProgress();
+ if (operationPb.getInsertTime() != null) {
+ insertTime = TIMESTAMP_FORMATTER.parseMillis(operationPb.getInsertTime());
+ }
+ if (operationPb.getStartTime() != null) {
+ startTime = TIMESTAMP_FORMATTER.parseMillis(operationPb.getStartTime());
+ }
+ if (operationPb.getEndTime() != null) {
+ endTime = TIMESTAMP_FORMATTER.parseMillis(operationPb.getEndTime());
+ }
+ if (operationPb.getError() != null && operationPb.getError().getErrors() != null) {
+ errors =
+ Lists.transform(operationPb.getError().getErrors(), OperationError.FROM_PB_FUNCTION);
+ }
+ if (operationPb.getWarnings() != null) {
+ warnings = Lists.transform(operationPb.getWarnings(), OperationWarning.FROM_PB_FUNCTION);
+ }
+ httpErrorStatusCode = operationPb.getHttpErrorStatusCode();
+ httpErrorMessage = operationPb.getHttpErrorMessage();
+ description = operationPb.getDescription();
+ }
+
+ Builder id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ Builder creationTimestamp(Long creationTimestamp) {
+ this.creationTimestamp = creationTimestamp;
+ return this;
+ }
+
+ Builder operationId(OperationId operationId) {
+ this.operationId = checkNotNull(operationId);
+ return this;
+ }
+
+ Builder clientOperationId(String clientOperationId) {
+ this.clientOperationId = clientOperationId;
+ return this;
+ }
+
+ Builder operationType(String operationType) {
+ this.operationType = operationType;
+ return this;
+ }
+
+ Builder targetLink(String targetLink) {
+ this.targetLink = targetLink;
+ return this;
+ }
+
+ Builder targetId(String targetId) {
+ this.targetId = targetId;
+ return this;
+ }
+
+ Builder status(Status status) {
+ this.status = status;
+ return this;
+ }
+
+ Builder statusMessage(String statusMessage) {
+ this.statusMessage = statusMessage;
+ return this;
+ }
+
+ Builder user(String user) {
+ this.user = user;
+ return this;
+ }
+
+ Builder progress(Integer progress) {
+ this.progress = progress;
+ return this;
+ }
+
+ Builder insertTime(Long insertTime) {
+ this.insertTime = insertTime;
+ return this;
+ }
+
+ Builder startTime(Long startTime) {
+ this.startTime = startTime;
+ return this;
+ }
+
+ Builder endTime(Long endTime) {
+ this.endTime = endTime;
+ return this;
+ }
+
+ Builder errors(List errors) {
+ this.errors = ImmutableList.copyOf(checkNotNull(errors));
+ return this;
+ }
+
+ Builder warnings(List warnings) {
+ this.warnings = ImmutableList.copyOf(checkNotNull(warnings));
+ return this;
+ }
+
+ Builder httpErrorStatusCode(Integer httpErrorStatusCode) {
+ this.httpErrorStatusCode = httpErrorStatusCode;
+ return this;
+ }
+
+ Builder httpErrorMessage(String httpErrorMessage) {
+ this.httpErrorMessage = httpErrorMessage;
+ return this;
+ }
+
+ Builder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ Operation build() {
+ return new Operation(this);
+ }
+ }
+
+ private Operation(Builder builder) {
+ this.compute = checkNotNull(builder.compute);
+ this.options = compute.options();
+ this.id = builder.id;
+ this.creationTimestamp = builder.creationTimestamp;
+ this.operationId = checkNotNull(builder.operationId);
+ this.clientOperationId = builder.clientOperationId;
+ this.operationType = builder.operationType;
+ this.targetLink = builder.targetLink;
+ this.targetId = builder.targetId;
+ this.status = builder.status;
+ this.statusMessage = builder.statusMessage;
+ this.user = builder.user;
+ this.progress = builder.progress;
+ this.insertTime = builder.insertTime;
+ this.startTime = builder.startTime;
+ this.endTime = builder.endTime;
+ this.errors = builder.errors != null ? ImmutableList.copyOf(builder.errors) : null;
+ this.warnings = builder.warnings != null ? ImmutableList.copyOf(builder.warnings) : null;
+ this.httpErrorStatusCode = builder.httpErrorStatusCode;
+ this.httpErrorMessage = builder.httpErrorMessage;
+ this.description = builder.description;
+ }
+
+ /**
+ * Returns the operation's {@code Compute} object used to issue requests.
+ */
+ public Compute compute() {
+ return compute;
+ }
+
+ /**
+ * Returns the service-defined unique identifier for the operation.
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the creation timestamp in milliseconds since epoch.
+ */
+ public Long creationTimestamp() {
+ return creationTimestamp;
+ }
+
+ /**
+ * Returns the operation's identity. This method returns an {@link GlobalOperationId} for global
+ * operations, a {@link RegionOperationId} for region operations and a {@link ZoneOperationId} for
+ * zone operations.
+ *
+ * @see RFC1035
+ */
+ @SuppressWarnings("unchecked")
+ public T operationId() {
+ return (T) operationId;
+ }
+
+ /**
+ * Reserved for future use.
+ */
+ String clientOperationId() {
+ return clientOperationId;
+ }
+
+ /**
+ * Returns the type of operation.
+ */
+ public String operationType() {
+ return operationType;
+ }
+
+ /**
+ * Returns the URL of the resource that the operation is modifying.
+ */
+ public String targetLink() {
+ return targetLink;
+ }
+
+ /**
+ * Returns the unique service-defined target ID, which identifies the resource that the operation
+ * is modifying.
+ */
+ public String targetId() {
+ return targetId;
+ }
+
+ /**
+ * Returns the status of the operation.
+ */
+ public Status status() {
+ return status;
+ }
+
+ /**
+ * Returns an optional textual description of the current status of the operation.
+ */
+ public String statusMessage() {
+ return statusMessage;
+ }
+
+ /**
+ * Returns the user who requested the operation, for example: {@code user@example.com}.
+ */
+ public String user() {
+ return user;
+ }
+
+ /**
+ * Returns an optional progress indicator that ranges from 0 to 100. There is no requirement that
+ * this be linear or support any granularity of operations. This should not be used to guess when
+ * the operation will be complete. This number should monotonically increase as the operation
+ * progresses.
+ */
+ public Integer progress() {
+ return progress;
+ }
+
+ /**
+ * Returns the time that this operation was requested. In milliseconds since epoch.
+ */
+ public Long insertTime() {
+ return insertTime;
+ }
+
+ /**
+ * Returns the time that this operation was started by the service. In milliseconds since epoch.
+ * This value will be {@code null} if the operation has not started yet.
+ */
+ public Long startTime() {
+ return startTime;
+ }
+
+ /**
+ * Returns the time that this operation was completed. In milliseconds since epoch. This value
+ * will be {@code null} if the operation has not finished yet.
+ */
+ public Long endTime() {
+ return endTime;
+ }
+
+ /**
+ * Returns the errors encountered while processing this operation, if any. Returns {@code null} if
+ * no error occurred.
+ */
+ public List errors() {
+ return errors;
+ }
+
+ /**
+ * Returns the warnings encountered while processing this operation, if any. Returns {@code null}
+ * if no warning occurred.
+ */
+ public List warnings() {
+ return warnings;
+ }
+
+ /**
+ * Returns the HTTP error status code that was returned, if the operation failed. For example, a
+ * {@code 404} means the resource was not found.
+ */
+ public Integer httpErrorStatusCode() {
+ return httpErrorStatusCode;
+ }
+
+ /**
+ * Returns the the HTTP error message that was returned, if the operation failed. For example, a
+ * {@code NOT FOUND} message is returned if the resource was not found.
+ */
+ public String httpErrorMessage() {
+ return httpErrorMessage;
+ }
+
+ /**
+ * Returns an optional textual description of the operation.
+ */
+ public String description() {
+ return description;
+ }
+
+ /**
+ * Checks if this operation exists.
+ *
+ * @return {@code true} if this operation exists, {@code false} otherwise
+ * @throws ComputeException upon failure
+ */
+ public boolean exists() throws ComputeException {
+ return reload(Compute.OperationOption.fields()) != null;
+ }
+
+ /**
+ * Checks if this operation has completed its execution, either failing or succeeding. If the
+ * operation does not exist this method returns {@code false}. To correctly wait for operation's
+ * completion, check that the operation exists first using {@link #exists()}:
+ *
+ *
+ * @return {@code true} if this operation is in {@link Operation.Status#DONE} state, {@code false}
+ * if the state is not {@link Operation.Status#DONE} or the operation does not exist
+ * @throws ComputeException upon failure
+ */
+ public boolean isDone() throws ComputeException {
+ Operation operation =
+ compute.get(operationId, Compute.OperationOption.fields(Compute.OperationField.STATUS));
+ return operation != null && operation.status() == Status.DONE;
+ }
+
+ /**
+ * Fetches current operation's latest information. Returns {@code null} if the operation does not
+ * exist.
+ *
+ * @param options operation options
+ * @return an {@code Operation} object with latest information or {@code null} if not found
+ * @throws ComputeException upon failure
+ */
+ public Operation reload(Compute.OperationOption... options) throws ComputeException {
+ return compute.get(operationId, options);
+ }
+
+ /**
+ * Deletes this operation. Delete is only possible for operations that have completed their
+ * execution. Any attempt to delete a running operation will fail.
+ *
+ * @return {@code true} if operation was deleted, {@code false} if it was not found
+ * @throws ComputeException upon failure
+ */
+ public boolean delete() throws ComputeException {
+ return compute.delete(operationId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id)
+ .add("operationsId", operationId)
+ .add("creationTimestamp", creationTimestamp)
+ .add("clientOperationId", clientOperationId)
+ .add("operationType", operationType)
+ .add("targetLink", targetLink)
+ .add("targetId", targetId)
+ .add("status", status)
+ .add("statusMessage", statusMessage)
+ .add("user", user)
+ .add("progress", progress)
+ .add("insertTime", insertTime)
+ .add("startTime", startTime)
+ .add("endTime", endTime)
+ .add("errors", errors)
+ .add("warnings", warnings)
+ .add("httpErrorStatusCode", httpErrorStatusCode)
+ .add("httpErrorMessage", httpErrorMessage)
+ .add("description", description)
+ .toString();
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ return obj instanceof Operation
+ && Objects.equals(toPb(), ((Operation) obj).toPb())
+ && Objects.equals(options, ((Operation) obj).options);
+ }
+
+ com.google.api.services.compute.model.Operation toPb() {
+ com.google.api.services.compute.model.Operation operationPb =
+ new com.google.api.services.compute.model.Operation();
+ if (id != null) {
+ operationPb.setId(new BigInteger(id));
+ }
+ if (creationTimestamp != null) {
+ operationPb.setCreationTimestamp(TIMESTAMP_FORMATTER.print(creationTimestamp));
+ }
+ operationPb.setName(operationId.operation());
+ operationPb.setClientOperationId(clientOperationId);
+ switch (operationId.type()) {
+ case REGION:
+ operationPb.setRegion(this.operationId().regionId().selfLink());
+ break;
+ case ZONE:
+ operationPb.setZone(this.operationId().zoneId().selfLink());
+ break;
+ }
+ if (operationType != null) {
+ operationPb.setOperationType(operationType);
+ }
+ operationPb.setTargetLink(targetLink);
+ if (targetId != null) {
+ operationPb.setTargetId(new BigInteger(targetId));
+ }
+ if (status != null) {
+ operationPb.setStatus(status.name());
+ }
+ operationPb.setStatusMessage(statusMessage);
+ operationPb.setUser(user);
+ operationPb.setProgress(progress);
+ if (insertTime != null) {
+ operationPb.setInsertTime(TIMESTAMP_FORMATTER.print(insertTime));
+ }
+ if (startTime != null) {
+ operationPb.setStartTime(TIMESTAMP_FORMATTER.print(startTime));
+ }
+ if (endTime != null) {
+ operationPb.setEndTime(TIMESTAMP_FORMATTER.print(endTime));
+ }
+ if (errors != null) {
+ operationPb.setError(new com.google.api.services.compute.model.Operation.Error().setErrors(
+ Lists.transform(errors, OperationError.TO_PB_FUNCTION)));
+ }
+ if (warnings != null) {
+ operationPb.setWarnings(Lists.transform(warnings, OperationWarning.TO_PB_FUNCTION));
+ }
+ operationPb.setHttpErrorStatusCode(httpErrorStatusCode);
+ operationPb.setHttpErrorMessage(httpErrorMessage);
+ operationPb.setSelfLink(operationId.selfLink());
+ operationPb.setDescription(description);
+ return operationPb;
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ this.compute = options.service();
+ }
+
+ static Operation fromPb(Compute compute,
+ com.google.api.services.compute.model.Operation operationPb) {
+ return new Builder(compute, operationPb).build();
+ }
+}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/OperationId.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/OperationId.java
new file mode 100644
index 000000000000..7f9100aa61a2
--- /dev/null
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/OperationId.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 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.compute;
+
+/**
+ * Interface for Google Compute Engine operation identities.
+ */
+public interface OperationId {
+
+ /**
+ * Possible types for a Google Compute Engine operation identity.
+ */
+ enum Type {
+ /**
+ * Global operations are those operations that deal with global resources, such as global
+ * addresses or snapshots.
+ */
+ GLOBAL,
+ /**
+ * Region operations are those operations that deal with resources that live in a region, such
+ * as subnetworks.
+ */
+ REGION,
+ /**
+ * Zone operations are those operations that deal with resources that live in a zone, such as
+ * disks and instances.
+ */
+ ZONE
+ }
+
+ /**
+ * Returns the type of this operation identity.
+ */
+ Type type();
+
+ /**
+ * Returns the name of the project.
+ */
+ String project();
+
+ /**
+ * Returns the name of the operation resource.
+ */
+ String operation();
+
+ /**
+ * Returns a fully qualified URL to the operation.
+ */
+ String selfLink();
+}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Region.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Region.java
index 38f79a691721..470acf4e9061 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Region.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Region.java
@@ -34,7 +34,7 @@
*
* @see Region and Zones
*/
-public final class Region implements Serializable {
+public class Region implements Serializable {
static final Function FROM_PB_FUNCTION =
new Function() {
@@ -306,12 +306,12 @@ public String toString() {
}
@Override
- public int hashCode() {
+ public final int hashCode() {
return Objects.hash(regionId);
}
@Override
- public boolean equals(Object obj) {
+ public final boolean equals(Object obj) {
return obj instanceof Region && Objects.equals(toPb(), ((Region) obj).toPb());
}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/RegionOperationId.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/RegionOperationId.java
new file mode 100644
index 000000000000..96a772b5b9ea
--- /dev/null
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/RegionOperationId.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016 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.compute;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Identity for a Google Compute Engine region's operation.
+ */
+public final class RegionOperationId extends RegionResourceId implements OperationId {
+
+ private static final String REGEX = RegionResourceId.REGEX + "operations/([^/]+)";
+ private static final Pattern PATTERN = Pattern.compile(REGEX);
+ private static final long serialVersionUID = 5816161906501886782L;
+
+ private final String operation;
+
+ private RegionOperationId(String project, String region, String operation) {
+ super(project, region);
+ this.operation = checkNotNull(operation);
+ }
+
+ @Override
+ public Type type() {
+ return Type.REGION;
+ }
+
+ @Override
+ public String operation() {
+ return operation;
+ }
+
+ @Override
+ public String selfLink() {
+ return super.selfLink() + "/operations/" + operation;
+ }
+
+ @Override
+ MoreObjects.ToStringHelper toStringHelper() {
+ return MoreObjects.toStringHelper(this).add("operation", operation);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(baseHashCode(), operation);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof RegionOperationId
+ && baseEquals((RegionOperationId) obj)
+ && Objects.equals(operation, ((RegionOperationId) obj).operation);
+ }
+
+ @Override
+ RegionOperationId setProjectId(String projectId) {
+ if (project() != null) {
+ return this;
+ }
+ return RegionOperationId.of(projectId, region(), operation);
+ }
+
+ /**
+ * Returns a region operation identity given the region identity and the operation name.
+ */
+ public static RegionOperationId of(RegionId regionId, String operation) {
+ return new RegionOperationId(regionId.project(), regionId.region(), operation);
+ }
+
+ /**
+ * Returns a region operation identity given the region and operation names.
+ */
+ public static RegionOperationId of(String region, String operation) {
+ return new RegionOperationId(null, region, operation);
+ }
+
+ /**
+ * Returns a region operation identity given project, region and operation names.
+ */
+ public static RegionOperationId of(String project, String region, String operation) {
+ return new RegionOperationId(project, region, operation);
+ }
+
+ /**
+ * Returns {@code true} if the provided string matches the expected format of a region operation
+ * URL. Returns {@code false} otherwise.
+ */
+ static boolean matchesUrl(String url) {
+ return PATTERN.matcher(url).matches();
+ }
+
+ static RegionOperationId fromUrl(String url) {
+ Matcher matcher = PATTERN.matcher(url);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(url + " is not a valid region operation URL");
+ }
+ return RegionOperationId.of(matcher.group(1), matcher.group(2), matcher.group(3));
+ }
+}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Zone.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Zone.java
index 20c64946d7ce..903f994118de 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Zone.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Zone.java
@@ -34,7 +34,7 @@
*
* @see Region and Zones
*/
-public final class Zone implements Serializable {
+public class Zone implements Serializable {
static final Function FROM_PB_FUNCTION =
new Function() {
@@ -325,12 +325,12 @@ public String toString() {
}
@Override
- public int hashCode() {
+ public final int hashCode() {
return Objects.hash(zoneId);
}
@Override
- public boolean equals(Object obj) {
+ public final boolean equals(Object obj) {
return obj instanceof Zone && Objects.equals(toPb(), ((Zone) obj).toPb());
}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ZoneOperationId.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ZoneOperationId.java
new file mode 100644
index 000000000000..837ca6888c83
--- /dev/null
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ZoneOperationId.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016 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.compute;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Identity for a Google Compute Engine zone operation.
+ */
+public final class ZoneOperationId extends ZoneResourceId implements OperationId {
+
+ private static final String REGEX = ZoneResourceId.REGEX + "operations/([^/]+)";
+ private static final Pattern PATTERN = Pattern.compile(REGEX);
+ private static final long serialVersionUID = 4910670262094017392L;
+
+ private final String operation;
+
+ private ZoneOperationId(String project, String zone, String operation) {
+ super(project, zone);
+ this.operation = checkNotNull(operation);
+ }
+
+ @Override
+ public Type type() {
+ return Type.ZONE;
+ }
+
+ @Override
+ public String operation() {
+ return operation;
+ }
+
+ @Override
+ public String selfLink() {
+ return super.selfLink() + "/operations/" + operation;
+ }
+
+ @Override
+ MoreObjects.ToStringHelper toStringHelper() {
+ return MoreObjects.toStringHelper(this).add("operation", operation);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(baseHashCode(), operation);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ZoneOperationId
+ && baseEquals((ZoneOperationId) obj)
+ && Objects.equals(operation, ((ZoneOperationId) obj).operation);
+ }
+
+ @Override
+ ZoneOperationId setProjectId(String projectId) {
+ if (project() != null) {
+ return this;
+ }
+ return ZoneOperationId.of(projectId, zone(), operation);
+ }
+
+ /**
+ * Returns a zone operation identity given the zone identity and the operation name.
+ */
+ public static ZoneOperationId of(ZoneId zoneId, String operation) {
+ return new ZoneOperationId(zoneId.project(), zoneId.zone(), operation);
+ }
+
+ /**
+ * Returns a zone operation identity given the zone and operation names.
+ */
+ public static ZoneOperationId of(String zone, String operation) {
+ return new ZoneOperationId(null, zone, operation);
+ }
+
+ /**
+ * Returns a zone operation identity given project, zone and operation names.
+ */
+ public static ZoneOperationId of(String project, String zone, String operation) {
+ return new ZoneOperationId(project, zone, operation);
+ }
+
+ /**
+ * Returns {@code true} if the provided string matches the expected format of a zone operation
+ * URL. Returns {@code false} otherwise.
+ */
+ static boolean matchesUrl(String url) {
+ return PATTERN.matcher(url).matches();
+ }
+
+ static ZoneOperationId fromUrl(String url) {
+ Matcher matcher = PATTERN.matcher(url);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(url + " is not a valid zone operation URL");
+ }
+ return ZoneOperationId.of(matcher.group(1), matcher.group(2), matcher.group(3));
+ }
+}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java
index 35524e0c116d..740ad73b5b2e 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java
@@ -19,6 +19,7 @@
import com.google.api.services.compute.model.DiskType;
import com.google.api.services.compute.model.License;
import com.google.api.services.compute.model.MachineType;
+import com.google.api.services.compute.model.Operation;
import com.google.api.services.compute.model.Region;
import com.google.api.services.compute.model.Zone;
import com.google.gcloud.compute.ComputeException;
@@ -161,4 +162,70 @@ public Y y() {
* @throws ComputeException upon failure
*/
License getLicense(String project, String license, Map