From 231fe428d6fb614a612fc70c40ad30ac678a09e9 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Tue, 8 Mar 2016 15:18:42 +0100 Subject: [PATCH 1/6] Add support for Compute's operations - Add RegionOperationId, ZoneOperationId, GlobalOperationId classes - Add functional methods for operations to Compute's service and rpc classes - Add Operation class (with functional methods) - Add unit and integration tests --- .../com/google/gcloud/compute/Compute.java | 220 +++++ .../google/gcloud/compute/ComputeImpl.java | 214 +++++ .../gcloud/compute/GlobalOperationId.java | 107 +++ .../com/google/gcloud/compute/Operation.java | 800 ++++++++++++++++++ .../google/gcloud/compute/OperationId.java | 38 + .../gcloud/compute/RegionOperationId.java | 112 +++ .../gcloud/compute/ZoneOperationId.java | 114 +++ .../com/google/gcloud/spi/ComputeRpc.java | 67 ++ .../google/gcloud/spi/DefaultComputeRpc.java | 123 ++- .../gcloud/compute/ComputeImplTest.java | 496 ++++++++++- .../gcloud/compute/OperationIdTest.java | 143 ++++ .../google/gcloud/compute/OperationTest.java | 489 +++++++++++ .../gcloud/compute/SerializationTest.java | 54 +- .../gcloud/compute/it/ITComputeTest.java | 193 ++++- 14 files changed, 3151 insertions(+), 19 deletions(-) create mode 100644 gcloud-java-compute/src/main/java/com/google/gcloud/compute/GlobalOperationId.java create mode 100644 gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java create mode 100644 gcloud-java-compute/src/main/java/com/google/gcloud/compute/OperationId.java create mode 100644 gcloud-java-compute/src/main/java/com/google/gcloud/compute/RegionOperationId.java create mode 100644 gcloud-java-compute/src/main/java/com/google/gcloud/compute/ZoneOperationId.java create mode 100644 gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationIdTest.java create mode 100644 gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java 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..4acfab38addb 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,61 @@ 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"), + SELF_LINK("selfLink"), + START_TIME("startTime"), + STATUS("status"), + STATUS_MESSAGE("statusMessage"), + REGION("region"), + TARGET_ID("targetId"), + TARGET_LINK("targetLink"), + USER("user"), + WARNINGS("warnings"); + + 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. */ @@ -436,6 +491,68 @@ public static ZoneFilter notEquals(ZoneField field, String 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 equality 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 an equality 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 equality 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 an inequality filter for the given field and long value. + */ + public static OperationFilter notEquals(OperationField field, long value) { + return new OperationFilter(checkNotNull(field), ComparisonOperator.NE, value); + } + + /** + * Returns an equality filter for the given field and integer value. + */ + public static OperationFilter equals(OperationField field, int value) { + return new OperationFilter(checkNotNull(field), ComparisonOperator.EQ, value); + } + + /** + * Returns an inequality filter for the given field and integer value. + */ + public static OperationFilter notEquals(OperationField field, int value) { + return new OperationFilter(checkNotNull(field), ComparisonOperator.NE, value); + } + } + /** * Class for specifying disk type get options. */ @@ -792,6 +909,73 @@ public static LicenseOption fields(LicenseField... 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 to 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 to be returned. + */ + public static OperationListOption maxResults(long maxResults) { + return new OperationListOption(ComputeRpc.Option.MAX_RESULTS, maxResults); + } + + /** + * 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 +1073,40 @@ 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 in the provided region. + * + * @throws ComputeException upon failure + */ + Page listRegionOperations(String region, OperationListOption... options); + + /** + * Lists the operations in the provided zone. + * + * @throws ComputeException upon failure + */ + Page listZoneOperations(String zone, OperationListOption... options); + + /** + * Deletes the requested operation. + * + * @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..13239d8209b6 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 = 4111705358926164078L; + 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,159 @@ 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() { + if (operationId instanceof RegionOperationId) { + RegionOperationId regionOperationId = (RegionOperationId) operationId; + return computeRpc.getRegionOperation(regionOperationId.region(), + regionOperationId.operation(), optionsMap); + } else if (operationId instanceof ZoneOperationId) { + ZoneOperationId zoneOperationId = (ZoneOperationId) operationId; + return computeRpc.getZoneOperation(zoneOperationId.zone(), + zoneOperationId.operation(), optionsMap); + } else { + return computeRpc.getGlobalOperation(operationId.operation(), optionsMap); + } + } + }, 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() { + if (operation instanceof RegionOperationId) { + RegionOperationId regionOperationId = (RegionOperationId) operation; + return computeRpc.deleteRegionOperation(regionOperationId.region(), + regionOperationId.operation()); + } else if (operation instanceof ZoneOperationId) { + ZoneOperationId zoneOperationId = (ZoneOperationId) operation; + return computeRpc.deleteZoneOperation(zoneOperationId.zone(), + zoneOperationId.operation()); + } else { + return computeRpc.deleteGlobalOperation(operation.operation()); + } + } + }, 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/GlobalOperationId.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/GlobalOperationId.java new file mode 100644 index 000000000000..3c1601e8c4d0 --- /dev/null +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/GlobalOperationId.java @@ -0,0 +1,107 @@ +/* + * 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 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/Operation.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java new file mode 100644 index 000000000000..ac303843ebc4 --- /dev/null +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Operation.java @@ -0,0 +1,800 @@ +/* + * 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.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()}. + * For global operations {@link #operationId()} returns an {@link GlobalOperationId}, for region + * operations {@link #operationId()} returns a {@link RegionOperationId}, for zone operations + * {@link #operationId()} returns a {@link ZoneOperationId}. To get an {@code Operation} object with + * the most recent information use {@link #reload(Compute.OperationOption...)}. + */ +public final 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; + + /** + * Types of operations. + */ + 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. Might be {@code null}. + */ + 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. + */ + public String code() { + return code; + } + + /** + * Returns a human-readable error message. + */ + public String message() { + return message; + } + + /** + * Returns metadata about this warning. + */ + 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); + } + } + + /** + * Builder for Compute Engine operations. + */ + 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; + } + + /** + * Creates an object. + */ + public 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, returns a {@link RegionOperationId} for region operations and returns 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. + */ + public Long startTime() { + return startTime; + } + + /** + * Returns the time that this operation was completed. In milliseconds since epoch. + */ + 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. + * + * @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 int hashCode() { + return Objects.hash(id); + } + + @Override + public 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); + if (operationId instanceof RegionOperationId) { + operationPb.setRegion(this.operationId().regionId().selfLink()); + } + if (operationId instanceof ZoneOperationId) { + operationPb.setZone(this.operationId().zoneId().selfLink()); + } + 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..c7211e97ca2d --- /dev/null +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/OperationId.java @@ -0,0 +1,38 @@ +/* + * 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 { + + /** + * Returns the name of the project. + */ + String project(); + + /** + * Returns the name of the operation resource. + */ + String operation(); + + /** + * Returns a fully qualified URL to the entity. + */ + String selfLink(); +} 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..acc23410d285 --- /dev/null +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/RegionOperationId.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 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 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); + } + + @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/ZoneOperationId.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ZoneOperationId.java new file mode 100644 index 000000000000..c0364b0ead3f --- /dev/null +++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ZoneOperationId.java @@ -0,0 +1,114 @@ +/* + * 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 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..13b391adb75e 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 in the current project. + * + * @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 current project and 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 current project and 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..d54840e3fff0 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,11 +250,130 @@ 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); 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..0d8461d611a7 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 @@ -20,10 +20,12 @@ 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; @@ -117,6 +119,37 @@ 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 Operation.OperationError OPERATION_ERROR1 = + new Operation.OperationError("code1", "location1", "message1"); + private static final Operation.OperationError OPERATION_ERROR2 = + new Operation.OperationError("code2", "location2", "message2"); + private static final Operation.OperationWarning OPERATION_WARNING1 = + new Operation.OperationWarning("code1", "message1", ImmutableMap.of("k1", "v1")); + private static final Operation.OperationWarning OPERATION_WARNING2 = + new Operation.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 Operation.Status STATUS = Operation.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(); @@ -216,11 +249,33 @@ public class ComputeImplTest { private static final Compute.LicenseOption LICENSE_OPTION_FIELDS = Compute.LicenseOption.fields(Compute.LicenseField.CHARGES_USE_FEE); + // Operation options + private static final Compute.OperationOption OPERATION_OPTION_FIELDS = + Compute.OperationOption.fields(Compute.OperationField.ID, Compute.OperationField.DESCRIPTION); + + // Operation list options + private static final Compute.OperationFilter OPERATION_FILTER = + Compute.OperationFilter.notEquals(Compute.OperationField.PROGRESS, 0); + private static final Compute.OperationListOption OPERATION_LIST_PAGE_TOKEN = + Compute.OperationListOption.startPageToken("cursor"); + private static final Compute.OperationListOption OPERATION_LIST_MAX_RESULTS = + Compute.OperationListOption.maxResults(42L); + private static final Compute.OperationListOption OPERATION_LIST_FILTER = + Compute.OperationListOption.filter(OPERATION_FILTER); + private static final Map OPERATION_LIST_OPTIONS = ImmutableMap.of( + ComputeRpc.Option.PAGE_TOKEN, "cursor", + ComputeRpc.Option.MAX_RESULTS, 42L, + ComputeRpc.Option.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 +284,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 @@ -682,6 +801,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 +832,355 @@ 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 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 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 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..98430f0d1ad5 --- /dev/null +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationIdTest.java @@ -0,0 +1,143 @@ +/* + * 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(PROJECT, operationId.project()); + assertEquals(NAME, operationId.operation()); + assertEquals(GLOBAL_URL, operationId.selfLink()); + operationId = GlobalOperationId.of(NAME); + assertNull(operationId.project()); + assertEquals(NAME, operationId.operation()); + ZoneOperationId zoneOperationId = ZoneOperationId.of(PROJECT, ZONE, NAME); + assertEquals(PROJECT, zoneOperationId.project()); + assertEquals(ZONE, zoneOperationId.zone()); + assertEquals(NAME, zoneOperationId.operation()); + assertEquals(ZONE_URL, zoneOperationId.selfLink()); + zoneOperationId = ZoneOperationId.of(ZONE, NAME); + assertNull(zoneOperationId.project()); + assertEquals(ZONE, zoneOperationId.zone()); + assertEquals(NAME, zoneOperationId.operation()); + RegionOperationId regionOperationId = RegionOperationId.of(PROJECT, REGION, NAME); + assertEquals(PROJECT, regionOperationId.project()); + assertEquals(REGION, regionOperationId.region()); + assertEquals(NAME, regionOperationId.operation()); + assertEquals(REGION_URL, regionOperationId.selfLink()); + regionOperationId = RegionOperationId.of(REGION, NAME); + assertNull(regionOperationId.project()); + assertEquals(REGION, regionOperationId.region()); + assertEquals(NAME, regionOperationId.operation()); + } + + @Test + public void testToAndFromUrl() { + 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"); + 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"); + 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 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.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..05daaacda636 --- /dev/null +++ b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationTest.java @@ -0,0 +1,489 @@ +/* + * 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.Status; +import com.google.gcloud.compute.Operation.OperationWarning; + +import org.junit.After; +import org.junit.Test; + +import java.math.BigInteger; +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); + } + + @Test + public void testBuilder() { + initializeExpectedOperation(6); + assertEquals(CREATION_TIMESTAMP, globalOperation.creationTimestamp()); + assertEquals(ID, globalOperation.id()); + assertEquals(GLOBAL_OPERATION_ID, globalOperation.operationId()); + assertEquals(CLIENT_OPERATION_ID, globalOperation.clientOperationId()); + assertEquals(OPERATION_TYPE, globalOperation.operationType()); + assertEquals(TARGET_LINK, globalOperation.targetLink()); + assertEquals(TARGET_ID, globalOperation.targetId()); + assertEquals(STATUS, globalOperation.status()); + assertEquals(STATUS_MESSAGE, globalOperation.statusMessage()); + assertEquals(USER, globalOperation.user()); + assertEquals(PROGRESS, globalOperation.progress()); + assertEquals(INSERT_TIME, globalOperation.insertTime()); + assertEquals(START_TIME, globalOperation.startTime()); + assertEquals(END_TIME, globalOperation.endTime()); + assertEquals(ERRORS, globalOperation.errors()); + assertEquals(WARNINGS, globalOperation.warnings()); + assertEquals(HTTP_ERROR_STATUS_CODE, globalOperation.httpErrorStatusCode()); + assertEquals(HTTP_ERROR_MESSAGE, globalOperation.httpErrorMessage()); + assertEquals(DESCRIPTION, globalOperation.description()); + assertSame(serviceMockReturnsOptions, globalOperation.compute()); + assertEquals(ID, regionOperation.id()); + assertEquals(CREATION_TIMESTAMP, regionOperation.creationTimestamp()); + assertEquals(REGION_OPERATION_ID, regionOperation.operationId()); + assertEquals(CLIENT_OPERATION_ID, regionOperation.clientOperationId()); + assertEquals(OPERATION_TYPE, regionOperation.operationType()); + assertEquals(TARGET_LINK, regionOperation.targetLink()); + assertEquals(TARGET_ID, regionOperation.targetId()); + assertEquals(STATUS, regionOperation.status()); + assertEquals(STATUS_MESSAGE, regionOperation.statusMessage()); + assertEquals(USER, regionOperation.user()); + assertEquals(PROGRESS, regionOperation.progress()); + assertEquals(INSERT_TIME, regionOperation.insertTime()); + assertEquals(START_TIME, regionOperation.startTime()); + assertEquals(END_TIME, regionOperation.endTime()); + assertEquals(ERRORS, regionOperation.errors()); + assertEquals(WARNINGS, regionOperation.warnings()); + assertEquals(HTTP_ERROR_STATUS_CODE, regionOperation.httpErrorStatusCode()); + assertEquals(HTTP_ERROR_MESSAGE, regionOperation.httpErrorMessage()); + assertEquals(DESCRIPTION, regionOperation.description()); + assertSame(serviceMockReturnsOptions, regionOperation.compute()); + assertEquals(ID, zoneOperation.id()); + assertEquals(CREATION_TIMESTAMP, zoneOperation.creationTimestamp()); + assertEquals(ZONE_OPERATION_ID, zoneOperation.operationId()); + assertEquals(CLIENT_OPERATION_ID, zoneOperation.clientOperationId()); + assertEquals(OPERATION_TYPE, zoneOperation.operationType()); + assertEquals(TARGET_LINK, zoneOperation.targetLink()); + assertEquals(TARGET_ID, zoneOperation.targetId()); + assertEquals(STATUS, zoneOperation.status()); + assertEquals(STATUS_MESSAGE, zoneOperation.statusMessage()); + assertEquals(USER, zoneOperation.user()); + assertEquals(PROGRESS, zoneOperation.progress()); + assertEquals(INSERT_TIME, zoneOperation.insertTime()); + assertEquals(START_TIME, zoneOperation.startTime()); + assertEquals(END_TIME, zoneOperation.endTime()); + assertEquals(ERRORS, zoneOperation.errors()); + assertEquals(WARNINGS, zoneOperation.warnings()); + assertEquals(HTTP_ERROR_STATUS_CODE, zoneOperation.httpErrorStatusCode()); + assertEquals(HTTP_ERROR_MESSAGE, zoneOperation.httpErrorMessage()); + assertEquals(DESCRIPTION, zoneOperation.description()); + assertSame(serviceMockReturnsOptions, zoneOperation.compute()); + Operation operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(GLOBAL_OPERATION_ID) + .build(); + assertEquals(GLOBAL_OPERATION_ID, operation.operationId()); + assertSame(serviceMockReturnsOptions, operation.compute()); + 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()); + operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(ZONE_OPERATION_ID) + .build(); + assertSame(serviceMockReturnsOptions, operation.compute()); + assertEquals(ZONE_OPERATION_ID, operation.operationId()); + 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()); + operation = new Operation.Builder(serviceMockReturnsOptions) + .operationId(REGION_OPERATION_ID) + .build(); + assertSame(serviceMockReturnsOptions, operation.compute()); + assertEquals(REGION_OPERATION_ID, operation.operationId()); + 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()); + } + + @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(globalOperation); + replay(compute); + initializeOperation(); + assertTrue(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..ea316f521b29 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; @@ -62,7 +65,6 @@ public static void beforeClass() throws InterruptedException { @Test public void testGetDiskType() { DiskType diskType = compute.getDiskType(ZONE, DISK_TYPE); - // todo(mziccard): uncomment or remove once #695 is closed // assertNotNull(diskType.id()); assertEquals(ZONE, diskType.diskTypeId().zone()); assertEquals(DISK_TYPE, diskType.diskTypeId().diskType()); @@ -76,7 +78,6 @@ public void testGetDiskType() { public void testGetDiskTypeWithSelectedFields() { DiskType diskType = compute.getDiskType(ZONE, DISK_TYPE, Compute.DiskTypeOption.fields(Compute.DiskTypeField.CREATION_TIMESTAMP)); - // todo(mziccard): uncomment or remove once #695 is closed // assertNotNull(diskType.id()); assertEquals(ZONE, diskType.diskTypeId().zone()); assertEquals(DISK_TYPE, diskType.diskTypeId().diskType()); @@ -93,7 +94,6 @@ public void testListDiskTypes() { assertTrue(diskTypeIterator.hasNext()); while(diskTypeIterator.hasNext()) { DiskType diskType = diskTypeIterator.next(); - // todo(mziccard): uncomment or remove once #695 is closed // assertNotNull(diskType.id()); assertNotNull(diskType.diskTypeId()); assertEquals(ZONE, diskType.diskTypeId().zone()); @@ -148,7 +148,6 @@ public void testAggregatedListDiskTypes() { assertTrue(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()); @@ -447,4 +446,190 @@ 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()); + // 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()); + // 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()); + } + } } From 912ec8bda2d010fa5fb8b35ad1942d023760fa76 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Thu, 10 Mar 2016 10:07:53 +0100 Subject: [PATCH 2/6] Fix Compute operations issues - Fix javadoc errors - Fix OperationId and Operation tests - Fix RegionOperationId equals --- .../com/google/gcloud/compute/Compute.java | 71 ++++---- .../google/gcloud/compute/ComputeImpl.java | 2 +- .../com/google/gcloud/compute/Operation.java | 25 +-- .../google/gcloud/compute/OperationId.java | 2 +- .../gcloud/compute/RegionOperationId.java | 4 +- .../com/google/gcloud/spi/ComputeRpc.java | 6 +- .../gcloud/compute/ComputeImplTest.java | 171 ++++++++++-------- .../gcloud/compute/OperationIdTest.java | 31 +++- .../google/gcloud/compute/OperationTest.java | 147 +++++---------- .../gcloud/compute/it/ITComputeTest.java | 51 +++--- 10 files changed, 240 insertions(+), 270 deletions(-) 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 4acfab38addb..f8e8359e8444 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 @@ -244,15 +244,16 @@ enum OperationField { NAME("name"), OPERATION_TYPE("operationType"), PROGRESS("progress"), + REGION("region"), SELF_LINK("selfLink"), START_TIME("startTime"), STATUS("status"), STATUS_MESSAGE("statusMessage"), - REGION("region"), TARGET_ID("targetId"), TARGET_LINK("targetLink"), USER("user"), - WARNINGS("warnings"); + WARNINGS("warnings"), + ZONE("zone"); private final String selector; @@ -292,7 +293,7 @@ enum ComparisonOperator { EQ, /** - * Defines an inequality filter. + * Defines a not-equals filter. */ NE } @@ -350,7 +351,7 @@ public static DiskTypeFilter equals(DiskTypeField field, String 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. * @@ -368,7 +369,7 @@ public static DiskTypeFilter equals(DiskTypeField field, long 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); @@ -398,7 +399,7 @@ public static MachineTypeFilter equals(MachineTypeField field, String 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. * @@ -416,7 +417,7 @@ public static MachineTypeFilter equals(MachineTypeField field, long 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); @@ -446,7 +447,7 @@ public static RegionFilter equals(RegionField field, String 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. * @@ -480,7 +481,7 @@ public static ZoneFilter equals(ZoneField field, String 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. * @@ -514,7 +515,7 @@ public static OperationFilter equals(OperationField field, String 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. * @@ -532,7 +533,7 @@ public static OperationFilter equals(OperationField field, long 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 OperationFilter notEquals(OperationField field, long value) { return new OperationFilter(checkNotNull(field), ComparisonOperator.NE, value); @@ -546,7 +547,7 @@ public static OperationFilter equals(OperationField field, int value) { } /** - * Returns an inequality filter for the given field and integer value. + * Returns a not-equals filter for the given field and integer value. */ public static OperationFilter notEquals(OperationField field, int value) { return new OperationFilter(checkNotNull(field), ComparisonOperator.NE, value); @@ -566,8 +567,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) { @@ -587,7 +588,7 @@ 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()); @@ -609,9 +610,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(); @@ -632,7 +633,7 @@ 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()); @@ -666,7 +667,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. */ @@ -687,7 +688,7 @@ 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()); @@ -709,7 +710,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. */ @@ -732,7 +733,7 @@ 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()); @@ -766,7 +767,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. */ @@ -787,7 +788,7 @@ 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()); @@ -809,7 +810,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. */ @@ -833,7 +834,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. */ @@ -854,7 +855,7 @@ 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()); @@ -876,7 +877,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. */ @@ -900,9 +901,9 @@ 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)); @@ -922,7 +923,7 @@ private OperationOption(ComputeRpc.Option option, Object 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} + * 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. */ @@ -943,7 +944,7 @@ private OperationListOption(ComputeRpc.Option option, Object value) { } /** - * Returns an option to specify a filter to the operations being listed. + * 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()); @@ -965,7 +966,7 @@ public static OperationListOption startPageToken(String 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. + * 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. */ 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 13239d8209b6..453a0f5a8d53 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 @@ -195,7 +195,7 @@ public Page nextPage() { private static class ZoneOperationPageFetcher implements NextPageFetcher { - private static final long serialVersionUID = 4111705358926164078L; + private static final long serialVersionUID = -9012504536518197793L; private final Map requestOptions; private final ComputeOptions serviceOptions; private final String zone; 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 index ac303843ebc4..baaa62eaae05 100644 --- 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 @@ -38,10 +38,10 @@ /** * Google Compute Engine operations. Operation identity can be obtained via {@link #operationId()}. - * For global operations {@link #operationId()} returns an {@link GlobalOperationId}, for region - * operations {@link #operationId()} returns a {@link RegionOperationId}, for zone operations - * {@link #operationId()} returns a {@link ZoneOperationId}. To get an {@code Operation} object with - * the most recent information use {@link #reload(Compute.OperationOption...)}. + * {@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 final class Operation implements Serializable { @@ -71,7 +71,7 @@ public final class Operation implements Serializable { private final String description; /** - * Types of operations. + * Status of an operation. */ public enum Status { PENDING, @@ -286,9 +286,6 @@ public int hashCode() { } } - /** - * Builder for Compute Engine operations. - */ static final class Builder { private Compute compute; @@ -310,7 +307,6 @@ static final class Builder { private List warnings; private Integer httpErrorStatusCode; private String httpErrorMessage; - private String description; Builder(Compute compute) { @@ -460,10 +456,7 @@ Builder description(String description) { return this; } - /** - * Creates an object. - */ - public Operation build() { + Operation build() { return new Operation(this); } } @@ -515,8 +508,8 @@ public Long creationTimestamp() { /** * Returns the operation's identity. This method returns an {@link GlobalOperationId} for global - * operations, returns a {@link RegionOperationId} for region operations and returns a - * {@link ZoneOperationId} for zone operations. + * operations, a {@link RegionOperationId} for region operations and a {@link ZoneOperationId} for + * zone operations. * * @see RFC1035 */ @@ -658,7 +651,7 @@ public boolean exists() throws ComputeException { /** * 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()}: + * completion, check that the operation exists first using {@link #exists()}: *
 {@code
    * if (operation.exists()) {
    *   while(!operation.isDone()) {
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
index c7211e97ca2d..eaaf7dee0ca4 100644
--- 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
@@ -32,7 +32,7 @@ public interface OperationId {
   String operation();
 
   /**
-   * Returns a fully qualified URL to the entity.
+   * Returns a fully qualified URL to the operation.
    */
   String selfLink();
 }
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
index acc23410d285..e5c70bf23876 100644
--- 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
@@ -62,7 +62,9 @@ public int hashCode() {
 
   @Override
   public boolean equals(Object obj) {
-    return obj instanceof RegionOperationId && baseEquals((RegionOperationId) obj);
+    return obj instanceof RegionOperationId
+        && baseEquals((RegionOperationId) obj)
+        && Objects.equals(operation, ((RegionOperationId) obj).operation);
   }
 
   @Override
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 13b391adb75e..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
@@ -171,7 +171,7 @@ public Y y() {
   Operation getGlobalOperation(String operation, Map options);
 
   /**
-   * Lists the global operations in the current project.
+   * Lists the global operations.
    *
    * @throws ComputeException upon failure
    */
@@ -193,7 +193,7 @@ public Y y() {
   Operation getRegionOperation(String region, String operation, Map options);
 
   /**
-   * Lists the region operations for the current project and region.
+   * Lists the region operations for the provided region.
    *
    * @throws ComputeException upon failure
    */
@@ -215,7 +215,7 @@ public Y y() {
   Operation getZoneOperation(String zone, String operation, Map options);
 
   /**
-   * Lists the zone operations for the current project and zone.
+   * Lists the zone operations for the provided zone.
    *
    * @throws ComputeException upon failure
    */
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 0d8461d611a7..e9e61dbf74f5 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
@@ -31,6 +31,27 @@
 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;
@@ -119,28 +140,28 @@ 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 Operation.OperationError OPERATION_ERROR1 =
-      new Operation.OperationError("code1", "location1", "message1");
-  private static final Operation.OperationError OPERATION_ERROR2 =
-      new Operation.OperationError("code2", "location2", "message2");
-  private static final Operation.OperationWarning OPERATION_WARNING1 =
-      new Operation.OperationWarning("code1", "message1", ImmutableMap.of("k1", "v1"));
-  private static final Operation.OperationWarning OPERATION_WARNING2 =
-      new Operation.OperationWarning("code2", "location2", ImmutableMap.of("k2", "v2"));
+  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 Operation.Status STATUS = Operation.Status.DONE;
+  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 =
+  private static final List ERRORS =
       ImmutableList.of(OPERATION_ERROR1, OPERATION_ERROR2);
-  private static final List WARNINGS =
+  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";
@@ -155,113 +176,109 @@ public class ComputeImplTest {
   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.maxResults(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");
 
   // 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.maxResults(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.maxResults(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");
 
   // 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.maxResults(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.maxResults(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");
 
   // 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.maxResults(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");
 
   // 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 Compute.OperationOption OPERATION_OPTION_FIELDS =
-      Compute.OperationOption.fields(Compute.OperationField.ID, Compute.OperationField.DESCRIPTION);
+  private static final OperationOption OPERATION_OPTION_FIELDS =
+      OperationOption.fields(Compute.OperationField.ID, Compute.OperationField.DESCRIPTION);
 
   // Operation list options
-  private static final Compute.OperationFilter OPERATION_FILTER =
-      Compute.OperationFilter.notEquals(Compute.OperationField.PROGRESS, 0);
-  private static final Compute.OperationListOption OPERATION_LIST_PAGE_TOKEN =
-      Compute.OperationListOption.startPageToken("cursor");
-  private static final Compute.OperationListOption OPERATION_LIST_MAX_RESULTS =
-      Compute.OperationListOption.maxResults(42L);
-  private static final Compute.OperationListOption OPERATION_LIST_FILTER =
-      Compute.OperationListOption.filter(OPERATION_FILTER);
+  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.maxResults(42L);
+  private static final OperationListOption OPERATION_LIST_FILTER =
+      OperationListOption.filter(OPERATION_FILTER);
   private static final Map OPERATION_LIST_OPTIONS = ImmutableMap.of(
       ComputeRpc.Option.PAGE_TOKEN, "cursor",
       ComputeRpc.Option.MAX_RESULTS, 42L,
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
index 98430f0d1ad5..36ca5ef19090 100644
--- 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
@@ -60,6 +60,10 @@ public void testOf() {
     assertNull(zoneOperationId.project());
     assertEquals(ZONE, zoneOperationId.zone());
     assertEquals(NAME, zoneOperationId.operation());
+    zoneOperationId = ZoneOperationId.of(ZoneId.of(PROJECT, ZONE), NAME);
+    assertEquals(PROJECT, zoneOperationId.project());
+    assertEquals(ZONE, zoneOperationId.zone());
+    assertEquals(NAME, zoneOperationId.operation());
     RegionOperationId regionOperationId = RegionOperationId.of(PROJECT, REGION, NAME);
     assertEquals(PROJECT, regionOperationId.project());
     assertEquals(REGION, regionOperationId.region());
@@ -69,20 +73,23 @@ public void testOf() {
     assertNull(regionOperationId.project());
     assertEquals(REGION, regionOperationId.region());
     assertEquals(NAME, regionOperationId.operation());
+    regionOperationId = RegionOperationId.of(RegionId.of(PROJECT, REGION), NAME);
+    assertEquals(PROJECT, regionOperationId.project());
+    assertEquals(REGION, regionOperationId.region());
+    assertEquals(NAME, regionOperationId.operation());
   }
 
   @Test
-  public void testToAndFromUrl() {
+  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");
-    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 testToAndFromUrlRegion() {
     RegionOperationId regionOperationId = RegionOperationId.of(PROJECT, REGION, NAME);
     compareRegionOperationId(regionOperationId,
         RegionOperationId.fromUrl(regionOperationId.selfLink()));
@@ -91,6 +98,15 @@ public void testToAndFromUrl() {
     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);
@@ -109,7 +125,8 @@ public void testSetProjectId() {
   public void testMatchesUrl() {
     assertTrue(GlobalOperationId.matchesUrl(GlobalOperationId.of(PROJECT, NAME).selfLink()));
     assertFalse(GlobalOperationId.matchesUrl("notMatchingUrl"));
-    assertTrue(RegionOperationId.matchesUrl(RegionOperationId.of(PROJECT, REGION, NAME).selfLink()));
+    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"));
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
index 05daaacda636..9f08491a1632 100644
--- 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
@@ -31,13 +31,12 @@
 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.Status;
 import com.google.gcloud.compute.Operation.OperationWarning;
+import com.google.gcloud.compute.Operation.Status;
 
 import org.junit.After;
 import org.junit.Test;
 
-import java.math.BigInteger;
 import java.util.List;
 
 public class OperationTest {
@@ -183,74 +182,29 @@ public void tearDown() throws Exception {
     verify(serviceMockReturnsOptions);
   }
 
-  @Test
-  public void testBuilder() {
-    initializeExpectedOperation(6);
-    assertEquals(CREATION_TIMESTAMP, globalOperation.creationTimestamp());
-    assertEquals(ID, globalOperation.id());
-    assertEquals(GLOBAL_OPERATION_ID, globalOperation.operationId());
-    assertEquals(CLIENT_OPERATION_ID, globalOperation.clientOperationId());
-    assertEquals(OPERATION_TYPE, globalOperation.operationType());
-    assertEquals(TARGET_LINK, globalOperation.targetLink());
-    assertEquals(TARGET_ID, globalOperation.targetId());
-    assertEquals(STATUS, globalOperation.status());
-    assertEquals(STATUS_MESSAGE, globalOperation.statusMessage());
-    assertEquals(USER, globalOperation.user());
-    assertEquals(PROGRESS, globalOperation.progress());
-    assertEquals(INSERT_TIME, globalOperation.insertTime());
-    assertEquals(START_TIME, globalOperation.startTime());
-    assertEquals(END_TIME, globalOperation.endTime());
-    assertEquals(ERRORS, globalOperation.errors());
-    assertEquals(WARNINGS, globalOperation.warnings());
+  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());
-    assertEquals(ID, regionOperation.id());
-    assertEquals(CREATION_TIMESTAMP, regionOperation.creationTimestamp());
-    assertEquals(REGION_OPERATION_ID, regionOperation.operationId());
-    assertEquals(CLIENT_OPERATION_ID, regionOperation.clientOperationId());
-    assertEquals(OPERATION_TYPE, regionOperation.operationType());
-    assertEquals(TARGET_LINK, regionOperation.targetLink());
-    assertEquals(TARGET_ID, regionOperation.targetId());
-    assertEquals(STATUS, regionOperation.status());
-    assertEquals(STATUS_MESSAGE, regionOperation.statusMessage());
-    assertEquals(USER, regionOperation.user());
-    assertEquals(PROGRESS, regionOperation.progress());
-    assertEquals(INSERT_TIME, regionOperation.insertTime());
-    assertEquals(START_TIME, regionOperation.startTime());
-    assertEquals(END_TIME, regionOperation.endTime());
-    assertEquals(ERRORS, regionOperation.errors());
-    assertEquals(WARNINGS, regionOperation.warnings());
-    assertEquals(HTTP_ERROR_STATUS_CODE, regionOperation.httpErrorStatusCode());
-    assertEquals(HTTP_ERROR_MESSAGE, regionOperation.httpErrorMessage());
-    assertEquals(DESCRIPTION, regionOperation.description());
-    assertSame(serviceMockReturnsOptions, regionOperation.compute());
-    assertEquals(ID, zoneOperation.id());
-    assertEquals(CREATION_TIMESTAMP, zoneOperation.creationTimestamp());
-    assertEquals(ZONE_OPERATION_ID, zoneOperation.operationId());
-    assertEquals(CLIENT_OPERATION_ID, zoneOperation.clientOperationId());
-    assertEquals(OPERATION_TYPE, zoneOperation.operationType());
-    assertEquals(TARGET_LINK, zoneOperation.targetLink());
-    assertEquals(TARGET_ID, zoneOperation.targetId());
-    assertEquals(STATUS, zoneOperation.status());
-    assertEquals(STATUS_MESSAGE, zoneOperation.statusMessage());
-    assertEquals(USER, zoneOperation.user());
-    assertEquals(PROGRESS, zoneOperation.progress());
-    assertEquals(INSERT_TIME, zoneOperation.insertTime());
-    assertEquals(START_TIME, zoneOperation.startTime());
-    assertEquals(END_TIME, zoneOperation.endTime());
-    assertEquals(ERRORS, zoneOperation.errors());
-    assertEquals(WARNINGS, zoneOperation.warnings());
-    assertEquals(HTTP_ERROR_STATUS_CODE, zoneOperation.httpErrorStatusCode());
-    assertEquals(HTTP_ERROR_MESSAGE, zoneOperation.httpErrorMessage());
-    assertEquals(DESCRIPTION, zoneOperation.description());
-    assertSame(serviceMockReturnsOptions, zoneOperation.compute());
-    Operation operation = new Operation.Builder(serviceMockReturnsOptions)
-        .operationId(GLOBAL_OPERATION_ID)
-        .build();
-    assertEquals(GLOBAL_OPERATION_ID, operation.operationId());
-    assertSame(serviceMockReturnsOptions, operation.compute());
+  }
+
+  private void assertNullCommonFields(Operation operation) {
     assertNull(operation.creationTimestamp());
     assertNull(operation.id());
     assertNull(operation.clientOperationId());
@@ -269,52 +223,33 @@ public void testBuilder() {
     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();
-    assertSame(serviceMockReturnsOptions, operation.compute());
+    assertNullCommonFields(operation);
     assertEquals(ZONE_OPERATION_ID, operation.operationId());
-    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());
     operation = new Operation.Builder(serviceMockReturnsOptions)
         .operationId(REGION_OPERATION_ID)
         .build();
-    assertSame(serviceMockReturnsOptions, operation.compute());
+    assertNullCommonFields(operation);
     assertEquals(REGION_OPERATION_ID, operation.operationId());
-    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());
   }
 
   @Test
@@ -420,10 +355,10 @@ public void testIsDone_NotExists() throws Exception {
     Compute.OperationOption[] expectedOptions =
         {Compute.OperationOption.fields(Compute.OperationField.STATUS)};
     expect(compute.options()).andReturn(mockOptions);
-    expect(compute.get(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(globalOperation);
+    expect(compute.get(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(null);
     replay(compute);
     initializeOperation();
-    assertTrue(operation.isDone());
+    assertFalse(operation.isDone());
     verify(compute);
   }
 
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 ea316f521b29..da446821a088 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
@@ -65,6 +65,7 @@ public static void beforeClass() throws InterruptedException {
   @Test
   public void testGetDiskType() {
     DiskType diskType = compute.getDiskType(ZONE, DISK_TYPE);
+    // todo(mziccard): uncomment or remove once #695 is closed
     // assertNotNull(diskType.id());
     assertEquals(ZONE, diskType.diskTypeId().zone());
     assertEquals(DISK_TYPE, diskType.diskTypeId().diskType());
@@ -78,6 +79,7 @@ public void testGetDiskType() {
   public void testGetDiskTypeWithSelectedFields() {
     DiskType diskType = compute.getDiskType(ZONE, DISK_TYPE,
         Compute.DiskTypeOption.fields(Compute.DiskTypeField.CREATION_TIMESTAMP));
+    // todo(mziccard): uncomment or remove once #695 is closed
     // assertNotNull(diskType.id());
     assertEquals(ZONE, diskType.diskTypeId().zone());
     assertEquals(DISK_TYPE, diskType.diskTypeId().diskType());
@@ -92,8 +94,9 @@ 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());
       assertNotNull(diskType.diskTypeId());
       assertEquals(ZONE, diskType.diskTypeId().zone());
@@ -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,7 +149,7 @@ public void testAggregatedListDiskTypes() {
     Page diskPage = compute.listDiskTypes();
     Iterator diskTypeIterator = diskPage.iterateAll();
     assertTrue(diskTypeIterator.hasNext());
-    while(diskTypeIterator.hasNext()) {
+    while (diskTypeIterator.hasNext()) {
       DiskType diskType = diskTypeIterator.next();
       // assertNotNull(diskType.id());
       assertNotNull(diskType.diskTypeId());
@@ -163,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());
@@ -209,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());
@@ -229,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());
@@ -250,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());
@@ -270,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());
@@ -290,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());
@@ -346,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());
@@ -363,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());
@@ -411,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());
@@ -427,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());
@@ -451,7 +454,7 @@ public void testListZonesWithFilter() {
   public void testListGlobalOperations() {
     Page operationPage = compute.listGlobalOperations();
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    while (operationIterator.hasNext()) {
       Operation operation = operationIterator.next();
       assertNotNull(operation.id());
       assertNotNull(operation.operationId());
@@ -468,7 +471,7 @@ public void testListGlobalOperationsWithSelectedFields() {
     Page operationPage =
         compute.listGlobalOperations(Compute.OperationListOption.fields(Compute.OperationField.ID));
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    while (operationIterator.hasNext()) {
       Operation operation = operationIterator.next();
       assertNotNull(operation.id());
       assertNotNull(operation.operationId());
@@ -495,7 +498,7 @@ public void testListGlobalOperationsWithFilter() {
     Page operationPage = compute.listGlobalOperations(Compute.OperationListOption.filter(
         Compute.OperationFilter.equals(Compute.OperationField.STATUS, "DONE")));
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    while (operationIterator.hasNext()) {
       Operation operation = operationIterator.next();
       assertNotNull(operation.id());
       assertNotNull(operation.operationId());
@@ -511,11 +514,12 @@ public void testListGlobalOperationsWithFilter() {
   public void testListRegionOperations() {
     Page operationPage = compute.listRegionOperations(REGION);
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    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());
@@ -528,7 +532,7 @@ public void testListRegionOperationsWithSelectedFields() {
     Page operationPage = compute.listRegionOperations(REGION,
         Compute.OperationListOption.fields(Compute.OperationField.ID));
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    while (operationIterator.hasNext()) {
       Operation operation = operationIterator.next();
       assertNotNull(operation.id());
       assertNotNull(operation.operationId());
@@ -557,7 +561,7 @@ public void testListRegionOperationsWithFilter() {
         Compute.OperationListOption.filter(Compute.OperationFilter.equals(
             Compute.OperationField.STATUS, "DONE")));
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    while (operationIterator.hasNext()) {
       Operation operation = operationIterator.next();
       assertNotNull(operation.id());
       assertNotNull(operation.operationId());
@@ -574,11 +578,12 @@ public void testListRegionOperationsWithFilter() {
   public void testListZoneOperations() {
     Page operationPage = compute.listZoneOperations(ZONE);
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    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());
@@ -591,7 +596,7 @@ public void testListZoneOperationsWithSelectedFields() {
     Page operationPage = compute.listZoneOperations(ZONE,
         Compute.OperationListOption.fields(Compute.OperationField.ID));
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    while (operationIterator.hasNext()) {
       Operation operation = operationIterator.next();
       assertNotNull(operation.id());
       assertNotNull(operation.operationId());
@@ -620,7 +625,7 @@ public void testListZoneOperationsWithFilter() {
         Compute.OperationListOption.filter(Compute.OperationFilter.equals(
             Compute.OperationField.STATUS, "DONE")));
     Iterator operationIterator = operationPage.iterateAll();
-    while(operationIterator.hasNext()) {
+    while (operationIterator.hasNext()) {
       Operation operation = operationIterator.next();
       assertNotNull(operation.id());
       assertNotNull(operation.operationId());

From ef98269595b9af129856e22b5360b56ad191d1d4 Mon Sep 17 00:00:00 2001
From: Marco Ziccardi 
Date: Sun, 13 Mar 2016 13:26:20 +0100
Subject: [PATCH 3/6] Rename maxResults to pageSize, add tests for pagination

---
 .../com/google/gcloud/compute/Compute.java    |  28 +-
 .../com/google/gcloud/compute/Operation.java  |   2 +-
 .../gcloud/compute/ComputeImplTest.java       | 305 ++++++++++++++++--
 3 files changed, 298 insertions(+), 37 deletions(-)

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 f8e8359e8444..faa890a1e982 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
@@ -597,8 +597,8 @@ public static DiskTypeListOption filter(DiskTypeFilter filter) {
     /**
      * Returns an option to specify the maximum number of disk types to be returned.
      */
-    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);
     }
 
     /**
@@ -642,8 +642,8 @@ public static DiskTypeAggregatedListOption filter(DiskTypeFilter filter) {
     /**
      * Returns an option to specify the maximum number of disk types to be returned.
      */
-    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);
     }
 
     /**
@@ -697,8 +697,8 @@ public static MachineTypeListOption filter(MachineTypeFilter filter) {
     /**
      * Returns an option to specify the maximum number of machine types to be returned.
      */
-    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);
     }
 
     /**
@@ -742,8 +742,8 @@ public static MachineTypeAggregatedListOption filter(MachineTypeFilter filter) {
     /**
      * Returns an option to specify the maximum number of machine types to be returned.
      */
-    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);
     }
 
     /**
@@ -797,8 +797,8 @@ public static RegionListOption filter(RegionFilter filter) {
     /**
      * Returns an option to specify the maximum number of regions to be returned.
      */
-    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);
     }
 
     /**
@@ -864,8 +864,8 @@ public static ZoneListOption filter(ZoneFilter filter) {
     /**
      * Returns an option to specify the maximum number of zones to be returned.
      */
-    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);
     }
 
     /**
@@ -953,8 +953,8 @@ public static OperationListOption filter(OperationFilter filter) {
     /**
      * Returns an option to specify the maximum number of operations to be returned.
      */
-    public static OperationListOption maxResults(long maxResults) {
-      return new OperationListOption(ComputeRpc.Option.MAX_RESULTS, maxResults);
+    public static OperationListOption pageSize(long pageSize) {
+      return new OperationListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
     }
 
     /**
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
index baaa62eaae05..6734de60804b 100644
--- 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
@@ -40,7 +40,7 @@
  * 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
+ * To get an {@code Operation} object with the most recent information, use
  * {@link #reload(Compute.OperationOption...)}.
  */
 public final class Operation implements Serializable {
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 e9e61dbf74f5..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,6 +16,9 @@
 
 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;
@@ -185,19 +188,19 @@ public class ComputeImplTest {
   private static final DiskTypeListOption DISK_TYPE_LIST_PAGE_TOKEN =
       DiskTypeListOption.startPageToken("cursor");
   private static final DiskTypeListOption DISK_TYPE_LIST_MAX_RESULTS =
-      DiskTypeListOption.maxResults(42L);
+      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 DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_PAGE_TOKEN =
       DiskTypeAggregatedListOption.startPageToken("cursor");
   private static final DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_MAX_RESULTS =
-      DiskTypeAggregatedListOption.maxResults(42L);
+      DiskTypeAggregatedListOption.pageSize(42L);
   private static final DiskTypeAggregatedListOption DISK_TYPE_AGGREGATED_LIST_FILTER =
       DiskTypeAggregatedListOption.filter(DISK_TYPE_FILTER);
 
@@ -212,19 +215,19 @@ public class ComputeImplTest {
   private static final MachineTypeListOption MACHINE_TYPE_LIST_PAGE_TOKEN =
       MachineTypeListOption.startPageToken("cursor");
   private static final MachineTypeListOption MACHINE_TYPE_LIST_MAX_RESULTS =
-      MachineTypeListOption.maxResults(42L);
+      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 MachineTypeAggregatedListOption MACHINE_TYPE_AGGREGATED_LIST_PAGE_TOKEN =
       MachineTypeAggregatedListOption.startPageToken("cursor");
   private static final MachineTypeAggregatedListOption MACHINE_TYPE_AGGREGATED_LIST_MAX_RESULTS =
-      MachineTypeAggregatedListOption.maxResults(42L);
+      MachineTypeAggregatedListOption.pageSize(42L);
   private static final MachineTypeAggregatedListOption MACHINE_TYPE_AGGREGATED_LIST_FILTER =
       MachineTypeAggregatedListOption.filter(MACHINE_TYPE_FILTER);
 
@@ -238,13 +241,13 @@ public class ComputeImplTest {
   private static final RegionListOption REGION_LIST_PAGE_TOKEN =
       RegionListOption.startPageToken("cursor");
   private static final RegionListOption REGION_LIST_MAX_RESULTS =
-      RegionListOption.maxResults(42L);
+      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 ZoneOption ZONE_OPTION_FIELDS =
@@ -255,12 +258,12 @@ public class ComputeImplTest {
       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.maxResults(42L);
+  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 LicenseOption LICENSE_OPTION_FIELDS =
@@ -276,13 +279,13 @@ public class ComputeImplTest {
   private static final OperationListOption OPERATION_LIST_PAGE_TOKEN =
       OperationListOption.startPageToken("cursor");
   private static final OperationListOption OPERATION_LIST_MAX_RESULTS =
-      OperationListOption.maxResults(42L);
+      OperationListOption.pageSize(42L);
   private static final OperationListOption OPERATION_LIST_FILTER =
       OperationListOption.filter(OPERATION_FILTER);
   private static final Map OPERATION_LIST_OPTIONS = ImmutableMap.of(
-      ComputeRpc.Option.PAGE_TOKEN, "cursor",
-      ComputeRpc.Option.MAX_RESULTS, 42L,
-      ComputeRpc.Option.FILTER, "progress ne 0");
+      PAGE_TOKEN, "cursor",
+      MAX_RESULTS, 42L,
+      FILTER, "progress ne 0");
 
   private ComputeOptions options;
   private ComputeRpcFactory rpcFactoryMock;
@@ -442,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();
@@ -486,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();
@@ -573,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 =
@@ -622,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 =
@@ -694,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();
@@ -763,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();
@@ -896,6 +1045,42 @@ public com.google.api.services.compute.model.Operation apply(Operation operation
     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();
@@ -999,6 +1184,44 @@ public com.google.api.services.compute.model.Operation apply(Operation operation
     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();
@@ -1106,6 +1329,44 @@ public com.google.api.services.compute.model.Operation apply(Operation operation
     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();

From c41ad1436fdeb9d2421193b18c92160efc9c10a5 Mon Sep 17 00:00:00 2001
From: Marco Ziccardi 
Date: Tue, 15 Mar 2016 13:00:43 +0100
Subject: [PATCH 4/6] Refactor compute operations - Add Type enum to
 OperationId and type() getter - Replace instanceof with switch on type() -
 Add better javadoc to Operation - Remove final from Operation, make hashCode
 and equals final

---
 .../com/google/gcloud/compute/Compute.java    | 72 ++++++++-----------
 .../google/gcloud/compute/ComputeImpl.java    | 42 +++++------
 .../gcloud/compute/GlobalOperationId.java     |  5 ++
 .../com/google/gcloud/compute/Operation.java  | 36 ++++++----
 .../google/gcloud/compute/OperationId.java    | 26 +++++++
 .../gcloud/compute/RegionOperationId.java     |  5 ++
 .../gcloud/compute/ZoneOperationId.java       |  5 ++
 .../google/gcloud/spi/DefaultComputeRpc.java  |  2 +-
 .../gcloud/compute/OperationIdTest.java       |  9 +++
 .../gcloud/compute/it/ITComputeTest.java      |  2 +-
 10 files changed, 128 insertions(+), 76 deletions(-)

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 faa890a1e982..935cf1fa9d7f 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
@@ -288,7 +288,7 @@ abstract class ListFilter implements Serializable {
 
     enum ComparisonOperator {
       /**
-       * Defines an equality filter.
+       * Defines an equals filter.
        */
       EQ,
 
@@ -340,11 +340,11 @@ 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));
@@ -355,14 +355,14 @@ public static DiskTypeFilter equals(DiskTypeField field, String value) {
      * {@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);
@@ -388,11 +388,11 @@ 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));
@@ -403,14 +403,14 @@ public static MachineTypeFilter equals(MachineTypeField field, String value) {
      * {@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);
@@ -436,11 +436,11 @@ 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);
@@ -451,7 +451,7 @@ public static RegionFilter equals(RegionField field, String value) {
      * {@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));
@@ -470,11 +470,11 @@ 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));
@@ -485,7 +485,7 @@ public static ZoneFilter equals(ZoneField field, String value) {
      * {@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));
@@ -504,11 +504,11 @@ class OperationFilter 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 OperationFilter equals(OperationField field, String value) {
       return new OperationFilter(checkNotNull(field), ComparisonOperator.EQ, checkNotNull(value));
@@ -519,14 +519,14 @@ public static OperationFilter equals(OperationField field, String value) {
      * {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
      * match the entire field.
      *
-     * @see RE2
+     * @see RE2
      */
     public static OperationFilter notEquals(OperationField field, String value) {
       return new OperationFilter(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 OperationFilter equals(OperationField field, long value) {
       return new OperationFilter(checkNotNull(field), ComparisonOperator.EQ, value);
@@ -538,20 +538,6 @@ public static OperationFilter equals(OperationField field, long value) {
     public static OperationFilter notEquals(OperationField field, long value) {
       return new OperationFilter(checkNotNull(field), ComparisonOperator.NE, value);
     }
-
-    /**
-     * Returns an equality filter for the given field and integer value.
-     */
-    public static OperationFilter equals(OperationField field, int value) {
-      return new OperationFilter(checkNotNull(field), ComparisonOperator.EQ, value);
-    }
-
-    /**
-     * Returns a not-equals filter for the given field and integer value.
-     */
-    public static OperationFilter notEquals(OperationField field, int value) {
-      return new OperationFilter(checkNotNull(field), ComparisonOperator.NE, value);
-    }
   }
 
   /**
@@ -595,7 +581,7 @@ public static DiskTypeListOption filter(DiskTypeFilter filter) {
     }
 
     /**
-     * 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 pageSize(long pageSize) {
       return new DiskTypeListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
@@ -640,7 +626,7 @@ public static DiskTypeAggregatedListOption filter(DiskTypeFilter filter) {
     }
 
     /**
-     * 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 pageSize(long pageSize) {
       return new DiskTypeAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
@@ -695,7 +681,7 @@ public static MachineTypeListOption filter(MachineTypeFilter filter) {
     }
 
     /**
-     * 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 pageSize(long pageSize) {
       return new MachineTypeListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
@@ -740,7 +726,7 @@ public static MachineTypeAggregatedListOption filter(MachineTypeFilter filter) {
     }
 
     /**
-     * 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 pageSize(long pageSize) {
       return new MachineTypeAggregatedListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
@@ -795,7 +781,7 @@ public static RegionListOption filter(RegionFilter filter) {
     }
 
     /**
-     * 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 pageSize(long pageSize) {
       return new RegionListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
@@ -862,7 +848,7 @@ public static ZoneListOption filter(ZoneFilter filter) {
     }
 
     /**
-     * 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 pageSize(long pageSize) {
       return new ZoneListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
@@ -951,7 +937,7 @@ public static OperationListOption filter(OperationFilter filter) {
     }
 
     /**
-     * Returns an option to specify the maximum number of operations to be returned.
+     * 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);
@@ -1090,14 +1076,16 @@ public static OperationListOption fields(OperationField... fields) {
   Page listGlobalOperations(OperationListOption... options);
 
   /**
-   * Lists the operations in the provided region.
+   * 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 in the provided zone.
+   * 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
    */
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 453a0f5a8d53..f3b866be5b77 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
@@ -533,16 +533,17 @@ public Operation get(final OperationId operationId, OperationOption... options)
           runWithRetries(new Callable() {
             @Override
             public com.google.api.services.compute.model.Operation call() {
-              if (operationId instanceof RegionOperationId) {
-                RegionOperationId regionOperationId = (RegionOperationId) operationId;
-                return computeRpc.getRegionOperation(regionOperationId.region(),
-                    regionOperationId.operation(), optionsMap);
-              } else if (operationId instanceof ZoneOperationId) {
-                ZoneOperationId zoneOperationId = (ZoneOperationId) operationId;
-                return computeRpc.getZoneOperation(zoneOperationId.zone(),
-                    zoneOperationId.operation(), optionsMap);
-              } else {
-                return computeRpc.getGlobalOperation(operationId.operation(), optionsMap);
+              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);
+                default:
+                  return computeRpc.getGlobalOperation(operationId.operation(), optionsMap);
               }
             }
           }, options().retryParams(), EXCEPTION_HANDLER);
@@ -660,16 +661,17 @@ public boolean delete(final OperationId operation) {
       return runWithRetries(new Callable() {
         @Override
         public Boolean call() {
-          if (operation instanceof RegionOperationId) {
-            RegionOperationId regionOperationId = (RegionOperationId) operation;
-            return computeRpc.deleteRegionOperation(regionOperationId.region(),
-                regionOperationId.operation());
-          } else if (operation instanceof ZoneOperationId) {
-            ZoneOperationId zoneOperationId = (ZoneOperationId) operation;
-            return computeRpc.deleteZoneOperation(zoneOperationId.zone(),
-                zoneOperationId.operation());
-          } else {
-            return computeRpc.deleteGlobalOperation(operation.operation());
+          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());
+            default:
+              return computeRpc.deleteGlobalOperation(operation.operation());
           }
         }
       }, options().retryParams(), EXCEPTION_HANDLER);
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
index 3c1601e8c4d0..c12e24c903cc 100644
--- 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
@@ -40,6 +40,11 @@ private GlobalOperationId(String project, String operation) {
     this.operation = checkNotNull(operation);
   }
 
+  @Override
+  public Type type() {
+    return Type.GLOBAL;
+  }
+
   @Override
   public String operation() {
     return operation;
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
index 6734de60804b..d857f90bc84f 100644
--- 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
@@ -17,6 +17,8 @@
 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;
@@ -43,7 +45,7 @@
  * To get an {@code Operation} object with the most recent information, use
  * {@link #reload(Compute.OperationOption...)}.
  */
-public final class Operation implements Serializable {
+public class Operation implements Serializable {
 
   private static final long serialVersionUID = -8979001444590023899L;
   private static final DateTimeFormatter TIMESTAMP_FORMATTER = ISODateTimeFormat.dateTime();
@@ -124,7 +126,7 @@ public String code() {
     }
 
     /**
-     * Returns the field in the request which caused the error. Might be {@code null}.
+     * Returns the field in the request which caused the error. This value is optional.
      */
     public String location() {
       return location;
@@ -213,7 +215,8 @@ public com.google.api.services.compute.model.Operation.Warnings apply(
     }
 
     /**
-     * Returns a warning identifier for this warning.
+     * 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;
@@ -227,7 +230,12 @@ public String message() {
     }
 
     /**
-     * Returns metadata about this warning.
+     * 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;
@@ -587,13 +595,15 @@ public Long 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.
+   * 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;
@@ -717,12 +727,12 @@ public String toString() {
   }
 
   @Override
-  public int hashCode() {
+  public final int hashCode() {
     return Objects.hash(id);
   }
 
   @Override
-  public boolean equals(Object obj) {
+  public final boolean equals(Object obj) {
     return obj instanceof Operation
         && Objects.equals(toPb(), ((Operation) obj).toPb())
         && Objects.equals(options, ((Operation) obj).options);
@@ -739,11 +749,13 @@ com.google.api.services.compute.model.Operation toPb() {
     }
     operationPb.setName(operationId.operation());
     operationPb.setClientOperationId(clientOperationId);
-    if (operationId instanceof RegionOperationId) {
-      operationPb.setRegion(this.operationId().regionId().selfLink());
-    }
-    if (operationId instanceof ZoneOperationId) {
-      operationPb.setZone(this.operationId().zoneId().selfLink());
+    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);
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
index eaaf7dee0ca4..7f9100aa61a2 100644
--- 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
@@ -21,6 +21,32 @@
  */
 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.
    */
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
index e5c70bf23876..96a772b5b9ea 100644
--- 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
@@ -40,6 +40,11 @@ private RegionOperationId(String project, String region, String operation) {
     this.operation = checkNotNull(operation);
   }
 
+  @Override
+  public Type type() {
+    return Type.REGION;
+  }
+
   @Override
   public String operation() {
     return operation;
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
index c0364b0ead3f..837ca6888c83 100644
--- 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
@@ -40,6 +40,11 @@ private ZoneOperationId(String project, String zone, String operation) {
     this.operation = checkNotNull(operation);
   }
 
+  @Override
+  public Type type() {
+    return Type.ZONE;
+  }
+
   @Override
   public String operation() {
     return 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 d54840e3fff0..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
@@ -378,7 +378,7 @@ public boolean deleteZoneOperation(String zone, String operation) {
   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/OperationIdTest.java b/gcloud-java-compute/src/test/java/com/google/gcloud/compute/OperationIdTest.java
index 36ca5ef19090..76a53cdd1cd2 100644
--- 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
@@ -45,35 +45,43 @@ public class OperationIdTest {
   @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());
@@ -151,6 +159,7 @@ private void compareZoneOperationId(ZoneOperationId expected, ZoneOperationId va
 
   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());
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 da446821a088..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
@@ -57,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();
   }

From 484071c11a2a5687c0108a26b5940253f5eddc69 Mon Sep 17 00:00:00 2001
From: Marco Ziccardi 
Date: Tue, 15 Mar 2016 14:09:43 +0100
Subject: [PATCH 5/6] Remove final from immutable resource classes

---
 .../src/main/java/com/google/gcloud/compute/DiskType.java   | 6 +++---
 .../src/main/java/com/google/gcloud/compute/License.java    | 6 +++---
 .../main/java/com/google/gcloud/compute/MachineType.java    | 6 +++---
 .../src/main/java/com/google/gcloud/compute/Region.java     | 6 +++---
 .../src/main/java/com/google/gcloud/compute/Zone.java       | 6 +++---
 5 files changed, 15 insertions(+), 15 deletions(-)

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/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/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/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());
   }
 

From cc2992921460dc17cec662632d383970b56b23a6 Mon Sep 17 00:00:00 2001
From: Marco Ziccardi 
Date: Wed, 16 Mar 2016 09:52:22 +0100
Subject: [PATCH 6/6] Operations: better javadoc for delete, stricter parsing
 of identities

---
 .../src/main/java/com/google/gcloud/compute/Compute.java  | 3 ++-
 .../main/java/com/google/gcloud/compute/ComputeImpl.java  | 8 ++++++--
 .../main/java/com/google/gcloud/compute/Operation.java    | 3 ++-
 3 files changed, 10 insertions(+), 4 deletions(-)

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 935cf1fa9d7f..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
@@ -1092,7 +1092,8 @@ public static OperationListOption fields(OperationField... fields) {
   Page listZoneOperations(String zone, OperationListOption... options);
 
   /**
-   * Deletes the requested operation.
+   * 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
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 f3b866be5b77..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
@@ -542,8 +542,10 @@ public com.google.api.services.compute.model.Operation call() {
                   ZoneOperationId zoneOperationId = (ZoneOperationId) operationId;
                   return computeRpc.getZoneOperation(zoneOperationId.zone(),
                       zoneOperationId.operation(), optionsMap);
-                default:
+                case GLOBAL:
                   return computeRpc.getGlobalOperation(operationId.operation(), optionsMap);
+                default:
+                  throw new IllegalArgumentException("Unexpected operation identity type");
               }
             }
           }, options().retryParams(), EXCEPTION_HANDLER);
@@ -670,8 +672,10 @@ public Boolean call() {
               ZoneOperationId zoneOperationId = (ZoneOperationId) operation;
               return computeRpc.deleteZoneOperation(zoneOperationId.zone(),
                   zoneOperationId.operation());
-            default:
+            case GLOBAL:
               return computeRpc.deleteGlobalOperation(operation.operation());
+            default:
+              throw new IllegalArgumentException("Unexpected operation identity type");
           }
         }
       }, options().retryParams(), EXCEPTION_HANDLER);
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
index d857f90bc84f..7b8a888cf40c 100644
--- 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
@@ -692,7 +692,8 @@ public Operation reload(Compute.OperationOption... options) throws ComputeExcept
   }
 
   /**
-   * Deletes this operation.
+   * 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