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()}: + *
 {@code
+   * if (operation.exists()) {
+   *   while(!operation.isDone()) {
+   *     Thread.sleep(1000L);
+   *   }
+   * }}
+ * + * @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 options); + + /** + * Returns the requested global operation or {@code null} if not found. + * + * @throws ComputeException upon failure + */ + Operation getGlobalOperation(String operation, Map options); + + /** + * Lists the global operations. + * + * @throws ComputeException upon failure + */ + Tuple> listGlobalOperations(Map options); + + /** + * Deletes the requested global operation. + * + * @return {@code true} if operation was deleted, {@code false} if it was not found + * @throws ComputeException upon failure + */ + boolean deleteGlobalOperation(String operation); + + /** + * Returns the requested region operation or {@code null} if not found. + * + * @throws ComputeException upon failure + */ + Operation getRegionOperation(String region, String operation, Map options); + + /** + * Lists the region operations for the provided region. + * + * @throws ComputeException upon failure + */ + Tuple> listRegionOperations(String region, Map options); + + /** + * Deletes the requested region operation. + * + * @return {@code true} if operation was deleted, {@code false} if it was not found + * @throws ComputeException upon failure + */ + boolean deleteRegionOperation(String region, String operation); + + /** + * Returns the requested zone operation or {@code null} if not found. + * + * @throws ComputeException upon failure + */ + Operation getZoneOperation(String zone, String operation, Map options); + + /** + * Lists the zone operations for the provided zone. + * + * @throws ComputeException upon failure + */ + Tuple> listZoneOperations(String zone, Map options); + + /** + * Deletes the requested zone operation. + * + * @return {@code true} if operation was deleted, {@code false} if it was not found + * @throws ComputeException upon failure + */ + boolean deleteZoneOperation(String zone, String operation); } diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/DefaultComputeRpc.java b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/DefaultComputeRpc.java index fe714acab502..3209084a5983 100644 --- a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/DefaultComputeRpc.java +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/DefaultComputeRpc.java @@ -35,6 +35,8 @@ import com.google.api.services.compute.model.MachineTypeAggregatedList; import com.google.api.services.compute.model.MachineTypeList; import com.google.api.services.compute.model.MachineTypesScopedList; +import com.google.api.services.compute.model.Operation; +import com.google.api.services.compute.model.OperationList; import com.google.api.services.compute.model.Region; import com.google.api.services.compute.model.RegionList; import com.google.api.services.compute.model.Zone; @@ -248,16 +250,135 @@ public License getLicense(String project, String license, Map options } } + @Override + public Operation getGlobalOperation(String operation, Map options) { + try { + return compute.globalOperations() + .get(this.options.projectId(), operation) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Tuple> listGlobalOperations(Map options) { + try { + OperationList operationsList = compute.globalOperations() + .list(this.options.projectId()) + .setFilter(FILTER.getString(options)) + .setMaxResults(MAX_RESULTS.getLong(options)) + .setPageToken(PAGE_TOKEN.getString(options)) + .setFields(FIELDS.getString(options)) + .execute(); + Iterable operations = operationsList.getItems(); + return Tuple.of(operationsList.getNextPageToken(), operations); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public boolean deleteGlobalOperation(String operation) { + try { + compute.globalOperations().delete(this.options.projectId(), operation).execute(); + return true; + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Operation getRegionOperation(String region, String operation, Map options) { + try { + return compute.regionOperations() + .get(this.options.projectId(), region, operation) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Tuple> listRegionOperations(String region, + Map options) { + try { + OperationList operationsList = compute.regionOperations() + .list(this.options.projectId(), region) + .setFilter(FILTER.getString(options)) + .setMaxResults(MAX_RESULTS.getLong(options)) + .setPageToken(PAGE_TOKEN.getString(options)) + .setFields(FIELDS.getString(options)) + .execute(); + Iterable operations = operationsList.getItems(); + return Tuple.of(operationsList.getNextPageToken(), operations); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public boolean deleteRegionOperation(String region, String operation) { + try { + compute.regionOperations().delete(this.options.projectId(), region, operation).execute(); + return true; + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Operation getZoneOperation(String zone, String operation, Map options) { + try { + return compute.zoneOperations() + .get(this.options.projectId(), zone, operation) + .setFields(FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + + @Override + public Tuple> listZoneOperations(String zone, + Map options) { + try { + OperationList operationsList = compute.zoneOperations() + .list(this.options.projectId(), zone) + .setFilter(FILTER.getString(options)) + .setMaxResults(MAX_RESULTS.getLong(options)) + .setPageToken(PAGE_TOKEN.getString(options)) + .setFields(FIELDS.getString(options)) + .execute(); + Iterable operations = operationsList.getItems(); + return Tuple.of(operationsList.getNextPageToken(), operations); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public boolean deleteZoneOperation(String zone, String operation) { + try { + compute.zoneOperations().delete(this.options.projectId(), zone, operation).execute(); + return true; + } catch (IOException ex) { + return nullForNotFound(ex); + } + } + /** * This method returns {@code null} if the error code of {@code exception} was 404, re-throws the * exception otherwise. * - * @throws ComputeException if the error code of {@code exception} was not 404. + * @throws ComputeException if the error code of {@code exception} was not 404 */ private static T nullForNotFound(IOException exception) { ComputeException serviceException = translate(exception); if (serviceException.code() == HTTP_NOT_FOUND) { - return (T) null; + return null; } throw serviceException; } diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java index 5ef9b04ed446..e1a81b5afe48 100644 --- a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/ComputeImplTest.java @@ -16,19 +16,45 @@ package com.google.gcloud.compute; +import static com.google.gcloud.spi.ComputeRpc.Option.FILTER; +import static com.google.gcloud.spi.ComputeRpc.Option.MAX_RESULTS; +import static com.google.gcloud.spi.ComputeRpc.Option.PAGE_TOKEN; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.eq; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +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.gcloud.Page; import com.google.gcloud.RetryParams; +import com.google.gcloud.compute.Compute.DiskTypeAggregatedListOption; +import com.google.gcloud.compute.Compute.DiskTypeFilter; +import com.google.gcloud.compute.Compute.DiskTypeListOption; +import com.google.gcloud.compute.Compute.DiskTypeOption; +import com.google.gcloud.compute.Compute.LicenseOption; +import com.google.gcloud.compute.Compute.MachineTypeAggregatedListOption; +import com.google.gcloud.compute.Compute.MachineTypeFilter; +import com.google.gcloud.compute.Compute.MachineTypeListOption; +import com.google.gcloud.compute.Compute.MachineTypeOption; +import com.google.gcloud.compute.Compute.OperationFilter; +import com.google.gcloud.compute.Compute.OperationListOption; +import com.google.gcloud.compute.Compute.OperationOption; +import com.google.gcloud.compute.Compute.RegionFilter; +import com.google.gcloud.compute.Compute.RegionListOption; +import com.google.gcloud.compute.Compute.RegionOption; +import com.google.gcloud.compute.Compute.ZoneFilter; +import com.google.gcloud.compute.Compute.ZoneListOption; +import com.google.gcloud.compute.Compute.ZoneOption; +import com.google.gcloud.compute.Operation.OperationError; +import com.google.gcloud.compute.Operation.OperationWarning; +import com.google.gcloud.compute.Operation.Status; import com.google.gcloud.compute.Zone.MaintenanceWindow; import com.google.gcloud.spi.ComputeRpc; import com.google.gcloud.spi.ComputeRpc.Tuple; @@ -117,110 +143,159 @@ public class ComputeImplTest { private static final LicenseId LICENSE_ID = LicenseId.of("project", "license"); private static final Boolean CHARGES_USE_FEE = true; private static final License LICENSE = new License(LICENSE_ID, CHARGES_USE_FEE); + private static final OperationError OPERATION_ERROR1 = + new OperationError("code1", "location1", "message1"); + private static final OperationError OPERATION_ERROR2 = + new OperationError("code2", "location2", "message2"); + private static final OperationWarning OPERATION_WARNING1 = + new OperationWarning("code1", "message1", ImmutableMap.of("k1", "v1")); + private static final OperationWarning OPERATION_WARNING2 = + new OperationWarning("code2", "location2", ImmutableMap.of("k2", "v2")); + private static final String CLIENT_OPERATION_ID = "clientOperationId"; + private static final String OPERATION_TYPE = "delete"; + private static final String TARGET_LINK = "targetLink"; + private static final String TARGET_ID = "42"; + private static final Status STATUS = Status.DONE; + private static final String STATUS_MESSAGE = "statusMessage"; + private static final String USER = "user"; + private static final Integer PROGRESS = 100; + private static final Long INSERT_TIME = 1453293540000L; + private static final Long START_TIME = 1453293420000L; + private static final Long END_TIME = 1453293480000L; + private static final List ERRORS = + ImmutableList.of(OPERATION_ERROR1, OPERATION_ERROR2); + private static final List WARNINGS = + ImmutableList.of(OPERATION_WARNING1, OPERATION_WARNING2); + private static final Integer HTTP_ERROR_STATUS_CODE = 404; + private static final String HTTP_ERROR_MESSAGE = "NOT FOUND"; + private static final GlobalOperationId GLOBAL_OPERATION_ID = + GlobalOperationId.of("project", "op"); + private static final ZoneOperationId ZONE_OPERATION_ID = + ZoneOperationId.of("project", "zone", "op"); + private static final RegionOperationId REGION_OPERATION_ID = + RegionOperationId.of("project", "region", "op"); // Empty ComputeRpc options private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); // DiskType options - private static final Compute.DiskTypeOption DISK_TYPE_OPTION_FIELDS = - Compute.DiskTypeOption.fields(Compute.DiskTypeField.ID, Compute.DiskTypeField.DESCRIPTION); + private static final DiskTypeOption DISK_TYPE_OPTION_FIELDS = + DiskTypeOption.fields(Compute.DiskTypeField.ID, Compute.DiskTypeField.DESCRIPTION); // DiskType list options - private static final Compute.DiskTypeFilter DISK_TYPE_FILTER = - Compute.DiskTypeFilter.equals(Compute.DiskTypeField.DESCRIPTION, "someDescription"); - private static final Compute.DiskTypeListOption DISK_TYPE_LIST_PAGE_TOKEN = - Compute.DiskTypeListOption.startPageToken("cursor"); - private static final Compute.DiskTypeListOption DISK_TYPE_LIST_MAX_RESULTS = - Compute.DiskTypeListOption.maxResults(42L); - private static final Compute.DiskTypeListOption DISK_TYPE_LIST_FILTER = - Compute.DiskTypeListOption.filter(DISK_TYPE_FILTER); + private static final DiskTypeFilter DISK_TYPE_FILTER = + DiskTypeFilter.equals(Compute.DiskTypeField.DESCRIPTION, "someDescription"); + private static final DiskTypeListOption DISK_TYPE_LIST_PAGE_TOKEN = + DiskTypeListOption.startPageToken("cursor"); + private static final DiskTypeListOption DISK_TYPE_LIST_MAX_RESULTS = + DiskTypeListOption.pageSize(42L); + private static final DiskTypeListOption DISK_TYPE_LIST_FILTER = + DiskTypeListOption.filter(DISK_TYPE_FILTER); private static final Map DISK_TYPE_LIST_OPTIONS = ImmutableMap.of( - ComputeRpc.Option.PAGE_TOKEN, "cursor", - ComputeRpc.Option.MAX_RESULTS, 42L, - ComputeRpc.Option.FILTER, "description eq someDescription"); + PAGE_TOKEN, "cursor", + MAX_RESULTS, 42L, + FILTER, "description eq someDescription"); // DiskType aggregated list options - private static final Compute.DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_PAGE_TOKEN = - Compute.DiskTypeAggregatedListOption.startPageToken("cursor"); - private static final Compute.DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_MAX_RESULTS = - Compute.DiskTypeAggregatedListOption.maxResults(42L); - private static final Compute.DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_FILTER = - Compute.DiskTypeAggregatedListOption.filter(DISK_TYPE_FILTER); + private static final DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_PAGE_TOKEN = + DiskTypeAggregatedListOption.startPageToken("cursor"); + private static final DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_MAX_RESULTS = + DiskTypeAggregatedListOption.pageSize(42L); + private static final DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_FILTER = + DiskTypeAggregatedListOption.filter(DISK_TYPE_FILTER); // MachineType options - private static final Compute.MachineTypeOption MACHINE_TYPE_OPTION_FIELDS = - Compute.MachineTypeOption.fields(Compute.MachineTypeField.ID, + private static final MachineTypeOption MACHINE_TYPE_OPTION_FIELDS = + MachineTypeOption.fields(Compute.MachineTypeField.ID, Compute.MachineTypeField.DESCRIPTION); // MachineType list options - private static final Compute.MachineTypeFilter MACHINE_TYPE_FILTER = - Compute.MachineTypeFilter.notEquals(Compute.MachineTypeField.MAXIMUM_PERSISTENT_DISKS, 42L); - private static final Compute.MachineTypeListOption MACHINE_TYPE_LIST_PAGE_TOKEN = - Compute.MachineTypeListOption.startPageToken("cursor"); - private static final Compute.MachineTypeListOption MACHINE_TYPE_LIST_MAX_RESULTS = - Compute.MachineTypeListOption.maxResults(42L); - private static final Compute.MachineTypeListOption MACHINE_TYPE_LIST_FILTER = - Compute.MachineTypeListOption.filter(MACHINE_TYPE_FILTER); + private static final MachineTypeFilter MACHINE_TYPE_FILTER = + MachineTypeFilter.notEquals(Compute.MachineTypeField.MAXIMUM_PERSISTENT_DISKS, 42L); + private static final MachineTypeListOption MACHINE_TYPE_LIST_PAGE_TOKEN = + MachineTypeListOption.startPageToken("cursor"); + private static final MachineTypeListOption MACHINE_TYPE_LIST_MAX_RESULTS = + MachineTypeListOption.pageSize(42L); + private static final MachineTypeListOption MACHINE_TYPE_LIST_FILTER = + MachineTypeListOption.filter(MACHINE_TYPE_FILTER); private static final Map MACHINE_TYPE_LIST_OPTIONS = ImmutableMap.of( - ComputeRpc.Option.PAGE_TOKEN, "cursor", - ComputeRpc.Option.MAX_RESULTS, 42L, - ComputeRpc.Option.FILTER, "maximumPersistentDisks ne 42"); + PAGE_TOKEN, "cursor", + MAX_RESULTS, 42L, + FILTER, "maximumPersistentDisks ne 42"); // MachineType aggregated list options - private static final Compute.MachineTypeAggregatedListOption - MACHINE_TYPE_AGGREGATED_LIST_PAGE_TOKEN = - Compute.MachineTypeAggregatedListOption.startPageToken("cursor"); - private static final Compute.MachineTypeAggregatedListOption - MACHINE_TYPE_AGGREGATED_LIST_MAX_RESULTS = - Compute.MachineTypeAggregatedListOption.maxResults(42L); - private static final Compute.MachineTypeAggregatedListOption MACHINE_TYPE_AGGREGATED_LIST_FILTER = - Compute.MachineTypeAggregatedListOption.filter(MACHINE_TYPE_FILTER); + private static final MachineTypeAggregatedListOption MACHINE_TYPE_AGGREGATED_LIST_PAGE_TOKEN = + MachineTypeAggregatedListOption.startPageToken("cursor"); + private static final MachineTypeAggregatedListOption MACHINE_TYPE_AGGREGATED_LIST_MAX_RESULTS = + MachineTypeAggregatedListOption.pageSize(42L); + private static final MachineTypeAggregatedListOption MACHINE_TYPE_AGGREGATED_LIST_FILTER = + MachineTypeAggregatedListOption.filter(MACHINE_TYPE_FILTER); // Region options - private static final Compute.RegionOption REGION_OPTION_FIELDS = - Compute.RegionOption.fields(Compute.RegionField.ID, Compute.RegionField.DESCRIPTION); + private static final RegionOption REGION_OPTION_FIELDS = + RegionOption.fields(Compute.RegionField.ID, Compute.RegionField.DESCRIPTION); // Region list options - private static final Compute.RegionFilter REGION_FILTER = - Compute.RegionFilter.equals(Compute.RegionField.ID, "someId"); - private static final Compute.RegionListOption REGION_LIST_PAGE_TOKEN = - Compute.RegionListOption.startPageToken("cursor"); - private static final Compute.RegionListOption REGION_LIST_MAX_RESULTS = - Compute.RegionListOption.maxResults(42L); - private static final Compute.RegionListOption REGION_LIST_FILTER = - Compute.RegionListOption.filter(REGION_FILTER); + private static final RegionFilter REGION_FILTER = + RegionFilter.equals(Compute.RegionField.ID, "someId"); + private static final RegionListOption REGION_LIST_PAGE_TOKEN = + RegionListOption.startPageToken("cursor"); + private static final RegionListOption REGION_LIST_MAX_RESULTS = + RegionListOption.pageSize(42L); + private static final RegionListOption REGION_LIST_FILTER = + RegionListOption.filter(REGION_FILTER); private static final Map REGION_LIST_OPTIONS = ImmutableMap.of( - ComputeRpc.Option.PAGE_TOKEN, "cursor", - ComputeRpc.Option.MAX_RESULTS, 42L, - ComputeRpc.Option.FILTER, "id eq someId"); + PAGE_TOKEN, "cursor", + MAX_RESULTS, 42L, + FILTER, "id eq someId"); // Zone options - private static final Compute.ZoneOption ZONE_OPTION_FIELDS = - Compute.ZoneOption.fields(Compute.ZoneField.ID, Compute.ZoneField.DESCRIPTION); + private static final ZoneOption ZONE_OPTION_FIELDS = + ZoneOption.fields(Compute.ZoneField.ID, Compute.ZoneField.DESCRIPTION); // Zone list options - private static final Compute.ZoneFilter ZONE_FILTER = - Compute.ZoneFilter.notEquals(Compute.ZoneField.NAME, "someName"); - private static final Compute.ZoneListOption ZONE_LIST_PAGE_TOKEN = - Compute.ZoneListOption.startPageToken("cursor"); - private static final Compute.ZoneListOption ZONE_LIST_MAX_RESULTS = - Compute.ZoneListOption.maxResults(42L); - private static final Compute.ZoneListOption ZONE_LIST_FILTER = - Compute.ZoneListOption.filter(ZONE_FILTER); + private static final ZoneFilter ZONE_FILTER = + ZoneFilter.notEquals(Compute.ZoneField.NAME, "someName"); + private static final ZoneListOption ZONE_LIST_PAGE_TOKEN = + ZoneListOption.startPageToken("cursor"); + private static final ZoneListOption ZONE_LIST_MAX_RESULTS = ZoneListOption.pageSize(42L); + private static final ZoneListOption ZONE_LIST_FILTER = ZoneListOption.filter(ZONE_FILTER); private static final Map ZONE_LIST_OPTIONS = ImmutableMap.of( - ComputeRpc.Option.PAGE_TOKEN, "cursor", - ComputeRpc.Option.MAX_RESULTS, 42L, - ComputeRpc.Option.FILTER, "name ne someName"); + PAGE_TOKEN, "cursor", + MAX_RESULTS, 42L, + FILTER, "name ne someName"); // License options - private static final Compute.LicenseOption LICENSE_OPTION_FIELDS = - Compute.LicenseOption.fields(Compute.LicenseField.CHARGES_USE_FEE); + private static final LicenseOption LICENSE_OPTION_FIELDS = + LicenseOption.fields(Compute.LicenseField.CHARGES_USE_FEE); + + // Operation options + private static final OperationOption OPERATION_OPTION_FIELDS = + OperationOption.fields(Compute.OperationField.ID, Compute.OperationField.DESCRIPTION); + + // Operation list options + private static final OperationFilter OPERATION_FILTER = + OperationFilter.notEquals(Compute.OperationField.PROGRESS, 0); + private static final OperationListOption OPERATION_LIST_PAGE_TOKEN = + OperationListOption.startPageToken("cursor"); + private static final OperationListOption OPERATION_LIST_MAX_RESULTS = + OperationListOption.pageSize(42L); + private static final OperationListOption OPERATION_LIST_FILTER = + OperationListOption.filter(OPERATION_FILTER); + private static final Map OPERATION_LIST_OPTIONS = ImmutableMap.of( + PAGE_TOKEN, "cursor", + MAX_RESULTS, 42L, + FILTER, "progress ne 0"); private ComputeOptions options; private ComputeRpcFactory rpcFactoryMock; private ComputeRpc computeRpcMock; private Compute compute; + private Operation globalOperation; + private Operation zoneOperation; + private Operation regionOperation; + @Rule public ExpectedException thrown = ExpectedException.none(); @@ -229,13 +304,77 @@ public void setUp() { rpcFactoryMock = EasyMock.createMock(ComputeRpcFactory.class); computeRpcMock = EasyMock.createMock(ComputeRpc.class); EasyMock.expect(rpcFactoryMock.create(EasyMock.anyObject(ComputeOptions.class))) - .andReturn(computeRpcMock).times(1); + .andReturn(computeRpcMock).times(2); EasyMock.replay(rpcFactoryMock); options = ComputeOptions.builder() .projectId(PROJECT) .serviceRpcFactory(rpcFactoryMock) .retryParams(RetryParams.noRetries()) .build(); + Compute otherService = options.toBuilder().build().service(); + globalOperation = new Operation.Builder(otherService) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .operationId(GLOBAL_OPERATION_ID) + .clientOperationId(CLIENT_OPERATION_ID) + .operationType(OPERATION_TYPE) + .targetLink(TARGET_LINK) + .targetId(TARGET_ID) + .status(STATUS) + .statusMessage(STATUS_MESSAGE) + .user(USER) + .progress(PROGRESS) + .insertTime(INSERT_TIME) + .startTime(START_TIME) + .endTime(END_TIME) + .errors(ERRORS) + .warnings(WARNINGS) + .httpErrorStatusCode(HTTP_ERROR_STATUS_CODE) + .httpErrorMessage(HTTP_ERROR_MESSAGE) + .description(DESCRIPTION) + .build(); + zoneOperation = new Operation.Builder(otherService) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .operationId(ZONE_OPERATION_ID) + .clientOperationId(CLIENT_OPERATION_ID) + .operationType(OPERATION_TYPE) + .targetLink(TARGET_LINK) + .targetId(TARGET_ID) + .status(STATUS) + .statusMessage(STATUS_MESSAGE) + .user(USER) + .progress(PROGRESS) + .insertTime(INSERT_TIME) + .startTime(START_TIME) + .endTime(END_TIME) + .errors(ERRORS) + .warnings(WARNINGS) + .httpErrorStatusCode(HTTP_ERROR_STATUS_CODE) + .httpErrorMessage(HTTP_ERROR_MESSAGE) + .description(DESCRIPTION) + .build(); + regionOperation = new Operation.Builder(otherService) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .operationId(REGION_OPERATION_ID) + .clientOperationId(CLIENT_OPERATION_ID) + .operationType(OPERATION_TYPE) + .targetLink(TARGET_LINK) + .targetId(TARGET_ID) + .status(STATUS) + .statusMessage(STATUS_MESSAGE) + .user(USER) + .progress(PROGRESS) + .insertTime(INSERT_TIME) + .startTime(START_TIME) + .endTime(END_TIME) + .errors(ERRORS) + .warnings(WARNINGS) + .httpErrorStatusCode(HTTP_ERROR_STATUS_CODE) + .httpErrorMessage(HTTP_ERROR_MESSAGE) + .description(DESCRIPTION) + .build(); } @After @@ -306,6 +445,31 @@ public void testListDiskTypes() { assertArrayEquals(diskTypeList.toArray(), Iterables.toArray(page.values(), DiskType.class)); } + @Test + public void testListDiskTypesNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList diskTypeList = ImmutableList.of(DISK_TYPE, DISK_TYPE); + Tuple> result = + Tuple.of(cursor, Iterables.transform(diskTypeList, DiskType.TO_PB_FUNCTION)); + ImmutableList nextDiskTypeList = ImmutableList.of(DISK_TYPE); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextDiskTypeList, DiskType.TO_PB_FUNCTION)); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listDiskTypes(DISK_TYPE_ID.zone(), EMPTY_RPC_OPTIONS)) + .andReturn(result); + EasyMock.expect(computeRpcMock.listDiskTypes(DISK_TYPE_ID.zone(), nextOptions)) + .andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listDiskTypes(DISK_TYPE_ID.zone()); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(diskTypeList.toArray(), Iterables.toArray(page.values(), DiskType.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextDiskTypeList.toArray(), Iterables.toArray(page.values(), DiskType.class)); + } + @Test public void testListEmptyDiskTypes() { ImmutableList diskTypes = ImmutableList.of(); @@ -350,6 +514,29 @@ public void testAggregatedListDiskTypes() { assertArrayEquals(diskTypeList.toArray(), Iterables.toArray(page.values(), DiskType.class)); } + @Test + public void testAggregatedListDiskTypesNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList diskTypeList = ImmutableList.of(DISK_TYPE, DISK_TYPE); + Tuple> result = + Tuple.of(cursor, Iterables.transform(diskTypeList, DiskType.TO_PB_FUNCTION)); + ImmutableList nextDiskTypeList = ImmutableList.of(DISK_TYPE); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextDiskTypeList, DiskType.TO_PB_FUNCTION)); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listDiskTypes(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.expect(computeRpcMock.listDiskTypes(nextOptions)).andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listDiskTypes(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(diskTypeList.toArray(), Iterables.toArray(page.values(), DiskType.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextDiskTypeList.toArray(), Iterables.toArray(page.values(), DiskType.class)); + } + @Test public void testAggregatedListEmptyDiskTypes() { ImmutableList diskTypes = ImmutableList.of(); @@ -437,6 +624,33 @@ public void testListMachineTypes() { MachineType.class)); } + @Test + public void testListMachineTypesNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList machineTypeList = ImmutableList.of(MACHINE_TYPE, MACHINE_TYPE); + Tuple> result = + Tuple.of(cursor, Iterables.transform(machineTypeList, MachineType.TO_PB_FUNCTION)); + ImmutableList nextMachineTypeList = ImmutableList.of(MACHINE_TYPE); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextMachineTypeList, MachineType.TO_PB_FUNCTION)); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listMachineTypes(MACHINE_TYPE_ID.zone(), EMPTY_RPC_OPTIONS)) + .andReturn(result); + EasyMock.expect(computeRpcMock.listMachineTypes(MACHINE_TYPE_ID.zone(), nextOptions)) + .andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listMachineTypes(MACHINE_TYPE_ID.zone()); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(machineTypeList.toArray(), + Iterables.toArray(page.values(), MachineType.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextMachineTypeList.toArray(), + Iterables.toArray(page.values(), MachineType.class)); + } + @Test public void testListEmptyMachineTypes() { ImmutableList machineTypes = @@ -486,6 +700,31 @@ public void testAggregatedListMachineTypes() { MachineType.class)); } + @Test + public void testAggregatedListMachineTypesNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList machineTypeList = ImmutableList.of(MACHINE_TYPE, MACHINE_TYPE); + Tuple> result = + Tuple.of(cursor, Iterables.transform(machineTypeList, MachineType.TO_PB_FUNCTION)); + ImmutableList nextMachineTypeList = ImmutableList.of(MACHINE_TYPE); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextMachineTypeList, MachineType.TO_PB_FUNCTION)); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listMachineTypes(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.expect(computeRpcMock.listMachineTypes(nextOptions)).andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listMachineTypes(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(machineTypeList.toArray(), + Iterables.toArray(page.values(), MachineType.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextMachineTypeList.toArray(), + Iterables.toArray(page.values(), MachineType.class)); + } + @Test public void testAggregatedListEmptyMachineTypes() { ImmutableList machineTypes = @@ -558,6 +797,29 @@ public void testListRegions() { assertArrayEquals(regionList.toArray(), Iterables.toArray(page.values(), Region.class)); } + @Test + public void testListRegionsNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList regionList = ImmutableList.of(REGION, REGION); + ImmutableList nextRegionList = ImmutableList.of(REGION); + Tuple> result = + Tuple.of(cursor, Iterables.transform(regionList, Region.TO_PB_FUNCTION)); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextRegionList, Region.TO_PB_FUNCTION)); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listRegions(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.expect(computeRpcMock.listRegions(nextOptions)).andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listRegions(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(regionList.toArray(), Iterables.toArray(page.values(), Region.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextRegionList.toArray(), Iterables.toArray(page.values(), Region.class)); + } + @Test public void testListEmptyRegions() { ImmutableList regions = ImmutableList.of(); @@ -627,6 +889,29 @@ public void testListZones() { assertArrayEquals(zoneList.toArray(), Iterables.toArray(page.values(), Zone.class)); } + @Test + public void testListZonesNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList zoneList = ImmutableList.of(ZONE, ZONE); + ImmutableList nextZoneList = ImmutableList.of(ZONE); + Tuple> result = + Tuple.of(cursor, Iterables.transform(zoneList, Zone.TO_PB_FUNCTION)); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextZoneList, Zone.TO_PB_FUNCTION)); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listZones(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.expect(computeRpcMock.listZones(nextOptions)).andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listZones(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(zoneList.toArray(), Iterables.toArray(page.values(), Zone.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextZoneList.toArray(), Iterables.toArray(page.values(), Zone.class)); + } + @Test public void testListEmptyZones() { ImmutableList zones = ImmutableList.of(); @@ -682,6 +967,24 @@ public void testGetLicenseFromStringWithOptions() { assertEquals(LICENSE, license); } + @Test + public void testGetLicenseFromIdWithOptions() { + Capture> capturedOptions = Capture.newInstance(); + LicenseId licenseId = LicenseId.of("project2", "license2"); + EasyMock.expect(computeRpcMock.getLicense(eq(licenseId.project()), eq(licenseId.license()), + capture(capturedOptions))) + .andReturn(LICENSE.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + License license = compute.getLicense(licenseId, LICENSE_OPTION_FIELDS); + assertEquals(LICENSE, license); + String selector = (String) capturedOptions.getValue().get(LICENSE_OPTION_FIELDS.rpcOption()); + assertTrue(selector.contains("selfLink")); + assertTrue(selector.contains("chargesUseFee")); + assertEquals(22, selector.length()); + assertEquals(LICENSE, license); + } + @Test public void testGetLicenseFromId() { LicenseId licenseId = LicenseId.of("project2", "license2"); @@ -695,20 +998,467 @@ public void testGetLicenseFromId() { } @Test - public void testGetLicenseFromIdWithOptions() { + public void testGetGlobalOperation() { + EasyMock.expect( + computeRpcMock.getGlobalOperation(GLOBAL_OPERATION_ID.operation(), EMPTY_RPC_OPTIONS)) + .andReturn(globalOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.get(GLOBAL_OPERATION_ID); + assertEquals(globalOperation, operation); + } + + @Test + public void testGetGlobalOperationWithSelectedFields() { Capture> capturedOptions = Capture.newInstance(); - LicenseId licenseId = LicenseId.of("project2", "license2"); - EasyMock.expect(computeRpcMock.getLicense(eq(licenseId.project()), eq(licenseId.license()), - capture(capturedOptions))) - .andReturn(LICENSE.toPb()); + EasyMock.expect(computeRpcMock.getGlobalOperation( + eq(GLOBAL_OPERATION_ID.operation()), capture(capturedOptions))) + .andReturn(globalOperation.toPb()); EasyMock.replay(computeRpcMock); compute = options.service(); - License license = compute.getLicense(licenseId, LICENSE_OPTION_FIELDS); - assertEquals(LICENSE, license); - String selector = (String) capturedOptions.getValue().get(LICENSE_OPTION_FIELDS.rpcOption()); + Operation operation = compute.get(GLOBAL_OPERATION_ID, OPERATION_OPTION_FIELDS); + String selector = (String) capturedOptions.getValue().get(OPERATION_OPTION_FIELDS.rpcOption()); assertTrue(selector.contains("selfLink")); - assertTrue(selector.contains("chargesUseFee")); - assertEquals(22, selector.length()); - assertEquals(LICENSE, license); + assertTrue(selector.contains("id")); + assertTrue(selector.contains("description")); + assertEquals(23, selector.length()); + assertEquals(globalOperation, operation); + } + + @Test + public void testListGlobalOperations() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(globalOperation, globalOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + EasyMock.expect(computeRpcMock.listGlobalOperations(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listGlobalOperations(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListGlobalOperationsNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(globalOperation, globalOperation); + ImmutableList nextOperationList = ImmutableList.of(globalOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextOperationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listGlobalOperations(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.expect(computeRpcMock.listGlobalOperations(nextOptions)).andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listGlobalOperations(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextOperationList.toArray(), + Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListEmptyGlobalOperations() { + ImmutableList operations = ImmutableList.of(); + Tuple> result = + Tuple.>of(null, + operations); + EasyMock.expect(computeRpcMock.listGlobalOperations(EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Page page = compute.listGlobalOperations(); + assertNull(page.nextPageCursor()); + assertArrayEquals(operations.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListGlobalOperationsWithOptions() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(globalOperation, globalOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + EasyMock.expect(computeRpcMock.listGlobalOperations(OPERATION_LIST_OPTIONS)).andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listGlobalOperations(OPERATION_LIST_MAX_RESULTS, + OPERATION_LIST_PAGE_TOKEN, OPERATION_LIST_FILTER); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testDeleteGlobalOperation_True() { + EasyMock.expect(computeRpcMock.deleteGlobalOperation(GLOBAL_OPERATION_ID.operation())) + .andReturn(true); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertTrue(compute.delete(GLOBAL_OPERATION_ID)); + } + + @Test + public void testDeleteGlobalOperation_False() { + EasyMock.expect(computeRpcMock.deleteGlobalOperation(GLOBAL_OPERATION_ID.operation())) + .andReturn(false); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertFalse(compute.delete(GLOBAL_OPERATION_ID)); + } + + @Test + public void testGetRegionOperation() { + EasyMock.expect(computeRpcMock.getRegionOperation(REGION_OPERATION_ID.region(), + REGION_OPERATION_ID.operation(), EMPTY_RPC_OPTIONS)) + .andReturn(regionOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.get(REGION_OPERATION_ID); + assertEquals(regionOperation, operation); + } + + @Test + public void testGetRegionOperationWithSelectedFields() { + Capture> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.getRegionOperation(eq(REGION_OPERATION_ID.region()), + eq(REGION_OPERATION_ID.operation()), capture(capturedOptions))) + .andReturn(regionOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.get(REGION_OPERATION_ID, OPERATION_OPTION_FIELDS); + String selector = (String) capturedOptions.getValue().get(OPERATION_OPTION_FIELDS.rpcOption()); + assertTrue(selector.contains("selfLink")); + assertTrue(selector.contains("id")); + assertTrue(selector.contains("description")); + assertEquals(23, selector.length()); + assertEquals(regionOperation, operation); + } + + @Test + public void testListRegionOperations() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(regionOperation, regionOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + EasyMock.expect( + computeRpcMock.listRegionOperations(REGION_OPERATION_ID.region(), EMPTY_RPC_OPTIONS)) + .andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listRegionOperations(REGION_OPERATION_ID.region()); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListRegionOperationsNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(regionOperation, regionOperation); + ImmutableList nextOperationList = ImmutableList.of(regionOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextOperationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listRegionOperations(REGION_OPERATION_ID.region(), + EMPTY_RPC_OPTIONS)).andReturn(result); + EasyMock.expect(computeRpcMock.listRegionOperations(REGION_OPERATION_ID.region(), + nextOptions)).andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listRegionOperations(REGION_OPERATION_ID.region()); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextOperationList.toArray(), + Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListEmptyRegionOperations() { + ImmutableList operations = ImmutableList.of(); + Tuple> result = + Tuple.>of(null, + operations); + EasyMock.expect( + computeRpcMock.listRegionOperations(REGION_OPERATION_ID.region(), EMPTY_RPC_OPTIONS)) + .andReturn(result); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Page page = compute.listRegionOperations(REGION_OPERATION_ID.region()); + assertNull(page.nextPageCursor()); + assertArrayEquals(operations.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListRegionOperationsWithOptions() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(regionOperation, regionOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + EasyMock.expect( + computeRpcMock.listRegionOperations(REGION_OPERATION_ID.region(), OPERATION_LIST_OPTIONS)) + .andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listRegionOperations(REGION_OPERATION_ID.region(), + OPERATION_LIST_MAX_RESULTS, OPERATION_LIST_PAGE_TOKEN, OPERATION_LIST_FILTER); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testDeleteRegionOperation_True() { + EasyMock.expect(computeRpcMock.deleteRegionOperation(REGION_OPERATION_ID.region(), + REGION_OPERATION_ID.operation())).andReturn(true); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertTrue(compute.delete(REGION_OPERATION_ID)); + } + + @Test + public void testDeleteRegionOperation_False() { + EasyMock.expect(computeRpcMock.deleteRegionOperation(REGION_OPERATION_ID.region(), + REGION_OPERATION_ID.operation())).andReturn(false); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertFalse(compute.delete(REGION_OPERATION_ID)); + } + + @Test + public void testGetZoneOperation() { + EasyMock.expect(computeRpcMock.getZoneOperation(ZONE_OPERATION_ID.zone(), + ZONE_OPERATION_ID.operation(), EMPTY_RPC_OPTIONS)) + .andReturn(zoneOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.get(ZONE_OPERATION_ID); + assertEquals(zoneOperation, operation); + } + + @Test + public void testGetZoneOperationWithSelectedFields() { + Capture> capturedOptions = Capture.newInstance(); + EasyMock.expect(computeRpcMock.getZoneOperation(eq(ZONE_OPERATION_ID.zone()), + eq(ZONE_OPERATION_ID.operation()), capture(capturedOptions))) + .andReturn(zoneOperation.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Operation operation = compute.get(ZONE_OPERATION_ID, OPERATION_OPTION_FIELDS); + String selector = (String) capturedOptions.getValue().get(OPERATION_OPTION_FIELDS.rpcOption()); + assertTrue(selector.contains("selfLink")); + assertTrue(selector.contains("id")); + assertTrue(selector.contains("description")); + assertEquals(23, selector.length()); + assertEquals(zoneOperation, operation); + } + + @Test + public void testListZoneOperations() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(zoneOperation, zoneOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + EasyMock.expect( + computeRpcMock.listZoneOperations(ZONE_OPERATION_ID.zone(), EMPTY_RPC_OPTIONS)) + .andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listZoneOperations(ZONE_OPERATION_ID.zone()); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListZoneOperationsNextPage() { + String cursor = "cursor"; + String nextCursor = "nextCursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(zoneOperation, zoneOperation); + ImmutableList nextOperationList = ImmutableList.of(zoneOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + Tuple> nextResult = + Tuple.of(nextCursor, Iterables.transform(nextOperationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + Map nextOptions = ImmutableMap.of(PAGE_TOKEN, cursor); + EasyMock.expect(computeRpcMock.listZoneOperations(ZONE_OPERATION_ID.zone(), EMPTY_RPC_OPTIONS)) + .andReturn(result); + EasyMock.expect(computeRpcMock.listZoneOperations(ZONE_OPERATION_ID.zone(), nextOptions)) + .andReturn(nextResult); + EasyMock.replay(computeRpcMock); + Page page = compute.listZoneOperations(ZONE_OPERATION_ID.zone()); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + page = page.nextPage(); + assertEquals(nextCursor, page.nextPageCursor()); + assertArrayEquals(nextOperationList.toArray(), + Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListEmptyZoneOperations() { + ImmutableList operations = ImmutableList.of(); + Tuple> result = + Tuple.>of(null, + operations); + EasyMock.expect( + computeRpcMock.listZoneOperations(ZONE_OPERATION_ID.zone(), EMPTY_RPC_OPTIONS)) + .andReturn(result); + EasyMock.replay(computeRpcMock); + compute = options.service(); + Page page = compute.listZoneOperations(ZONE_OPERATION_ID.zone()); + assertNull(page.nextPageCursor()); + assertArrayEquals(operations.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testListZoneOperationsWithOptions() { + String cursor = "cursor"; + compute = options.service(); + ImmutableList operationList = ImmutableList.of(zoneOperation, zoneOperation); + Tuple> result = + Tuple.of(cursor, Iterables.transform(operationList, + new Function() { + @Override + public com.google.api.services.compute.model.Operation apply(Operation operation) { + return operation.toPb(); + } + })); + EasyMock.expect( + computeRpcMock.listZoneOperations(ZONE_OPERATION_ID.zone(), OPERATION_LIST_OPTIONS)) + .andReturn(result); + EasyMock.replay(computeRpcMock); + Page page = compute.listZoneOperations(ZONE_OPERATION_ID.zone(), + OPERATION_LIST_MAX_RESULTS, OPERATION_LIST_PAGE_TOKEN, OPERATION_LIST_FILTER); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(operationList.toArray(), Iterables.toArray(page.values(), Operation.class)); + } + + @Test + public void testDeleteZoneOperation_True() { + EasyMock.expect(computeRpcMock.deleteZoneOperation(ZONE_OPERATION_ID.zone(), + ZONE_OPERATION_ID.operation())).andReturn(true); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertTrue(compute.delete(ZONE_OPERATION_ID)); + } + + @Test + public void testDeleteZoneOperation_False() { + EasyMock.expect(computeRpcMock.deleteZoneOperation(ZONE_OPERATION_ID.zone(), + ZONE_OPERATION_ID.operation())).andReturn(false); + EasyMock.replay(computeRpcMock); + compute = options.service(); + assertFalse(compute.delete(ZONE_OPERATION_ID)); + } + + @Test + public void testRetryableException() { + EasyMock.expect( + computeRpcMock.getDiskType(DISK_TYPE_ID.zone(), DISK_TYPE_ID.diskType(), EMPTY_RPC_OPTIONS)) + .andThrow(new ComputeException(500, "InternalError")) + .andReturn(DISK_TYPE.toPb()); + EasyMock.replay(computeRpcMock); + compute = options.toBuilder().retryParams(RetryParams.defaultInstance()).build().service(); + DiskType diskType = compute.getDiskType(DISK_TYPE_ID); + assertEquals(DISK_TYPE, diskType); + } + + @Test + public void testNonRetryableException() { + String exceptionMessage = "Not Implemented"; + EasyMock.expect( + computeRpcMock.getDiskType(DISK_TYPE_ID.zone(), DISK_TYPE_ID.diskType(), EMPTY_RPC_OPTIONS)) + .andThrow(new ComputeException(501, exceptionMessage)); + EasyMock.replay(computeRpcMock); + compute = options.toBuilder().retryParams(RetryParams.defaultInstance()).build().service(); + thrown.expect(ComputeException.class); + thrown.expectMessage(exceptionMessage); + compute.getDiskType(DISK_TYPE_ID); + } + + @Test + public void testRuntimeException() { + String exceptionMessage = "Artificial runtime exception"; + EasyMock.expect( + computeRpcMock.getDiskType(DISK_TYPE_ID.zone(), DISK_TYPE_ID.diskType(), EMPTY_RPC_OPTIONS)) + .andThrow(new RuntimeException(exceptionMessage)); + EasyMock.replay(computeRpcMock); + compute = options.toBuilder().retryParams(RetryParams.defaultInstance()).build().service(); + thrown.expect(ComputeException.class); + thrown.expectMessage(exceptionMessage); + compute.getDiskType(DISK_TYPE_ID); } } diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationIdTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationIdTest.java new file mode 100644 index 000000000000..76a53cdd1cd2 --- /dev/null +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationIdTest.java @@ -0,0 +1,169 @@ +/* + * 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class OperationIdTest { + + private static final String PROJECT = "project"; + private static final String ZONE = "zone"; + private static final String REGION = "region"; + private static final String NAME = "op"; + private static final String GLOBAL_URL = + "https://www.googleapis.com/compute/v1/projects/project/global/operations/op"; + private static final String ZONE_URL = + "https://www.googleapis.com/compute/v1/projects/project/zones/zone/operations/op"; + private static final String REGION_URL = + "https://www.googleapis.com/compute/v1/projects/project/regions/region/operations/op"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testOf() { + GlobalOperationId operationId = GlobalOperationId.of(PROJECT, NAME); + assertEquals(OperationId.Type.GLOBAL, operationId.type()); + assertEquals(PROJECT, operationId.project()); + assertEquals(NAME, operationId.operation()); + assertEquals(GLOBAL_URL, operationId.selfLink()); + operationId = GlobalOperationId.of(NAME); + assertEquals(OperationId.Type.GLOBAL, operationId.type()); + assertNull(operationId.project()); + assertEquals(NAME, operationId.operation()); + ZoneOperationId zoneOperationId = ZoneOperationId.of(PROJECT, ZONE, NAME); + assertEquals(OperationId.Type.ZONE, zoneOperationId.type()); + assertEquals(PROJECT, zoneOperationId.project()); + assertEquals(ZONE, zoneOperationId.zone()); + assertEquals(NAME, zoneOperationId.operation()); + assertEquals(ZONE_URL, zoneOperationId.selfLink()); + zoneOperationId = ZoneOperationId.of(ZONE, NAME); + assertEquals(OperationId.Type.ZONE, zoneOperationId.type()); + assertNull(zoneOperationId.project()); + assertEquals(ZONE, zoneOperationId.zone()); + assertEquals(NAME, zoneOperationId.operation()); + zoneOperationId = ZoneOperationId.of(ZoneId.of(PROJECT, ZONE), NAME); + assertEquals(OperationId.Type.ZONE, zoneOperationId.type()); + assertEquals(PROJECT, zoneOperationId.project()); + assertEquals(ZONE, zoneOperationId.zone()); + assertEquals(NAME, zoneOperationId.operation()); + RegionOperationId regionOperationId = RegionOperationId.of(PROJECT, REGION, NAME); + assertEquals(OperationId.Type.REGION, regionOperationId.type()); + assertEquals(PROJECT, regionOperationId.project()); + assertEquals(REGION, regionOperationId.region()); + assertEquals(NAME, regionOperationId.operation()); + assertEquals(REGION_URL, regionOperationId.selfLink()); + regionOperationId = RegionOperationId.of(REGION, NAME); + assertEquals(OperationId.Type.REGION, regionOperationId.type()); + assertNull(regionOperationId.project()); + assertEquals(REGION, regionOperationId.region()); + assertEquals(NAME, regionOperationId.operation()); + regionOperationId = RegionOperationId.of(RegionId.of(PROJECT, REGION), NAME); + assertEquals(OperationId.Type.REGION, regionOperationId.type()); + assertEquals(PROJECT, regionOperationId.project()); + assertEquals(REGION, regionOperationId.region()); + assertEquals(NAME, regionOperationId.operation()); + } + + @Test + public void testToAndFromUrlGlobal() { + GlobalOperationId operationId = GlobalOperationId.of(PROJECT, NAME); + compareOperationId(operationId, GlobalOperationId.fromUrl(operationId.selfLink())); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("notMatchingUrl is not a valid global operation URL"); + GlobalOperationId.fromUrl("notMatchingUrl"); + } + + @Test + public void testToAndFromUrlRegion() { + RegionOperationId regionOperationId = RegionOperationId.of(PROJECT, REGION, NAME); + compareRegionOperationId(regionOperationId, + RegionOperationId.fromUrl(regionOperationId.selfLink())); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("notMatchingUrl is not a valid region operation URL"); + RegionOperationId.fromUrl("notMatchingUrl"); + } + + @Test + public void testToAndFromUrlZone() { + ZoneOperationId zoneOperationId = ZoneOperationId.of(PROJECT, ZONE, NAME); + compareZoneOperationId(zoneOperationId, ZoneOperationId.fromUrl(zoneOperationId.selfLink())); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("notMatchingUrl is not a valid zone operation URL"); + ZoneOperationId.fromUrl("notMatchingUrl"); + } + + @Test + public void testSetProjectId() { + GlobalOperationId operationId = GlobalOperationId.of(PROJECT, NAME); + assertSame(operationId, operationId.setProjectId(PROJECT)); + compareOperationId(operationId, GlobalOperationId.of(NAME).setProjectId(PROJECT)); + ZoneOperationId zoneOperationId = ZoneOperationId.of(PROJECT, ZONE, NAME); + assertSame(zoneOperationId, zoneOperationId.setProjectId(PROJECT)); + compareZoneOperationId(zoneOperationId, ZoneOperationId.of(ZONE, NAME).setProjectId(PROJECT)); + RegionOperationId regionOperationId = RegionOperationId.of(PROJECT, REGION, NAME); + assertSame(regionOperationId, regionOperationId.setProjectId(PROJECT)); + compareRegionOperationId(regionOperationId, + RegionOperationId.of(REGION, NAME).setProjectId(PROJECT)); + } + + @Test + public void testMatchesUrl() { + assertTrue(GlobalOperationId.matchesUrl(GlobalOperationId.of(PROJECT, NAME).selfLink())); + assertFalse(GlobalOperationId.matchesUrl("notMatchingUrl")); + assertTrue( + RegionOperationId.matchesUrl(RegionOperationId.of(PROJECT, REGION, NAME).selfLink())); + assertFalse(RegionOperationId.matchesUrl("notMatchingUrl")); + assertTrue(ZoneOperationId.matchesUrl(ZoneOperationId.of(PROJECT, REGION, NAME).selfLink())); + assertFalse(ZoneOperationId.matchesUrl("notMatchingUrl")); + } + + private void compareOperationId(GlobalOperationId expected, GlobalOperationId value) { + assertEquals(expected, value); + assertEquals(expected.project(), expected.project()); + assertEquals(expected.operation(), expected.operation()); + assertEquals(expected.selfLink(), expected.selfLink()); + assertEquals(expected.hashCode(), expected.hashCode()); + } + + private void compareZoneOperationId(ZoneOperationId expected, ZoneOperationId value) { + assertEquals(expected, value); + assertEquals(expected.project(), expected.project()); + assertEquals(expected.zone(), expected.zone()); + assertEquals(expected.operation(), expected.operation()); + assertEquals(expected.selfLink(), expected.selfLink()); + assertEquals(expected.hashCode(), expected.hashCode()); + } + + private void compareRegionOperationId(RegionOperationId expected, RegionOperationId value) { + assertEquals(expected, value); + assertEquals(expected.type(), value.type()); + assertEquals(expected.project(), expected.project()); + assertEquals(expected.region(), expected.region()); + assertEquals(expected.operation(), expected.operation()); + assertEquals(expected.selfLink(), expected.selfLink()); + assertEquals(expected.hashCode(), expected.hashCode()); + } +} diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java new file mode 100644 index 000000000000..9f08491a1632 --- /dev/null +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java @@ -0,0 +1,424 @@ +/* + * 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 org.easymock.EasyMock.createMock; +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.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; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.gcloud.compute.Operation.OperationError; +import com.google.gcloud.compute.Operation.OperationWarning; +import com.google.gcloud.compute.Operation.Status; + +import org.junit.After; +import org.junit.Test; + +import java.util.List; + +public class OperationTest { + + private static final OperationError OPERATION_ERROR1 = + new OperationError("code1", "location1", "message1"); + private static final OperationError OPERATION_ERROR2 = + new OperationError("code2", "location2", "message2"); + private static final OperationWarning OPERATION_WARNING1 = + new OperationWarning("code1", "message1", ImmutableMap.of("k1", "v1")); + private static final OperationWarning OPERATION_WARNING2 = + new OperationWarning("code2", "location2", ImmutableMap.of("k2", "v2")); + private static final String ID = "1"; + private static final Long CREATION_TIMESTAMP = 1453293540000L; + private static final String CLIENT_OPERATION_ID = "clientOperationId"; + private static final String OPERATION_TYPE = "delete"; + private static final String TARGET_LINK = "targetLink"; + private static final String TARGET_ID = "42"; + private static final Status STATUS = Status.DONE; + private static final String STATUS_MESSAGE = "statusMessage"; + private static final String USER = "user"; + private static final Integer PROGRESS = 100; + private static final Long INSERT_TIME = 1453293540000L; + private static final Long START_TIME = 1453293420000L; + private static final Long END_TIME = 1453293480000L; + private static final List ERRORS = + ImmutableList.of(OPERATION_ERROR1, OPERATION_ERROR2); + private static final List WARNINGS = + ImmutableList.of(OPERATION_WARNING1, OPERATION_WARNING2); + private static final Integer HTTP_ERROR_STATUS_CODE = 404; + private static final String HTTP_ERROR_MESSAGE = "NOT FOUND"; + private static final String DESCRIPTION = "description"; + private static final GlobalOperationId GLOBAL_OPERATION_ID = + GlobalOperationId.of("project", "op"); + private static final ZoneOperationId ZONE_OPERATION_ID = + ZoneOperationId.of("project", "zone", "op"); + private static final RegionOperationId REGION_OPERATION_ID = + RegionOperationId.of("project", "region", "op"); + + private Compute serviceMockReturnsOptions = createStrictMock(Compute.class); + private ComputeOptions mockOptions = createMock(ComputeOptions.class); + private Compute compute; + private Operation globalOperation; + private Operation regionOperation; + private Operation zoneOperation; + private Operation operation; + + private void initializeExpectedOperation(int optionsCalls) { + expect(serviceMockReturnsOptions.options()).andReturn(mockOptions).times(optionsCalls); + replay(serviceMockReturnsOptions); + globalOperation = new Operation.Builder(serviceMockReturnsOptions) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .operationId(GLOBAL_OPERATION_ID) + .clientOperationId(CLIENT_OPERATION_ID) + .operationType(OPERATION_TYPE) + .targetLink(TARGET_LINK) + .targetId(TARGET_ID) + .status(STATUS) + .statusMessage(STATUS_MESSAGE) + .user(USER) + .progress(PROGRESS) + .insertTime(INSERT_TIME) + .startTime(START_TIME) + .endTime(END_TIME) + .errors(ERRORS) + .warnings(WARNINGS) + .httpErrorStatusCode(HTTP_ERROR_STATUS_CODE) + .httpErrorMessage(HTTP_ERROR_MESSAGE) + .description(DESCRIPTION) + .build(); + zoneOperation = new Operation.Builder(serviceMockReturnsOptions) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .operationId(ZONE_OPERATION_ID) + .clientOperationId(CLIENT_OPERATION_ID) + .operationType(OPERATION_TYPE) + .targetLink(TARGET_LINK) + .targetId(TARGET_ID) + .status(STATUS) + .statusMessage(STATUS_MESSAGE) + .user(USER) + .progress(PROGRESS) + .insertTime(INSERT_TIME) + .startTime(START_TIME) + .endTime(END_TIME) + .errors(ERRORS) + .warnings(WARNINGS) + .httpErrorStatusCode(HTTP_ERROR_STATUS_CODE) + .httpErrorMessage(HTTP_ERROR_MESSAGE) + .description(DESCRIPTION) + .build(); + regionOperation = new Operation.Builder(serviceMockReturnsOptions) + .id(ID) + .creationTimestamp(CREATION_TIMESTAMP) + .operationId(REGION_OPERATION_ID) + .clientOperationId(CLIENT_OPERATION_ID) + .operationType(OPERATION_TYPE) + .targetLink(TARGET_LINK) + .targetId(TARGET_ID) + .status(STATUS) + .statusMessage(STATUS_MESSAGE) + .user(USER) + .progress(PROGRESS) + .insertTime(INSERT_TIME) + .startTime(START_TIME) + .endTime(END_TIME) + .errors(ERRORS) + .warnings(WARNINGS) + .httpErrorStatusCode(HTTP_ERROR_STATUS_CODE) + .httpErrorMessage(HTTP_ERROR_MESSAGE) + .description(DESCRIPTION) + .build(); + compute = createStrictMock(Compute.class); + } + + private void initializeOperation() { + operation = new Operation.Builder(compute) + .id(ID) + .operationId(GLOBAL_OPERATION_ID) + .creationTimestamp(CREATION_TIMESTAMP) + .clientOperationId(CLIENT_OPERATION_ID) + .operationType(OPERATION_TYPE) + .targetLink(TARGET_LINK) + .targetId(TARGET_ID) + .status(STATUS) + .statusMessage(STATUS_MESSAGE) + .user(USER) + .progress(PROGRESS) + .insertTime(INSERT_TIME) + .startTime(START_TIME) + .endTime(END_TIME) + .errors(ERRORS) + .warnings(WARNINGS) + .httpErrorStatusCode(HTTP_ERROR_STATUS_CODE) + .httpErrorMessage(HTTP_ERROR_MESSAGE) + .description(DESCRIPTION) + .build(); + } + + @After + public void tearDown() throws Exception { + verify(serviceMockReturnsOptions); + } + + private void assertEqualsCommonFields(Operation operation) { + assertEquals(CREATION_TIMESTAMP, operation.creationTimestamp()); + assertEquals(ID, operation.id()); + assertEquals(CLIENT_OPERATION_ID, operation.clientOperationId()); + assertEquals(OPERATION_TYPE, operation.operationType()); + assertEquals(TARGET_LINK, operation.targetLink()); + assertEquals(TARGET_ID, operation.targetId()); + assertEquals(STATUS, operation.status()); + assertEquals(STATUS_MESSAGE, operation.statusMessage()); + assertEquals(USER, operation.user()); + assertEquals(PROGRESS, operation.progress()); + assertEquals(INSERT_TIME, operation.insertTime()); + assertEquals(START_TIME, operation.startTime()); + assertEquals(END_TIME, operation.endTime()); + assertEquals(ERRORS, operation.errors()); + assertEquals(WARNINGS, operation.warnings()); + assertEquals(HTTP_ERROR_STATUS_CODE, globalOperation.httpErrorStatusCode()); + assertEquals(HTTP_ERROR_MESSAGE, globalOperation.httpErrorMessage()); + assertEquals(DESCRIPTION, globalOperation.description()); + assertSame(serviceMockReturnsOptions, globalOperation.compute()); + } + + private void assertNullCommonFields(Operation operation) { + assertNull(operation.creationTimestamp()); + assertNull(operation.id()); + assertNull(operation.clientOperationId()); + assertNull(operation.operationType()); + assertNull(operation.targetLink()); + assertNull(operation.targetId()); + assertNull(operation.status()); + assertNull(operation.statusMessage()); + assertNull(operation.user()); + assertNull(operation.progress()); + assertNull(operation.insertTime()); + assertNull(operation.startTime()); + assertNull(operation.endTime()); + assertNull(operation.errors()); + assertNull(operation.warnings()); + assertNull(operation.httpErrorStatusCode()); + assertNull(operation.httpErrorMessage()); + assertNull(operation.description()); + assertSame(serviceMockReturnsOptions, operation.compute()); + } + + @Test + public void testBuilder() { + initializeExpectedOperation(6); + assertEqualsCommonFields(globalOperation); + assertEquals(GLOBAL_OPERATION_ID, globalOperation.operationId()); + assertEqualsCommonFields(regionOperation); + assertEquals(REGION_OPERATION_ID, regionOperation.operationId()); + assertEqualsCommonFields(zoneOperation); + assertEquals(ZONE_OPERATION_ID, zoneOperation.operationId()); + Operation operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(GLOBAL_OPERATION_ID) + .build(); + assertNullCommonFields(operation); + assertEquals(GLOBAL_OPERATION_ID, operation.operationId()); + operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(ZONE_OPERATION_ID) + .build(); + assertNullCommonFields(operation); + assertEquals(ZONE_OPERATION_ID, operation.operationId()); + operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(REGION_OPERATION_ID) + .build(); + assertNullCommonFields(operation); + assertEquals(REGION_OPERATION_ID, operation.operationId()); + } + + @Test + public void testToAndFromPb() { + initializeExpectedOperation(24); + compareOperation(globalOperation, + Operation.fromPb(serviceMockReturnsOptions, globalOperation.toPb())); + assertNotNull(regionOperation.toPb().getRegion()); + compareOperation(regionOperation, + Operation.fromPb(serviceMockReturnsOptions, regionOperation.toPb())); + assertNotNull(zoneOperation.toPb().getZone()); + compareOperation(zoneOperation, + Operation.fromPb(serviceMockReturnsOptions, zoneOperation.toPb())); + Operation operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(GLOBAL_OPERATION_ID) + .build(); + compareOperation(operation, Operation.fromPb(serviceMockReturnsOptions, operation.toPb())); + operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(ZONE_OPERATION_ID) + .build(); + compareOperation(operation, Operation.fromPb(serviceMockReturnsOptions, operation.toPb())); + operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(REGION_OPERATION_ID) + .build(); + compareOperation(operation, Operation.fromPb(serviceMockReturnsOptions, operation.toPb())); + } + + @Test + public void testDeleteTrue() { + initializeExpectedOperation(3); + expect(compute.options()).andReturn(mockOptions); + expect(compute.delete(GLOBAL_OPERATION_ID)).andReturn(true); + replay(compute); + initializeOperation(); + assertTrue(operation.delete()); + verify(compute); + } + + @Test + public void testDeleteFalse() { + initializeExpectedOperation(3); + expect(compute.options()).andReturn(mockOptions); + expect(compute.delete(GLOBAL_OPERATION_ID)).andReturn(false); + replay(compute); + initializeOperation(); + assertFalse(operation.delete()); + verify(compute); + } + + @Test + public void testExists_True() throws Exception { + initializeExpectedOperation(3); + Compute.OperationOption[] expectedOptions = {Compute.OperationOption.fields()}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(globalOperation); + replay(compute); + initializeOperation(); + assertTrue(operation.exists()); + verify(compute); + } + + @Test + public void testExists_False() throws Exception { + initializeExpectedOperation(3); + Compute.OperationOption[] expectedOptions = {Compute.OperationOption.fields()}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(null); + replay(compute); + initializeOperation(); + assertFalse(operation.exists()); + verify(compute); + } + + @Test + public void testIsDone_True() throws Exception { + initializeExpectedOperation(3); + Compute.OperationOption[] expectedOptions = + {Compute.OperationOption.fields(Compute.OperationField.STATUS)}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(globalOperation); + replay(compute); + initializeOperation(); + assertTrue(operation.isDone()); + verify(compute); + } + + @Test + public void testIsDone_False() throws Exception { + initializeExpectedOperation(4); + Compute.OperationOption[] expectedOptions = + {Compute.OperationOption.fields(Compute.OperationField.STATUS)}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(GLOBAL_OPERATION_ID, expectedOptions)).andReturn( + Operation.fromPb(serviceMockReturnsOptions, globalOperation.toPb().setStatus("PENDING"))); + replay(compute); + initializeOperation(); + assertFalse(operation.isDone()); + verify(compute); + } + @Test + public void testIsDone_NotExists() throws Exception { + initializeExpectedOperation(3); + Compute.OperationOption[] expectedOptions = + {Compute.OperationOption.fields(Compute.OperationField.STATUS)}; + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(null); + replay(compute); + initializeOperation(); + assertFalse(operation.isDone()); + verify(compute); + } + + @Test + public void testReload() throws Exception { + initializeExpectedOperation(5); + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(GLOBAL_OPERATION_ID)).andReturn(globalOperation); + replay(compute); + initializeOperation(); + Operation updatedOperation = operation.reload(); + compareOperation(globalOperation, updatedOperation); + verify(compute); + } + + @Test + public void testReloadNull() throws Exception { + initializeExpectedOperation(3); + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(GLOBAL_OPERATION_ID)).andReturn(null); + replay(compute); + initializeOperation(); + assertNull(operation.reload()); + verify(compute); + } + + @Test + public void testReloadWithOptions() throws Exception { + initializeExpectedOperation(5); + expect(compute.options()).andReturn(mockOptions); + expect(compute.get(GLOBAL_OPERATION_ID, Compute.OperationOption.fields())) + .andReturn(globalOperation); + replay(compute); + initializeOperation(); + Operation updatedOperation = operation.reload(Compute.OperationOption.fields()); + compareOperation(globalOperation, updatedOperation); + verify(compute); + } + + private void compareOperation(Operation expected, Operation value) { + assertEquals(expected, value); + assertEquals(expected.compute().options(), value.compute().options()); + assertEquals(expected.creationTimestamp(), value.creationTimestamp()); + assertEquals(expected.operationId(), value.operationId()); + assertEquals(expected.clientOperationId(), value.clientOperationId()); + assertEquals(expected.operationType(), value.operationType()); + assertEquals(expected.targetLink(), value.targetLink()); + assertEquals(expected.targetId(), value.targetId()); + assertEquals(expected.status(), value.status()); + assertEquals(expected.statusMessage(), value.statusMessage()); + assertEquals(expected.user(), value.user()); + assertEquals(expected.progress(), value.progress()); + assertEquals(expected.insertTime(), value.insertTime()); + assertEquals(expected.startTime(), value.startTime()); + assertEquals(expected.endTime(), value.endTime()); + assertEquals(expected.errors(), value.errors()); + assertEquals(expected.warnings(), value.warnings()); + assertEquals(expected.httpErrorStatusCode(), value.httpErrorStatusCode()); + assertEquals(expected.httpErrorMessage(), value.httpErrorMessage()); + assertEquals(expected.description(), value.description()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SerializationTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SerializationTest.java index 74c062fd6c27..a73cf860810f 100644 --- a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SerializationTest.java +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/SerializationTest.java @@ -36,6 +36,7 @@ public class SerializationTest { + private static final Compute COMPUTE = ComputeOptions.builder().projectId("p").build().service(); private static final String ID = "42"; private static final Long CREATION_TIMESTAMP = 1453293540000L; private static final String DESCRIPTION = "description"; @@ -113,7 +114,50 @@ public class SerializationTest { private static final LicenseId LICENSE_ID = LicenseId.of("project", "license"); private static final Boolean CHARGES_USE_FEE = true; private static final License LICENSE = new License(LICENSE_ID, CHARGES_USE_FEE); - + private static final GlobalOperationId GLOBAL_OPERATION_ID = + GlobalOperationId.of("project", "op"); + private static final ZoneOperationId ZONE_OPERATION_ID = + ZoneOperationId.of("project", "zone", "op"); + private static final RegionOperationId REGION_OPERATION_ID = + RegionOperationId.of("project", "region", "op"); + private static final Operation GLOBAL_OPERATION = + new Operation.Builder(COMPUTE).operationId(GLOBAL_OPERATION_ID).build(); + private static final Operation ZONE_OPERATION = + new Operation.Builder(COMPUTE).operationId(ZONE_OPERATION_ID).build(); + private static final Operation REGION_OPERATION = + new Operation.Builder(COMPUTE).operationId(REGION_OPERATION_ID).build(); + private static final Compute.DiskTypeOption DISK_TYPE_OPTION = + Compute.DiskTypeOption.fields(); + private static final Compute.DiskTypeFilter DISK_TYPE_FILTER = + Compute.DiskTypeFilter.equals(Compute.DiskTypeField.SELF_LINK, "selfLink"); + private static final Compute.DiskTypeListOption DISK_TYPE_LIST_OPTION = + Compute.DiskTypeListOption.filter(DISK_TYPE_FILTER); + private static final Compute.DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_OPTION = + Compute.DiskTypeAggregatedListOption.filter(DISK_TYPE_FILTER); + private static final Compute.MachineTypeOption MACHINE_TYPE_OPTION = + Compute.MachineTypeOption.fields(); + private static final Compute.MachineTypeFilter MACHINE_TYPE_FILTER = + Compute.MachineTypeFilter.equals(Compute.MachineTypeField.SELF_LINK, "selfLink"); + private static final Compute.MachineTypeListOption MACHINE_TYPE_LIST_OPTION = + Compute.MachineTypeListOption.filter(MACHINE_TYPE_FILTER); + private static final Compute.MachineTypeAggregatedListOption MACHINE_TYPE_AGGREGATED_LIST_OPTION = + Compute.MachineTypeAggregatedListOption.filter(MACHINE_TYPE_FILTER); + private static final Compute.RegionOption REGION_OPTION = Compute.RegionOption.fields(); + private static final Compute.RegionFilter REGION_FILTER = + Compute.RegionFilter.equals(Compute.RegionField.SELF_LINK, "selfLink"); + private static final Compute.RegionListOption REGION_LIST_OPTION = + Compute.RegionListOption.filter(REGION_FILTER); + private static final Compute.ZoneOption ZONE_OPTION = Compute.ZoneOption.fields(); + private static final Compute.ZoneFilter ZONE_FILTER = + Compute.ZoneFilter.equals(Compute.ZoneField.SELF_LINK, "selfLink"); + private static final Compute.ZoneListOption ZONE_LIST_OPTION = + Compute.ZoneListOption.filter(ZONE_FILTER); + private static final Compute.LicenseOption LICENSE_OPTION = Compute.LicenseOption.fields(); + private static final Compute.OperationOption OPERATION_OPTION = Compute.OperationOption.fields(); + private static final Compute.OperationFilter OPERATION_FILTER = + Compute.OperationFilter.equals(Compute.OperationField.SELF_LINK, "selfLink"); + private static final Compute.OperationListOption OPERATION_LIST_OPTION = + Compute.OperationListOption.filter(OPERATION_FILTER); @Test public void testServiceOptions() throws Exception { ComputeOptions options = ComputeOptions.builder() @@ -135,7 +179,13 @@ public void testServiceOptions() throws Exception { @Test public void testModelAndRequests() throws Exception { Serializable[] objects = {DISK_TYPE_ID, DISK_TYPE, MACHINE_TYPE_ID, MACHINE_TYPE, REGION_ID, - REGION, ZONE_ID, ZONE, LICENSE_ID, LICENSE, DEPRECATION_STATUS}; + REGION, ZONE_ID, ZONE, LICENSE_ID, LICENSE, DEPRECATION_STATUS, GLOBAL_OPERATION_ID, + REGION_OPERATION_ID, ZONE_OPERATION_ID, GLOBAL_OPERATION, REGION_OPERATION, ZONE_OPERATION, + DISK_TYPE_OPTION, DISK_TYPE_FILTER, DISK_TYPE_LIST_OPTION, DISK_TYPE_AGGREGATED_LIST_OPTION, + MACHINE_TYPE_OPTION, MACHINE_TYPE_FILTER, MACHINE_TYPE_LIST_OPTION, + MACHINE_TYPE_AGGREGATED_LIST_OPTION, REGION_OPTION, REGION_FILTER, REGION_LIST_OPTION, + ZONE_OPTION, ZONE_FILTER, ZONE_LIST_OPTION, LICENSE_OPTION, OPERATION_OPTION, + OPERATION_FILTER, OPERATION_LIST_OPTION}; for (Serializable obj : objects) { Object copy = serializeAndDeserialize(obj); assertEquals(obj, obj); diff --git a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/it/ITComputeTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/it/ITComputeTest.java index 5bc2589e6244..56960903d6c7 100644 --- a/gcloud-java-compute/src/test/java/com/google/gcloud/compute/it/ITComputeTest.java +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/it/ITComputeTest.java @@ -29,8 +29,11 @@ import com.google.gcloud.compute.License; import com.google.gcloud.compute.LicenseId; import com.google.gcloud.compute.MachineType; +import com.google.gcloud.compute.Operation; import com.google.gcloud.compute.Region; +import com.google.gcloud.compute.RegionOperationId; import com.google.gcloud.compute.Zone; +import com.google.gcloud.compute.ZoneOperationId; import com.google.gcloud.compute.testing.RemoteComputeHelper; import org.junit.BeforeClass; @@ -54,7 +57,7 @@ public class ITComputeTest { public Timeout globalTimeout = Timeout.seconds(300); @BeforeClass - public static void beforeClass() throws InterruptedException { + public static void beforeClass() { RemoteComputeHelper computeHelper = RemoteComputeHelper.create(); compute = computeHelper.options().service(); } @@ -91,7 +94,7 @@ public void testListDiskTypes() { Page diskPage = compute.listDiskTypes(ZONE); Iterator diskTypeIterator = diskPage.iterateAll(); assertTrue(diskTypeIterator.hasNext()); - while(diskTypeIterator.hasNext()) { + while (diskTypeIterator.hasNext()) { DiskType diskType = diskTypeIterator.next(); // todo(mziccard): uncomment or remove once #695 is closed // assertNotNull(diskType.id()); @@ -110,7 +113,7 @@ public void testListDiskTypesWithSelectedFields() { Compute.DiskTypeListOption.fields(Compute.DiskTypeField.CREATION_TIMESTAMP)); Iterator diskTypeIterator = diskPage.iterateAll(); assertTrue(diskTypeIterator.hasNext()); - while(diskTypeIterator.hasNext()) { + while (diskTypeIterator.hasNext()) { DiskType diskType = diskTypeIterator.next(); assertNull(diskType.id()); assertNotNull(diskType.diskTypeId()); @@ -128,7 +131,7 @@ public void testListDiskTypesWithFilter() { Compute.DiskTypeFilter.equals(Compute.DiskTypeField.DEFAULT_DISK_SIZE_GB, 375))); Iterator diskTypeIterator = diskPage.iterateAll(); assertTrue(diskTypeIterator.hasNext()); - while(diskTypeIterator.hasNext()) { + while (diskTypeIterator.hasNext()) { DiskType diskType = diskTypeIterator.next(); // todo(mziccard): uncomment or remove once #695 is closed // assertNotNull(diskType.id()); @@ -146,9 +149,8 @@ public void testAggregatedListDiskTypes() { Page diskPage = compute.listDiskTypes(); Iterator diskTypeIterator = diskPage.iterateAll(); assertTrue(diskTypeIterator.hasNext()); - while(diskTypeIterator.hasNext()) { + while (diskTypeIterator.hasNext()) { DiskType diskType = diskTypeIterator.next(); - // todo(mziccard): uncomment or remove once #695 is closed // assertNotNull(diskType.id()); assertNotNull(diskType.diskTypeId()); assertNotNull(diskType.creationTimestamp()); @@ -164,7 +166,7 @@ public void testAggregatedListDiskTypesWithFilter() { Compute.DiskTypeFilter.notEquals(Compute.DiskTypeField.DEFAULT_DISK_SIZE_GB, 375))); Iterator diskTypeIterator = diskPage.iterateAll(); assertTrue(diskTypeIterator.hasNext()); - while(diskTypeIterator.hasNext()) { + while (diskTypeIterator.hasNext()) { DiskType diskType = diskTypeIterator.next(); // todo(mziccard): uncomment or remove once #695 is closed // assertNotNull(diskType.id()); @@ -210,7 +212,7 @@ public void testListMachineTypes() { Page machinePage = compute.listMachineTypes(ZONE); Iterator machineTypeIterator = machinePage.iterateAll(); assertTrue(machineTypeIterator.hasNext()); - while(machineTypeIterator.hasNext()) { + while (machineTypeIterator.hasNext()) { MachineType machineType = machineTypeIterator.next(); assertNotNull(machineType.machineTypeId()); assertEquals(ZONE, machineType.machineTypeId().zone()); @@ -230,7 +232,7 @@ public void testListMachineTypesWithSelectedFields() { Compute.MachineTypeListOption.fields(Compute.MachineTypeField.CREATION_TIMESTAMP)); Iterator machineTypeIterator = machinePage.iterateAll(); assertTrue(machineTypeIterator.hasNext()); - while(machineTypeIterator.hasNext()) { + while (machineTypeIterator.hasNext()) { MachineType machineType = machineTypeIterator.next(); assertNotNull(machineType.machineTypeId()); assertEquals(ZONE, machineType.machineTypeId().zone()); @@ -251,7 +253,7 @@ public void testListMachineTypesWithFilter() { Compute.MachineTypeFilter.equals(Compute.MachineTypeField.GUEST_CPUS, 2))); Iterator machineTypeIterator = machinePage.iterateAll(); assertTrue(machineTypeIterator.hasNext()); - while(machineTypeIterator.hasNext()) { + while (machineTypeIterator.hasNext()) { MachineType machineType = machineTypeIterator.next(); assertNotNull(machineType.machineTypeId()); assertEquals(ZONE, machineType.machineTypeId().zone()); @@ -271,7 +273,7 @@ public void testAggregatedListMachineTypes() { Page machinePage = compute.listMachineTypes(); Iterator machineTypeIterator = machinePage.iterateAll(); assertTrue(machineTypeIterator.hasNext()); - while(machineTypeIterator.hasNext()) { + while (machineTypeIterator.hasNext()) { MachineType machineType = machineTypeIterator.next(); assertNotNull(machineType.machineTypeId()); assertNotNull(machineType.id()); @@ -291,7 +293,7 @@ public void testAggregatedListMachineTypesWithFilter() { Compute.MachineTypeFilter.notEquals(Compute.MachineTypeField.GUEST_CPUS, 2))); Iterator machineTypeIterator = machinePage.iterateAll(); assertTrue(machineTypeIterator.hasNext()); - while(machineTypeIterator.hasNext()) { + while (machineTypeIterator.hasNext()) { MachineType machineType = machineTypeIterator.next(); assertNotNull(machineType.machineTypeId()); assertNotNull(machineType.id()); @@ -347,7 +349,7 @@ public void testGetRegionWithSelectedFields() { public void testListRegions() { Page regionPage = compute.listRegions(); Iterator regionIterator = regionPage.iterateAll(); - while(regionIterator.hasNext()) { + while (regionIterator.hasNext()) { Region region = regionIterator.next(); assertNotNull(region.regionId()); assertNotNull(region.description()); @@ -364,7 +366,7 @@ public void testListRegionsWithSelectedFields() { Page regionPage = compute.listRegions(Compute.RegionListOption.fields(Compute.RegionField.ID)); Iterator regionIterator = regionPage.iterateAll(); - while(regionIterator.hasNext()) { + while (regionIterator.hasNext()) { Region region = regionIterator.next(); assertNotNull(region.regionId()); assertNull(region.description()); @@ -412,7 +414,7 @@ public void testGetZoneWithSelectedFields() { public void testListZones() { Page zonePage = compute.listZones(); Iterator zoneIterator = zonePage.iterateAll(); - while(zoneIterator.hasNext()) { + while (zoneIterator.hasNext()) { Zone zone = zoneIterator.next(); assertNotNull(zone.zoneId()); assertNotNull(zone.id()); @@ -428,7 +430,7 @@ public void testListZonesWithSelectedFields() { Page zonePage = compute.listZones( Compute.ZoneListOption.fields(Compute.ZoneField.CREATION_TIMESTAMP)); Iterator zoneIterator = zonePage.iterateAll(); - while(zoneIterator.hasNext()) { + while (zoneIterator.hasNext()) { Zone zone = zoneIterator.next(); assertNotNull(zone.zoneId()); assertNull(zone.id()); @@ -447,4 +449,192 @@ public void testListZonesWithFilter() { assertEquals(ZONE, zoneIterator.next().zoneId().zone()); assertFalse(zoneIterator.hasNext()); } + + @Test + public void testListGlobalOperations() { + Page operationPage = compute.listGlobalOperations(); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + // todo(mziccard): uncomment or remove once #727 is closed + // assertNotNull(operation.creationTimestamp()); + assertNotNull(operation.operationType()); + assertNotNull(operation.status()); + assertNotNull(operation.user()); + } + } + + @Test + public void testListGlobalOperationsWithSelectedFields() { + Page operationPage = + compute.listGlobalOperations(Compute.OperationListOption.fields(Compute.OperationField.ID)); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + assertNull(operation.operationType()); + assertNull(operation.targetLink()); + assertNull(operation.targetId()); + assertNull(operation.creationTimestamp()); + assertNull(operation.operationType()); + assertNull(operation.status()); + assertNull(operation.statusMessage()); + assertNull(operation.user()); + assertNull(operation.progress()); + assertNull(operation.description()); + assertNull(operation.insertTime()); + assertNull(operation.startTime()); + assertNull(operation.endTime()); + assertNull(operation.warnings()); + assertNull(operation.httpErrorMessage()); + } + } + + @Test + public void testListGlobalOperationsWithFilter() { + Page operationPage = compute.listGlobalOperations(Compute.OperationListOption.filter( + Compute.OperationFilter.equals(Compute.OperationField.STATUS, "DONE"))); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + // todo(mziccard): uncomment or remove once #727 is closed + // assertNotNull(operation.creationTimestamp()); + assertNotNull(operation.operationType()); + assertEquals(Operation.Status.DONE, operation.status()); + assertNotNull(operation.user()); + } + } + + @Test + public void testListRegionOperations() { + Page operationPage = compute.listRegionOperations(REGION); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + assertEquals(REGION, operation.operationId().region()); + // todo(mziccard): uncomment or remove once #727 is closed + // assertNotNull(operation.creationTimestamp()); + assertNotNull(operation.operationType()); + assertNotNull(operation.status()); + assertNotNull(operation.user()); + } + } + + @Test + public void testListRegionOperationsWithSelectedFields() { + Page operationPage = compute.listRegionOperations(REGION, + Compute.OperationListOption.fields(Compute.OperationField.ID)); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + assertEquals(REGION, operation.operationId().region()); + assertNull(operation.operationType()); + assertNull(operation.targetLink()); + assertNull(operation.targetId()); + assertNull(operation.creationTimestamp()); + assertNull(operation.operationType()); + assertNull(operation.status()); + assertNull(operation.statusMessage()); + assertNull(operation.user()); + assertNull(operation.progress()); + assertNull(operation.description()); + assertNull(operation.insertTime()); + assertNull(operation.startTime()); + assertNull(operation.endTime()); + assertNull(operation.warnings()); + assertNull(operation.httpErrorMessage()); + } + } + + @Test + public void testListRegionOperationsWithFilter() { + Page operationPage = compute.listRegionOperations(REGION, + Compute.OperationListOption.filter(Compute.OperationFilter.equals( + Compute.OperationField.STATUS, "DONE"))); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + assertEquals(REGION, operation.operationId().region()); + // todo(mziccard): uncomment or remove once #727 is closed + // assertNotNull(operation.creationTimestamp()); + assertNotNull(operation.operationType()); + assertEquals(Operation.Status.DONE, operation.status()); + assertNotNull(operation.user()); + } + } + + @Test + public void testListZoneOperations() { + Page operationPage = compute.listZoneOperations(ZONE); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + assertEquals(ZONE, operation.operationId().zone()); + // todo(mziccard): uncomment or remove once #727 is closed + // assertNotNull(operation.creationTimestamp()); + assertNotNull(operation.operationType()); + assertNotNull(operation.status()); + assertNotNull(operation.user()); + } + } + + @Test + public void testListZoneOperationsWithSelectedFields() { + Page operationPage = compute.listZoneOperations(ZONE, + Compute.OperationListOption.fields(Compute.OperationField.ID)); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + assertEquals(ZONE, operation.operationId().zone()); + assertNull(operation.operationType()); + assertNull(operation.targetLink()); + assertNull(operation.targetId()); + assertNull(operation.creationTimestamp()); + assertNull(operation.operationType()); + assertNull(operation.status()); + assertNull(operation.statusMessage()); + assertNull(operation.user()); + assertNull(operation.progress()); + assertNull(operation.description()); + assertNull(operation.insertTime()); + assertNull(operation.startTime()); + assertNull(operation.endTime()); + assertNull(operation.warnings()); + assertNull(operation.httpErrorMessage()); + } + } + + @Test + public void testListZoneOperationsWithFilter() { + Page operationPage = compute.listZoneOperations(ZONE, + Compute.OperationListOption.filter(Compute.OperationFilter.equals( + Compute.OperationField.STATUS, "DONE"))); + Iterator operationIterator = operationPage.iterateAll(); + while (operationIterator.hasNext()) { + Operation operation = operationIterator.next(); + assertNotNull(operation.id()); + assertNotNull(operation.operationId()); + assertEquals(ZONE, operation.operationId().zone()); + // todo(mziccard): uncomment or remove once #727 is closed + // assertNotNull(operation.creationTimestamp()); + assertNotNull(operation.operationType()); + assertEquals(Operation.Status.DONE, operation.status()); + assertNotNull(operation.user()); + } + } }