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