You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ad...@apache.org on 2014/11/07 17:08:05 UTC

[5/5] jclouds-labs-google git commit: Consolidate operation state management.

Consolidate operation state management.


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/commit/8895953d
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/tree/8895953d
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/diff/8895953d

Branch: refs/heads/master
Commit: 8895953d149abfd9bc2256d94df70fe07850a64a
Parents: 867c785
Author: Adrian Cole <ac...@twitter.com>
Authored: Thu Nov 6 17:42:13 2014 -0800
Committer: Adrian Cole <ac...@twitter.com>
Committed: Fri Nov 7 07:57:33 2014 -0800

----------------------------------------------------------------------
 google-compute-engine/pom.xml                   |   1 +
 .../GoogleComputeEngineApi.java                 |  51 +---
 .../compute/GoogleComputeEngineService.java     |  34 +--
 .../GoogleComputeEngineServiceAdapter.java      |  71 ++---
 ...GoogleComputeEngineServiceContextModule.java |  23 ++
 ...ogleComputeEngineSecurityGroupExtension.java |  41 ++-
 .../functions/CreateNetworkIfNeeded.java        |  31 +-
 .../functions/FirewallTagNamingConvention.java  |   8 +-
 .../compute/functions/ResourceFunctions.java    |  55 ++++
 .../predicates/AllNodesInGroupTerminated.java   |  13 +-
 .../predicates/AtomicInstanceVisible.java       |  44 +++
 .../compute/predicates/AtomicOperationDone.java |  53 ++++
 .../predicates/NetworkFirewallPredicates.java   | 125 ++++++++
 ...desWithGroupEncodedIntoNameThenAddToSet.java |  26 +-
 .../GoogleComputeEngineHttpApiModule.java       |  21 --
 .../googlecomputeengine/domain/Tags.java        |   4 +-
 .../features/AddressApi.java                    |   2 +-
 .../features/FirewallApi.java                   |   6 +-
 .../features/GlobalOperationApi.java            | 110 -------
 .../features/InstanceApi.java                   |   4 +-
 .../features/NetworkApi.java                    |   4 +-
 .../features/OperationApi.java                  | 198 +++++++++++++
 .../features/RegionOperationApi.java            | 110 -------
 .../googlecomputeengine/features/RouteApi.java  |   2 +-
 .../features/ZoneOperationApi.java              | 110 -------
 .../BaseWithRegionToIteratorOfListPage.java     |   2 +-
 .../BaseWithZoneToIteratorOfListPage.java       |   2 +-
 .../internal/ParseGlobalOperations.java         |   2 +-
 .../internal/ParseRegionOperations.java         |  23 +-
 .../functions/internal/ParseZoneOperations.java |  23 +-
 .../GlobalOperationDonePredicate.java           |  54 ----
 .../predicates/InstancePredicates.java          |  33 ---
 .../predicates/NetworkFirewallPredicates.java   | 125 --------
 .../RegionOperationDonePredicate.java           |  62 ----
 .../predicates/ZoneOperationDonePredicate.java  |  65 ----
 .../GoogleComputeEngineServiceExpectTest.java   |  32 +-
 .../functions/CreateNetworkIfNeededTest.java    |  53 ++--
 .../functions/FindNetworkOrCreateTest.java      |  60 ++--
 .../GoogleComputeEngineImageToImageTest.java    |   2 +-
 .../functions/NetworkToSecurityGroupTest.java   |   8 +-
 .../OrphanedGroupsFromDeadNodesTest.java        |  22 +-
 .../features/AddressApiLiveTest.java            |   8 +-
 .../features/DiskApiLiveTest.java               |   9 +-
 .../features/FirewallApiLiveTest.java           |  29 +-
 .../features/ForwardingRuleApiLiveTest.java     |  19 +-
 .../features/GlobalOperationApiExpectTest.java  | 157 ----------
 .../features/GlobalOperationApiLiveTest.java    |  64 ----
 .../features/HttpHealthCheckApiLiveTest.java    |  10 +-
 .../features/ImageApiLiveTest.java              |   9 +-
 .../features/InstanceApiLiveTest.java           |  54 ++--
 .../features/NetworkApiLiveTest.java            |   5 +-
 .../features/OperationApiExpectTest.java        | 294 +++++++++++++++++++
 .../features/OperationApiLiveTest.java          |  98 +++++++
 .../features/ProjectApiLiveTest.java            |  12 +-
 .../features/RegionOperationApiExpectTest.java  | 168 -----------
 .../features/RegionOperationApiLiveTest.java    |  64 ----
 .../features/RouteApiLiveTest.java              |  15 +-
 .../features/SnapshotApiLiveTest.java           |   9 +-
 .../features/TargetPoolApiExpectTest.java       |  15 +-
 .../features/TargetPoolApiLiveTest.java         |  45 ++-
 .../features/ZoneOperationApiExpectTest.java    | 170 -----------
 .../features/ZoneOperationApiLiveTest.java      |  64 ----
 .../BaseGoogleComputeEngineApiLiveTest.java     |  62 ++--
 .../parse/ParseOperationTest.java               |   2 +-
 .../parse/ParseZoneOperationTest.java           |   2 +-
 .../NetworkFirewallPredicatesTest.java          |  10 +-
 .../src/test/resources/operation.json           |   2 +-
 .../src/test/resources/operation_error.json     |   4 +-
 .../src/test/resources/operation_list.json      |   2 +-
 .../src/test/resources/zone_operation.json      |   2 +-
 .../test/resources/zone_operation_error.json    |   4 +-
 .../src/test/resources/zone_operation_list.json |   2 +-
 72 files changed, 1255 insertions(+), 1870 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/pom.xml
----------------------------------------------------------------------
diff --git a/google-compute-engine/pom.xml b/google-compute-engine/pom.xml
index ac45197..a5913a2 100644
--- a/google-compute-engine/pom.xml
+++ b/google-compute-engine/pom.xml
@@ -125,6 +125,7 @@
                                     <goal>test</goal>
                                 </goals>
                                 <configuration>
+                                    <threadCount>1</threadCount>
                                     <systemPropertyVariables>
                                         <test.google-compute-engine.identity>${test.google-compute-engine.identity}
                                         </test.google-compute-engine.identity>

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/GoogleComputeEngineApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/GoogleComputeEngineApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/GoogleComputeEngineApi.java
index 095f8c8..807e84a 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/GoogleComputeEngineApi.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/GoogleComputeEngineApi.java
@@ -26,32 +26,20 @@ import org.jclouds.googlecomputeengine.features.DiskApi;
 import org.jclouds.googlecomputeengine.features.DiskTypeApi;
 import org.jclouds.googlecomputeengine.features.FirewallApi;
 import org.jclouds.googlecomputeengine.features.ForwardingRuleApi;
-import org.jclouds.googlecomputeengine.features.GlobalOperationApi;
 import org.jclouds.googlecomputeengine.features.HttpHealthCheckApi;
 import org.jclouds.googlecomputeengine.features.ImageApi;
 import org.jclouds.googlecomputeengine.features.InstanceApi;
 import org.jclouds.googlecomputeengine.features.MachineTypeApi;
 import org.jclouds.googlecomputeengine.features.NetworkApi;
+import org.jclouds.googlecomputeengine.features.OperationApi;
 import org.jclouds.googlecomputeengine.features.ProjectApi;
 import org.jclouds.googlecomputeengine.features.RegionApi;
-import org.jclouds.googlecomputeengine.features.RegionOperationApi;
 import org.jclouds.googlecomputeengine.features.RouteApi;
 import org.jclouds.googlecomputeengine.features.SnapshotApi;
 import org.jclouds.googlecomputeengine.features.TargetPoolApi;
 import org.jclouds.googlecomputeengine.features.ZoneApi;
-import org.jclouds.googlecomputeengine.features.ZoneOperationApi;
 import org.jclouds.rest.annotations.Delegate;
 
-import com.google.common.annotations.Beta;
-
-
-/**
- * Provides access to GoogleCompute.
- * <p/>
- *
- * @see <a href="https://developers.google.com/compute/docs/reference/v1">api doc</a>
- */
-@Beta
 public interface GoogleComputeEngineApi extends Closeable {
 
    /**
@@ -104,15 +92,6 @@ public interface GoogleComputeEngineApi extends Closeable {
    ForwardingRuleApi getForwardingRuleApi(@PathParam("project") String projectName, @PathParam("region") String region);
 
    /**
-    * Provides access to Global Operation features
-    *
-    * @param projectName the name of the project
-    */
-   @Delegate
-   @Path("/projects/{project}/global")
-   GlobalOperationApi getGlobalOperationApi(@PathParam("project") String projectName);
-
-   /**
     * Provides access to HttpHealthCheck features
     *
     * @param projectName the name of the project
@@ -160,6 +139,14 @@ public interface GoogleComputeEngineApi extends Closeable {
    NetworkApi getNetworkApi(@PathParam("project") String projectName);
 
    /**
+    * Provides access to Operation features
+    *
+    * @param projectName the name of the project
+    */
+   @Delegate
+   OperationApi getOperationApi(@PathParam("project") String projectName);
+
+   /**
     * Provides access to Project features
     */
    @Delegate
@@ -175,16 +162,6 @@ public interface GoogleComputeEngineApi extends Closeable {
    RegionApi getRegionApi(@PathParam("project") String projectName);
 
    /**
-    * Provides access to Region Operation features
-    *
-    * @param project the name of the project
-    * @param region     the name of the region scoping this request.
-    */
-   @Delegate
-   @Path("/projects/{project}/regions/{region}")
-   RegionOperationApi getRegionOperationApi(@PathParam("project") String project, @PathParam("region") String region);
-
-   /**
     * Provides access to Route features
     *
     * @param projectName the name of the project
@@ -220,14 +197,4 @@ public interface GoogleComputeEngineApi extends Closeable {
    @Delegate
    @Path("/projects/{project}")
    ZoneApi getZoneApi(@PathParam("project") String projectName);
-
-   /**
-    * Provides access to Zone Operation features
-    *
-    * @param projectName the name of the project
-    * @param zone        the name of the zone scoping this request.
-    */
-   @Delegate
-   @Path("/projects/{project}/zones/{zone}")
-   ZoneOperationApi getZoneOperationApi(@PathParam("project") String projectName, @PathParam("zone") String zone);
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java
index 34b8a87..9abd6e7 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java
@@ -16,15 +16,10 @@
  */
 package org.jclouds.googlecomputeengine.compute;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
 import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
 import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_INTERVAL;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_TIMEOUT;
 import static org.jclouds.googlecomputeengine.internal.ListPages.concat;
-import static org.jclouds.util.Predicates2.retry;
 
 import java.util.Map;
 import java.util.Set;
@@ -82,9 +77,7 @@ public final class GoogleComputeEngineService extends BaseComputeService {
    private final GroupNamingConvention.Factory namingConvention;
    private final GoogleComputeEngineApi api;
    private final Supplier<String> project;
-   private final Predicate<AtomicReference<Operation>> operationDonePredicate;
-   private final long operationCompleteCheckInterval;
-   private final long operationCompleteCheckTimeout;
+   private final Predicate<AtomicReference<Operation>> operationDone;
 
    @Inject GoogleComputeEngineService(ComputeServiceContext context,
                                         Map<String, Credentials> credentialStore,
@@ -119,24 +112,17 @@ public final class GoogleComputeEngineService extends BaseComputeService {
                                         GroupNamingConvention.Factory namingConvention,
                                         GoogleComputeEngineApi api,
                                         @UserProject Supplier<String> project,
-                                        @Named("global") Predicate<AtomicReference<Operation>> operationDonePredicate,
-                                        @Named(OPERATION_COMPLETE_INTERVAL) Long operationCompleteCheckInterval,
-                                        @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout) {
-
+                                        Predicate<AtomicReference<Operation>> operationDone) {
       super(context, credentialStore, images, hardwareProfiles, locations, listNodesStrategy, getImageStrategy,
               getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
               resumeNodeStrategy, suspendNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
               nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
               persistNodeCredentials, timeouts, userExecutor, imageExtension, securityGroupExtension);
-      this.findOrphanedGroups = checkNotNull(findOrphanedGroups, "find orphaned groups function");
-      this.namingConvention = checkNotNull(namingConvention, "naming convention factory");
-      this.api = checkNotNull(api, "google compute api");
-      this.project = checkNotNull(project, "user project name");
-      this.operationDonePredicate = checkNotNull(operationDonePredicate, "operation completed predicate");
-      this.operationCompleteCheckInterval = checkNotNull(operationCompleteCheckInterval,
-              "operation completed check interval");
-      this.operationCompleteCheckTimeout = checkNotNull(operationCompleteCheckTimeout,
-              "operation completed check timeout");
+      this.findOrphanedGroups = findOrphanedGroups;
+      this.namingConvention = namingConvention;
+      this.api = api;
+      this.project = project;
+      this.operationDone = operationDone;
    }
 
    @Override
@@ -157,8 +143,7 @@ public final class GoogleComputeEngineService extends BaseComputeService {
             continue;
          }
          AtomicReference<Operation> operation = Atomics.newReference(firewallApi.delete(firewall.name()));
-         retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval,
-                 MILLISECONDS).apply(operation);
+         operationDone.apply(operation);
 
          if (operation.get().httpErrorStatusCode() != null) {
             logger.warn("delete orphaned firewall %s failed. Http Error Code: %d HttpError: %s",
@@ -170,8 +155,7 @@ public final class GoogleComputeEngineService extends BaseComputeService {
       AtomicReference<Operation> operation = Atomics
             .newReference(api.getNetworkApi(project.get()).delete(resourceName));
 
-      retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
-            .apply(operation);
+      operationDone.apply(operation);
 
       if (operation.get().httpErrorStatusCode() != null) {
          logger.warn("delete orphaned network failed. Http Error Code: " + operation.get().httpErrorStatusCode() +

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java
index 4299eab..586f137 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java
@@ -23,24 +23,18 @@ import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Iterables.transform;
 import static com.google.common.collect.Iterables.tryFind;
 import static com.google.common.collect.Lists.newArrayList;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.GCE_BOOT_DISK_SUFFIX;
 import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.GCE_DELETE_BOOT_DISK_METADATA_KEY;
 import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.GCE_IMAGE_METADATA_KEY;
 import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.GCE_IMAGE_PROJECTS;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_INTERVAL;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_TIMEOUT;
 import static org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig.Type;
 import static org.jclouds.googlecomputeengine.internal.ListPages.concat;
-import static org.jclouds.googlecomputeengine.predicates.InstancePredicates.isBootDisk;
-import static org.jclouds.util.Predicates2.retry;
 
 import java.net.URI;
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Inject;
@@ -66,7 +60,9 @@ import org.jclouds.googlecomputeengine.domain.Instance.AttachedDisk;
 import org.jclouds.googlecomputeengine.domain.Instance.AttachedDisk.Mode;
 import org.jclouds.googlecomputeengine.domain.ListPage;
 import org.jclouds.googlecomputeengine.domain.MachineType;
+import org.jclouds.googlecomputeengine.domain.Metadata;
 import org.jclouds.googlecomputeengine.domain.Operation;
+import org.jclouds.googlecomputeengine.domain.Tags;
 import org.jclouds.googlecomputeengine.domain.templates.InstanceTemplate;
 import org.jclouds.googlecomputeengine.domain.templates.InstanceTemplate.PersistentDisk;
 import org.jclouds.googlecomputeengine.features.DiskApi;
@@ -94,9 +90,8 @@ public final class GoogleComputeEngineServiceAdapter
    private final Supplier<String> userProject;
    private final Supplier<Set<String>> zoneIds;
    private final Function<TemplateOptions, ImmutableMap.Builder<String, String>> metatadaFromTemplateOptions;
-   private final Predicate<AtomicReference<Operation>> retryOperationDonePredicate;
-   private final long operationCompleteCheckInterval;
-   private final long operationCompleteCheckTimeout;
+   private final Predicate<AtomicReference<Operation>> operationDone;
+   private final Predicate<AtomicReference<Instance>> instanceVisible;
    private final FirewallTagNamingConvention.Factory firewallTagNamingConvention;
    private final List<String> imageProjects;
 
@@ -104,19 +99,16 @@ public final class GoogleComputeEngineServiceAdapter
                                             @UserProject Supplier<String> userProject,
                                             Function<TemplateOptions,
                                                     ImmutableMap.Builder<String, String>> metatadaFromTemplateOptions,
-                                            @Named("zone") Predicate<AtomicReference<Operation>> operationDonePredicate,
-                                            @Named(OPERATION_COMPLETE_INTERVAL) Long operationCompleteCheckInterval,
-                                            @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout,
+                                            Predicate<AtomicReference<Operation>> operationDone,
+                                            Predicate<AtomicReference<Instance>> instanceVisible,
                                             @Zone Supplier<Set<String>> zoneIds,
                                             FirewallTagNamingConvention.Factory firewallTagNamingConvention,
                                             @Named(GCE_IMAGE_PROJECTS) String imageProjects) {
       this.api = api;
       this.userProject = userProject;
       this.metatadaFromTemplateOptions = metatadaFromTemplateOptions;
-      this.operationCompleteCheckInterval = operationCompleteCheckInterval;
-      this.operationCompleteCheckTimeout = operationCompleteCheckTimeout;
-      this.retryOperationDonePredicate = retry(operationDonePredicate, operationCompleteCheckTimeout,
-                                               operationCompleteCheckInterval, TimeUnit.MILLISECONDS);
+      this.operationDone = operationDone;
+      this.instanceVisible = instanceVisible;
       this.zoneIds = zoneIds;
       this.firewallTagNamingConvention = firewallTagNamingConvention;
       this.imageProjects = Splitter.on(',').splitToList(imageProjects);
@@ -174,34 +166,39 @@ public final class GoogleComputeEngineServiceAdapter
       instanceTemplate.serviceAccounts(options.getServiceAccounts());
 
       String zone = template.getLocation().getId();
-      final InstanceApi instanceApi = api.getInstanceApi(userProject.get(), zone);
+      InstanceApi instanceApi = api.getInstanceApi(userProject.get(), zone);
       Operation operation = instanceApi.create(name, instanceTemplate);
 
       if (options.shouldBlockUntilRunning()) {
          waitOperationDone(operation);
       }
 
-      // some times the newly created instances are not immediately returned
-      AtomicReference<Instance> instance = Atomics.newReference();
-
-      retry(new Predicate<AtomicReference<Instance>>() {
-         @Override public boolean apply(AtomicReference<Instance> input) {
-            input.set(instanceApi.get(name));
-            return input.get() != null;
-         }
-      }, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS).apply(instance);
+      // Some times the newly created instances are not immediately visible to the api.
+      // Make sure that we have a stub object in case that happens.
+      AtomicReference<Instance> instance = Atomics.newReference(Instance.create( //
+            "0000000000000000000", // id can't be null, but isn't available until provisioning is done.
+            operation.targetLink(), // selfLink
+            instanceTemplate.name(), // name
+            instanceTemplate.description(), // description
+            Tags.create(null, null), // tags
+            instanceTemplate.machineType(), // machineType
+            Instance.Status.PROVISIONING, // status
+            null, // statusMessage
+            operation.zone(), // zone
+            null, // networkInterfaces
+            null, // disks
+            Metadata.create(null, null), // metadata
+            instanceTemplate.serviceAccounts() // serviceAccounts
+      ));
+
+      instanceVisible.apply(instance);
 
       if (!options.getTags().isEmpty()) {
          Operation tagsOperation = instanceApi.setTags(name, options.getTags(), instance.get().tags().fingerprint());
 
          waitOperationDone(tagsOperation);
 
-         retry(new Predicate<AtomicReference<Instance>>() {
-            @Override public boolean apply(AtomicReference<Instance> input) {
-               input.set(instanceApi.get(name));
-               return input.get() != null;
-            }
-         }, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS).apply(instance);
+         instanceVisible.apply(instance);
       }
 
       // Add tags for security groups
@@ -397,7 +394,7 @@ public final class GoogleComputeEngineServiceAdapter
       AtomicReference<Operation> operationRef = Atomics.newReference(operation);
 
       // wait for the operation to complete
-      if (!retryOperationDonePredicate.apply(operationRef)) {
+      if (!operationDone.apply(operationRef)) {
          throw new UncheckedTimeoutException("operation did not reach DONE state" + operationRef.get());
       }
 
@@ -408,4 +405,12 @@ public final class GoogleComputeEngineServiceAdapter
                      " HttpError: " + operationRef.get().httpErrorMessage());
       }
    }
+
+   private static Predicate<PersistentDisk> isBootDisk() {
+      return new Predicate<PersistentDisk>() {
+         @Override public boolean apply(PersistentDisk input) {
+            return input.boot();
+         }
+      };
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java
index 76dabd7..d7317b2 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java
@@ -17,12 +17,18 @@
 package org.jclouds.googlecomputeengine.compute.config;
 
 import static com.google.common.base.Suppliers.memoizeWithExpiration;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_INTERVAL;
+import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_TIMEOUT;
+import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
+import static org.jclouds.util.Predicates2.retry;
 
 import java.net.URI;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Named;
 import javax.inject.Singleton;
@@ -55,8 +61,11 @@ import org.jclouds.googlecomputeengine.compute.functions.InstanceInZoneToNodeMet
 import org.jclouds.googlecomputeengine.compute.functions.MachineTypeInZoneToHardware;
 import org.jclouds.googlecomputeengine.compute.functions.NetworkToSecurityGroup;
 import org.jclouds.googlecomputeengine.compute.functions.OrphanedGroupsFromDeadNodes;
+import org.jclouds.googlecomputeengine.compute.functions.ResourceFunctions;
 import org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions;
 import org.jclouds.googlecomputeengine.compute.predicates.AllNodesInGroupTerminated;
+import org.jclouds.googlecomputeengine.compute.predicates.AtomicInstanceVisible;
+import org.jclouds.googlecomputeengine.compute.predicates.AtomicOperationDone;
 import org.jclouds.googlecomputeengine.compute.strategy.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
 import org.jclouds.googlecomputeengine.compute.strategy.PopulateDefaultLoginCredentialsForImageStrategy;
 import org.jclouds.googlecomputeengine.compute.strategy.UseNodeCredentialsButOverrideFromTemplate;
@@ -64,6 +73,7 @@ import org.jclouds.googlecomputeengine.domain.Firewall;
 import org.jclouds.googlecomputeengine.domain.Image;
 import org.jclouds.googlecomputeengine.domain.Instance;
 import org.jclouds.googlecomputeengine.domain.Network;
+import org.jclouds.googlecomputeengine.domain.Operation;
 import org.jclouds.net.domain.IpPermission;
 
 import com.google.common.base.Function;
@@ -137,6 +147,19 @@ public final class GoogleComputeEngineServiceContextModule
 
       bind(PrioritizeCredentialsFromTemplate.class).to(UseNodeCredentialsButOverrideFromTemplate.class);
       bind(FirewallTagNamingConvention.Factory.class).in(Scopes.SINGLETON);
+
+      bindHttpApi(binder(), ResourceFunctions.class);
+   }
+
+   // TODO: these timeouts need thinking through.
+   @Provides Predicate<AtomicReference<Operation>> operationDone(AtomicOperationDone input,
+         @Named(OPERATION_COMPLETE_TIMEOUT) long timeout, @Named(OPERATION_COMPLETE_INTERVAL) long interval) {
+      return retry(input, timeout, interval, MILLISECONDS);
+   }
+
+   @Provides Predicate<AtomicReference<Instance>> instanceVisible(AtomicInstanceVisible input,
+         @Named(OPERATION_COMPLETE_TIMEOUT) long timeout, @Named(OPERATION_COMPLETE_INTERVAL) long interval) {
+      return retry(input, timeout, interval, MILLISECONDS);
    }
 
    @Provides @Singleton @Memoized Supplier<Map<URI, org.jclouds.compute.domain.Image>> imageByUri(

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/extensions/GoogleComputeEngineSecurityGroupExtension.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/extensions/GoogleComputeEngineSecurityGroupExtension.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/extensions/GoogleComputeEngineSecurityGroupExtension.java
index 28ee3eb..d92b304 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/extensions/GoogleComputeEngineSecurityGroupExtension.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/extensions/GoogleComputeEngineSecurityGroupExtension.java
@@ -21,11 +21,11 @@ import static com.google.common.base.Preconditions.checkState;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_INTERVAL;
 import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_TIMEOUT;
+import static org.jclouds.googlecomputeengine.compute.predicates.NetworkFirewallPredicates.equalsIpPermission;
+import static org.jclouds.googlecomputeengine.compute.predicates.NetworkFirewallPredicates.providesIpPermission;
 import static org.jclouds.googlecomputeengine.compute.strategy.CreateNodesWithGroupEncodedIntoNameThenAddToSet.DEFAULT_INTERNAL_NETWORK_RANGE;
 import static org.jclouds.googlecomputeengine.internal.ListPages.concat;
 import static org.jclouds.googlecomputeengine.options.ListOptions.Builder.filter;
-import static org.jclouds.googlecomputeengine.predicates.NetworkFirewallPredicates.equalsIpPermission;
-import static org.jclouds.googlecomputeengine.predicates.NetworkFirewallPredicates.providesIpPermission;
 import static org.jclouds.util.Predicates2.retry;
 
 import java.util.Collection;
@@ -66,38 +66,31 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.Atomics;
 
-/**
- * An extension to compute service to allow for the manipulation of {@link org.jclouds.compute.domain.SecurityGroup}s.
- * Implementation
- * is optional by providers.
- */
-public class GoogleComputeEngineSecurityGroupExtension implements SecurityGroupExtension {
+public final class GoogleComputeEngineSecurityGroupExtension implements SecurityGroupExtension {
 
    private final Supplier<String> userProject;
    private final GroupNamingConvention.Factory namingConvention;
    private final LoadingCache<NetworkAndAddressRange, Network> networkCreator;
    private final Function<Network, SecurityGroup> groupConverter;
    private final GoogleComputeEngineApi api;
-   private final Predicate<AtomicReference<Operation>> operationDonePredicate;
+   private final Predicate<AtomicReference<Operation>> operationDone;
    private final long operationCompleteCheckInterval;
    private final long operationCompleteCheckTimeout;
 
    @Inject GoogleComputeEngineSecurityGroupExtension(GoogleComputeEngineApi api,
          @UserProject Supplier<String> userProject, GroupNamingConvention.Factory namingConvention,
          LoadingCache<NetworkAndAddressRange, Network> networkCreator, Function<Network, SecurityGroup> groupConverter,
-         @Named("global") Predicate<AtomicReference<Operation>> operationDonePredicate,
+         Predicate<AtomicReference<Operation>> operationDone,
          @Named(OPERATION_COMPLETE_INTERVAL) Long operationCompleteCheckInterval,
          @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout) {
-      this.api = checkNotNull(api, "api");
-      this.userProject = checkNotNull(userProject, "userProject");
-      this.namingConvention = checkNotNull(namingConvention, "namingConvention");
-      this.networkCreator = checkNotNull(networkCreator, "networkCreator");
-      this.groupConverter = checkNotNull(groupConverter, "groupConverter");
-      this.operationCompleteCheckInterval = checkNotNull(operationCompleteCheckInterval,
-            "operation completed check interval");
-      this.operationCompleteCheckTimeout = checkNotNull(operationCompleteCheckTimeout,
-            "operation completed check timeout");
-      this.operationDonePredicate = checkNotNull(operationDonePredicate, "operationDonePredicate");
+      this.api = api;
+      this.userProject = userProject;
+      this.namingConvention = namingConvention;
+      this.networkCreator = networkCreator;
+      this.groupConverter = groupConverter;
+      this.operationCompleteCheckInterval = operationCompleteCheckInterval;
+      this.operationCompleteCheckTimeout = operationCompleteCheckTimeout;
+      this.operationDone = operationDone;
    }
 
    @Override
@@ -177,7 +170,7 @@ public class GoogleComputeEngineSecurityGroupExtension implements SecurityGroupE
          AtomicReference<Operation> operation = Atomics
                .newReference(api.getFirewallApi(userProject.get()).delete(fw.name()));
 
-         retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
+         retry(operationDone, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
                .apply(operation);
 
          checkState(operation.get().httpErrorStatusCode() == null,
@@ -186,7 +179,7 @@ public class GoogleComputeEngineSecurityGroupExtension implements SecurityGroupE
 
       AtomicReference<Operation> operation = Atomics.newReference(api.getNetworkApi(userProject.get()).delete(id));
 
-      retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
+      retry(operationDone, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
             .apply(operation);
 
       checkState(operation.get().httpErrorStatusCode() == null,
@@ -233,7 +226,7 @@ public class GoogleComputeEngineSecurityGroupExtension implements SecurityGroupE
       AtomicReference<Operation> operation = Atomics.newReference(
             api.getFirewallApi(userProject.get()).createInNetwork(uniqueFwName, group.getUri(), fwOptions));
 
-      retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
+      retry(operationDone, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
             .apply(operation);
 
       checkState(operation.get().httpErrorStatusCode() == null,
@@ -274,7 +267,7 @@ public class GoogleComputeEngineSecurityGroupExtension implements SecurityGroupE
             AtomicReference<Operation> operation = Atomics
                   .newReference(api.getFirewallApi(userProject.get()).delete(fw.name()));
 
-            retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
+            retry(operationDone, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
                   .apply(operation);
 
             checkState(operation.get().httpErrorStatusCode() == null,

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/CreateNetworkIfNeeded.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/CreateNetworkIfNeeded.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/CreateNetworkIfNeeded.java
index 4b4b113..338b731 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/CreateNetworkIfNeeded.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/CreateNetworkIfNeeded.java
@@ -18,15 +18,10 @@ package org.jclouds.googlecomputeengine.compute.functions;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_INTERVAL;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_TIMEOUT;
-import static org.jclouds.util.Predicates2.retry;
 
 import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 import org.jclouds.googlecomputeengine.GoogleComputeEngineApi;
 import org.jclouds.googlecomputeengine.compute.domain.NetworkAndAddressRange;
@@ -39,24 +34,16 @@ import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.util.concurrent.Atomics;
 
-public class CreateNetworkIfNeeded implements Function<NetworkAndAddressRange, Network> {
+public final class CreateNetworkIfNeeded implements Function<NetworkAndAddressRange, Network> {
    private final GoogleComputeEngineApi api;
    private final Supplier<String> userProject;
-   private final Predicate<AtomicReference<Operation>> operationDonePredicate;
-   private final long operationCompleteCheckInterval;
-   private final long operationCompleteCheckTimeout;
+   private final Predicate<AtomicReference<Operation>> operationDone;
 
    @Inject CreateNetworkIfNeeded(GoogleComputeEngineApi api, @UserProject Supplier<String> userProject,
-         @Named("global") Predicate<AtomicReference<Operation>> operationDonePredicate,
-         @Named(OPERATION_COMPLETE_INTERVAL) Long operationCompleteCheckInterval,
-         @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout) {
-      this.api = checkNotNull(api, "api");
-      this.userProject = checkNotNull(userProject, "userProject");
-      this.operationCompleteCheckInterval = checkNotNull(operationCompleteCheckInterval,
-            "operation completed check interval");
-      this.operationCompleteCheckTimeout = checkNotNull(operationCompleteCheckTimeout,
-            "operation completed check timeout");
-      this.operationDonePredicate = checkNotNull(operationDonePredicate, "operationDonePredicate");
+         Predicate<AtomicReference<Operation>> operationDone) {
+      this.api = api;
+      this.userProject = userProject;
+      this.operationDone = operationDone;
    }
 
    @Override
@@ -71,16 +58,14 @@ public class CreateNetworkIfNeeded implements Function<NetworkAndAddressRange, N
       if (input.gateway() != null) {
          AtomicReference<Operation> operation = Atomics.newReference(api.getNetworkApi(userProject.get())
                .createInIPv4RangeWithGateway(input.name(), input.rangeIPv4(), input.gateway()));
-         retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
-               .apply(operation);
+         operationDone.apply(operation);
 
          checkState(operation.get().httpErrorStatusCode() == null,
                "Could not insert network, operation failed" + operation);
       } else {
          AtomicReference<Operation> operation = Atomics
                .newReference(api.getNetworkApi(userProject.get()).createInIPv4Range(input.name(), input.rangeIPv4()));
-         retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS)
-               .apply(operation);
+         operationDone.apply(operation);
 
          checkState(operation.get().httpErrorStatusCode() == null,
                "Could not insert network, operation failed" + operation);

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java
index 51e331b..517221b 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java
@@ -25,14 +25,13 @@ import com.google.common.base.Predicate;
 /**
  * The convention for naming instance tags that firewall rules recognise.
  */
-public class FirewallTagNamingConvention {
+public final class FirewallTagNamingConvention {
 
-   public static class Factory {
+   public static final class Factory {
 
       private final GroupNamingConvention.Factory namingConvention;
 
-      @Inject
-      public Factory(GroupNamingConvention.Factory namingConvention) {
+      @Inject Factory(GroupNamingConvention.Factory namingConvention) {
          this.namingConvention = namingConvention;
       }
 
@@ -59,5 +58,4 @@ public class FirewallTagNamingConvention {
          }
       };
    }
-
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/ResourceFunctions.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/ResourceFunctions.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/ResourceFunctions.java
new file mode 100644
index 0000000..6b16725
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/ResourceFunctions.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.jclouds.googlecomputeengine.compute.functions;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_READONLY_SCOPE;
+
+import java.net.URI;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+
+import org.jclouds.googlecomputeengine.domain.Instance;
+import org.jclouds.googlecomputeengine.domain.Operation;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.oauth.v2.config.OAuthScopes;
+import org.jclouds.oauth.v2.filters.OAuthAuthenticationFilter;
+import org.jclouds.rest.annotations.EndpointParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SkipEncoding;
+
+@SkipEncoding({'/', '='})
+@RequestFilters(OAuthAuthenticationFilter.class)
+@Consumes(APPLICATION_JSON)
+public interface ResourceFunctions {
+
+   /** Returns an instance by self-link or null if not found. */
+   @Named("Instances:get")
+   @GET
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @Fallback(NullOnNotFoundOr404.class) @Nullable Instance instance(@EndpointParam URI selfLink);
+
+   /** Returns an operation by self-link or null if not found. */
+   @Named("Operations:get")
+   @GET
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @Fallback(NullOnNotFoundOr404.class) @Nullable Operation operation(@EndpointParam URI selfLink);
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AllNodesInGroupTerminated.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AllNodesInGroupTerminated.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AllNodesInGroupTerminated.java
index 38066ba..7897907 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AllNodesInGroupTerminated.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AllNodesInGroupTerminated.java
@@ -16,7 +16,6 @@
  */
 package org.jclouds.googlecomputeengine.compute.predicates;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.Iterables.all;
 import static com.google.common.collect.Sets.filter;
 import static org.jclouds.compute.predicates.NodePredicates.TERMINATED;
@@ -24,25 +23,21 @@ import static org.jclouds.compute.predicates.NodePredicates.all;
 import static org.jclouds.compute.predicates.NodePredicates.inGroup;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import org.jclouds.compute.ComputeService;
 
 import com.google.common.base.Predicate;
 
-@Singleton
-public class AllNodesInGroupTerminated implements Predicate<String> {
+public final class AllNodesInGroupTerminated implements Predicate<String> {
 
    private final ComputeService computeService;
 
-   @Inject
-   public AllNodesInGroupTerminated(ComputeService computeService) {
-      this.computeService = checkNotNull(computeService, "compute service");
+   @Inject AllNodesInGroupTerminated(ComputeService computeService) {
+      this.computeService = computeService;
    }
 
 
-   @Override
-   public boolean apply(String groupName) {
+   @Override public boolean apply(String groupName) {
       return all(filter(computeService.listNodesDetailsMatching(all()), inGroup(groupName)), TERMINATED);
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AtomicInstanceVisible.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AtomicInstanceVisible.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AtomicInstanceVisible.java
new file mode 100644
index 0000000..09d4486
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AtomicInstanceVisible.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.jclouds.googlecomputeengine.compute.predicates;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.inject.Inject;
+
+import org.jclouds.googlecomputeengine.compute.functions.ResourceFunctions;
+import org.jclouds.googlecomputeengine.domain.Instance;
+
+import com.google.common.base.Predicate;
+
+public final class AtomicInstanceVisible implements Predicate<AtomicReference<Instance>> {
+
+   private final ResourceFunctions resources;
+
+   @Inject AtomicInstanceVisible(ResourceFunctions resources) {
+      this.resources = resources;
+   }
+
+   @Override public boolean apply(AtomicReference<Instance> input) {
+      Instance response = resources.instance(input.get().selfLink());
+      if (response == null) {
+         return false;
+      }
+      input.set(response);
+      return true;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AtomicOperationDone.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AtomicOperationDone.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AtomicOperationDone.java
new file mode 100644
index 0000000..afe0dd0
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/AtomicOperationDone.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.jclouds.googlecomputeengine.compute.predicates;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.inject.Inject;
+
+import org.jclouds.googlecomputeengine.compute.functions.ResourceFunctions;
+import org.jclouds.googlecomputeengine.domain.Operation;
+
+import com.google.common.base.Predicate;
+
+public final class AtomicOperationDone implements Predicate<AtomicReference<Operation>> {
+
+   private final ResourceFunctions resources;
+
+   @Inject AtomicOperationDone(ResourceFunctions resources) {
+      this.resources = resources;
+   }
+
+   @Override public boolean apply(AtomicReference<Operation> input) {
+      checkNotNull(input.get(), "operation");
+      Operation current = resources.operation(input.get().selfLink());
+      input.set(current);
+      checkState(current.errors().isEmpty(), "Task ended in error %s", current); // ISE will break the loop.
+      switch (current.status()) {
+         case DONE:
+            return true;
+         case PENDING:
+         case RUNNING:
+         default:
+            return false;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/NetworkFirewallPredicates.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/NetworkFirewallPredicates.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/NetworkFirewallPredicates.java
new file mode 100644
index 0000000..02b189a
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/predicates/NetworkFirewallPredicates.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.jclouds.googlecomputeengine.compute.predicates;
+
+import static com.google.common.collect.Sets.intersection;
+
+import java.util.List;
+
+import org.jclouds.googlecomputeengine.domain.Firewall;
+import org.jclouds.googlecomputeengine.domain.Firewall.Rule;
+import org.jclouds.net.domain.IpPermission;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public final class NetworkFirewallPredicates {
+
+   public static Predicate<Firewall> hasPortRange(final String protocol, final int fromPort, final int toPort) {
+      return new Predicate<Firewall>() {
+         @Override public boolean apply(Firewall fw) {
+            for (Rule rule : fw.allowed()) {
+               if (!rule.ipProtocol().equals(protocol)) {
+                  continue;
+               }
+               if (rule.ports() == null || rule.ports().isEmpty()) {
+                  return true;
+               }
+               for (String range : rule.ports()) {
+                  if (range.indexOf('-') != -1) {
+                     if (inRange(range, fromPort, toPort)) {
+                        return true;
+                     }
+                  }
+               }
+            }
+            return false;
+         }
+      };
+   }
+
+   private static boolean inRange(String range, int fromPort, int toPort) {
+      List<String> ports = Splitter.on('-').splitToList(range);
+      return fromPort >= Integer.valueOf(ports.get(0)) && toPort <= Integer.valueOf(ports.get(1));
+   }
+
+   public static Predicate<Firewall> hasSourceTag(final String sourceTag) {
+      return new Predicate<Firewall>() {
+         @Override public boolean apply(Firewall input) {
+            return input.sourceTags().contains(sourceTag);
+         }
+      };
+   }
+
+   public static Predicate<Firewall> hasSourceRange(final String sourceRange) {
+      return new Predicate<Firewall>() {
+         @Override  public boolean apply(Firewall input) {
+            return input.sourceRanges().contains(sourceRange);
+         }
+      };
+   }
+
+   public static Predicate<Firewall> equalsIpPermission(final IpPermission permission) {
+      return new Predicate<Firewall>() {
+         @Override public boolean apply(Firewall input) {
+            return Iterables.elementsEqual(permission.getGroupIds(), input.sourceTags())
+                      && Iterables.elementsEqual(permission.getCidrBlocks(), input.sourceRanges())
+                      && (input.allowed().size() == 1
+                             && ruleEqualsIpPermission(permission).apply(Iterables.getOnlyElement(input.allowed())));
+         }
+      };
+   }
+
+   public static Predicate<Firewall> providesIpPermission(final IpPermission permission) {
+      return new Predicate<Firewall>() {
+         @Override  public boolean apply(Firewall input) {
+            boolean groupsMatchTags =
+                  (permission.getGroupIds().isEmpty() && input.sourceTags().isEmpty()) || !intersection(
+                        permission.getGroupIds(), ImmutableSet.copyOf(input.sourceTags())).isEmpty();
+            boolean cidrsMatchRanges =
+                  (permission.getCidrBlocks().isEmpty() && input.sourceRanges().isEmpty()) || !intersection(
+                        permission.getCidrBlocks(), ImmutableSet.copyOf(input.sourceRanges())).isEmpty();
+            boolean firewallHasPorts = hasPortRange(permission.getIpProtocol().value().toLowerCase(),
+                        permission.getFromPort(), permission.getToPort()).apply(input);
+            return groupsMatchTags && cidrsMatchRanges && firewallHasPorts;
+         }
+      };
+   }
+
+   private static Predicate<Firewall.Rule> ruleEqualsIpPermission(final IpPermission permission) {
+      return new Predicate<Rule>() {
+         @Override public boolean apply(Firewall.Rule input) {
+            if (!permission.getIpProtocol().value().toLowerCase().equals(input.ipProtocol())) {
+               return false;
+            }
+            if (input.ports() == null
+                  || input.ports().isEmpty() && permission.getFromPort() == 0 && permission.getToPort() == 0) {
+               return true;
+            } else if (input.ports().size() == 1) {
+               String port = Iterables.getOnlyElement(input.ports());
+               if (permission.getFromPort() == permission.getToPort()) {
+                  return port.equals(String.valueOf(permission.getFromPort()));
+               }
+               return port.equals(permission.getFromPort() + "-" + permission.getToPort());
+            }
+            return false;
+         }
+      };
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java
index b2ba892..ee2c877 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java
@@ -18,10 +18,6 @@ package org.jclouds.googlecomputeengine.compute.strategy;
 
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableList.of;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_INTERVAL;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_TIMEOUT;
-import static org.jclouds.util.Predicates2.retry;
 
 import java.util.List;
 import java.util.Map;
@@ -62,7 +58,7 @@ import com.google.common.util.concurrent.Atomics;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 
-public class CreateNodesWithGroupEncodedIntoNameThenAddToSet extends
+public final class CreateNodesWithGroupEncodedIntoNameThenAddToSet extends
         org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet {
 
    public static final String EXTERIOR_RANGE = "0.0.0.0/0";
@@ -71,9 +67,7 @@ public class CreateNodesWithGroupEncodedIntoNameThenAddToSet extends
    private final GoogleComputeEngineApi api;
    private final Supplier<String> userProject;
    private final LoadingCache<NetworkAndAddressRange, Network> networkMap;
-   private final Predicate<AtomicReference<Operation>> operationDonePredicate;
-   private final long operationCompleteCheckInterval;
-   private final long operationCompleteCheckTimeout;
+   private final Predicate<AtomicReference<Operation>> operationDone;
    private final FirewallTagNamingConvention.Factory firewallTagNamingConvention;
 
    @Inject CreateNodesWithGroupEncodedIntoNameThenAddToSet(
@@ -86,18 +80,14 @@ public class CreateNodesWithGroupEncodedIntoNameThenAddToSet extends
                    customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
            GoogleComputeEngineApi api,
            @UserProject Supplier<String> userProject,
-           @Named("global") Predicate<AtomicReference<Operation>> operationDonePredicate,
-           @Named(OPERATION_COMPLETE_INTERVAL) Long operationCompleteCheckInterval,
-           @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout,
+           Predicate<AtomicReference<Operation>> operationDone,
            LoadingCache<NetworkAndAddressRange, Network> networkMap,
            FirewallTagNamingConvention.Factory firewallTagNamingConvention) {
       super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
               customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
       this.api = api;
       this.userProject = userProject;
-      this.operationCompleteCheckInterval = operationCompleteCheckInterval;
-      this.operationCompleteCheckTimeout = operationCompleteCheckTimeout;
-      this.operationDonePredicate = operationDonePredicate;
+      this.operationDone = operationDone;
       this.networkMap = networkMap;
       this.firewallTagNamingConvention = firewallTagNamingConvention;
    }
@@ -171,11 +161,9 @@ public class CreateNodesWithGroupEncodedIntoNameThenAddToSet extends
       }
 
       for (AtomicReference<Operation> operation : operations) {
-         retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval,
-                 MILLISECONDS).apply(operation);
-         checkState(operation.get().httpErrorStatusCode() == null,
-               "Could not insert firewall, operation failed" + operation);
+         operationDone.apply(operation);
+         checkState(operation.get().httpErrorStatusCode() == null, "Could not insert firewall, operation failed %s",
+               operation);
       }
    }
-
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineHttpApiModule.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineHttpApiModule.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineHttpApiModule.java
index 1f95582..23fed76 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineHttpApiModule.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineHttpApiModule.java
@@ -17,7 +17,6 @@
 package org.jclouds.googlecomputeengine.config;
 
 import static com.google.common.base.Suppliers.compose;
-import static com.google.inject.name.Names.named;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
 
@@ -30,18 +29,12 @@ import javax.inject.Singleton;
 import org.jclouds.domain.Credentials;
 import org.jclouds.googlecomputeengine.GoogleComputeEngineApi;
 import org.jclouds.googlecomputeengine.compute.domain.SlashEncodedIds;
-import org.jclouds.googlecomputeengine.domain.Operation;
 import org.jclouds.googlecomputeengine.handlers.GoogleComputeEngineErrorHandler;
-import org.jclouds.googlecomputeengine.predicates.GlobalOperationDonePredicate;
-import org.jclouds.googlecomputeengine.predicates.RegionOperationDonePredicate;
-import org.jclouds.googlecomputeengine.predicates.ZoneOperationDonePredicate;
 import org.jclouds.http.HttpErrorHandler;
 import org.jclouds.http.Uris;
 import org.jclouds.http.annotation.ClientError;
 import org.jclouds.http.annotation.Redirection;
 import org.jclouds.http.annotation.ServerError;
-import org.jclouds.json.config.GsonModule.DateAdapter;
-import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
 import org.jclouds.location.Provider;
 import org.jclouds.rest.AuthorizationException;
 import org.jclouds.rest.ConfiguresHttpApi;
@@ -49,12 +42,10 @@ import org.jclouds.rest.config.HttpApiModule;
 import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;
 
 import com.google.common.base.Function;
-import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Supplier;
 import com.google.common.collect.Iterables;
 import com.google.inject.Provides;
-import com.google.inject.TypeLiteral;
 
 @ConfiguresHttpApi
 public final class GoogleComputeEngineHttpApiModule extends HttpApiModule<GoogleComputeEngineApi> {
@@ -62,18 +53,6 @@ public final class GoogleComputeEngineHttpApiModule extends HttpApiModule<Google
    }
 
    @Override
-   protected void configure() {
-      bind(DateAdapter.class).to(Iso8601DateAdapter.class);
-      bind(new TypeLiteral<Predicate<AtomicReference<Operation>>>() {
-      }).annotatedWith(named("global")).to(GlobalOperationDonePredicate.class);
-      bind(new TypeLiteral<Predicate<AtomicReference<Operation>>>() {
-      }).annotatedWith(named("region")).to(RegionOperationDonePredicate.class);
-      bind(new TypeLiteral<Predicate<AtomicReference<Operation>>>() {
-      }).annotatedWith(named("zone")).to(ZoneOperationDonePredicate.class);
-      super.configure();
-   }
-
-   @Override
    protected void bindErrorHandlers() {
       bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(GoogleComputeEngineErrorHandler.class);
       bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(GoogleComputeEngineErrorHandler.class);

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java
index ec92c33..cc1c880 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java
@@ -29,12 +29,12 @@ import com.google.auto.value.AutoValue;
 @AutoValue
 public abstract class Tags {
    /** The fingerprint for the items - needed for updating them. */
-   public abstract String fingerprint();
+   @Nullable public abstract String fingerprint();
 
    public abstract List<String> items();
 
    @SerializedNames({ "fingerprint", "items" })
-   public static Tags create(String fingerprint, @Nullable List<String> items) {
+   public static Tags create(String fingerprint, List<String> items) {
       return new AutoValue_Tags(fingerprint, copyOf(items));
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AddressApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AddressApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AddressApi.java
index 71bd2bb..679811d 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AddressApi.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AddressApi.java
@@ -77,7 +77,7 @@ public interface AddressApi {
    @Named("Addresses:insert")
    @POST
    @Produces(APPLICATION_JSON)
-   @OAuthScopes({COMPUTE_SCOPE})
+   @OAuthScopes(COMPUTE_SCOPE)
    @MapBinder(BindToJsonPayload.class)
    Operation create(@PayloadParam("name") String address);
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/FirewallApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/FirewallApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/FirewallApi.java
index 883d647..a9d8818 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/FirewallApi.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/FirewallApi.java
@@ -85,7 +85,7 @@ public interface FirewallApi {
    @Named("Firewalls:insert")
    @POST
    @Produces(APPLICATION_JSON)
-   @OAuthScopes({COMPUTE_SCOPE})
+   @OAuthScopes(COMPUTE_SCOPE)
    @MapBinder(FirewallBinder.class)
    Operation createInNetwork(@PayloadParam("name") String name,
                              @PayloadParam("network") URI network,
@@ -103,7 +103,7 @@ public interface FirewallApi {
    @PUT
    @Produces(APPLICATION_JSON)
    @Path("/{firewall}")
-   @OAuthScopes({COMPUTE_SCOPE})
+   @OAuthScopes(COMPUTE_SCOPE)
    Operation update(@PathParam("firewall") String firewall,
                     @BinderParam(BindToJsonPayload.class) FirewallOptions firewallOptions);
 
@@ -119,7 +119,7 @@ public interface FirewallApi {
    @PATCH
    @Produces(APPLICATION_JSON)
    @Path("/{firewall}")
-   @OAuthScopes({COMPUTE_SCOPE})
+   @OAuthScopes(COMPUTE_SCOPE)
    Operation patch(@PathParam("firewall") String firewall,
                    @BinderParam(BindToJsonPayload.class) FirewallOptions firewallOptions);
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/GlobalOperationApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/GlobalOperationApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/GlobalOperationApi.java
deleted file mode 100644
index 892a8d8..0000000
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/GlobalOperationApi.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.jclouds.googlecomputeengine.features;
-
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
-import static org.jclouds.Fallbacks.VoidOnNotFoundOr404;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_READONLY_SCOPE;
-import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_SCOPE;
-
-import java.util.Iterator;
-
-import javax.inject.Named;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.QueryParam;
-
-import org.jclouds.Fallbacks.NullOnNotFoundOr404;
-import org.jclouds.googlecomputeengine.GoogleComputeEngineFallbacks.EmptyIteratorOnNotFoundOr404;
-import org.jclouds.googlecomputeengine.GoogleComputeEngineFallbacks.EmptyListPageOnNotFoundOr404;
-import org.jclouds.googlecomputeengine.domain.ListPage;
-import org.jclouds.googlecomputeengine.domain.Operation;
-import org.jclouds.googlecomputeengine.functions.internal.ParseGlobalOperations;
-import org.jclouds.googlecomputeengine.options.ListOptions;
-import org.jclouds.javax.annotation.Nullable;
-import org.jclouds.oauth.v2.config.OAuthScopes;
-import org.jclouds.oauth.v2.filters.OAuthAuthenticationFilter;
-import org.jclouds.rest.annotations.Fallback;
-import org.jclouds.rest.annotations.RequestFilters;
-import org.jclouds.rest.annotations.ResponseParser;
-import org.jclouds.rest.annotations.SkipEncoding;
-import org.jclouds.rest.annotations.Transform;
-
-@SkipEncoding({'/', '='})
-@RequestFilters(OAuthAuthenticationFilter.class)
-@Path("/operations")
-@Consumes(APPLICATION_JSON)
-public interface GlobalOperationApi {
-
-   /** Returns an operation by name or null if not found. */
-   @Named("GlobalOperations:get")
-   @GET
-   @Path("/{operation}")
-   @OAuthScopes(COMPUTE_READONLY_SCOPE)
-   @Fallback(NullOnNotFoundOr404.class)
-   @Nullable
-   Operation get(@PathParam("operation") String operation);
-
-   /** Deletes an operation by name. */
-   @Named("GlobalOperations:delete")
-   @DELETE
-   @Path("/{operation}")
-   @OAuthScopes(COMPUTE_SCOPE)
-   @Fallback(VoidOnNotFoundOr404.class)
-   void delete(@PathParam("operation") String operation);
-
-   /**
-    * Retrieves the list of operation resources available to the specified project.
-    * By default the list as a maximum size of 100, if no options are provided or ListOptions#getMaxResults() has not
-    * been set.
-    *
-    * @param token       marks the beginning of the next list page
-    * @param listOptions listing options
-    * @return a page of the list
-    */
-   @Named("GlobalOperations:list")
-   @GET
-   @OAuthScopes(COMPUTE_READONLY_SCOPE)
-   @ResponseParser(ParseGlobalOperations.class)
-   @Fallback(EmptyListPageOnNotFoundOr404.class)
-   ListPage<Operation> listPage(@Nullable @QueryParam("pageToken") String token, ListOptions listOptions);
-
-   /**
-    * @see #list(org.jclouds.googlecomputeengine.options.ListOptions)
-    */
-   @Named("GlobalOperations:list")
-   @GET
-   @OAuthScopes(COMPUTE_READONLY_SCOPE)
-   @ResponseParser(ParseGlobalOperations.class)
-   @Transform(ParseGlobalOperations.ToIteratorOfListPage.class)
-   @Fallback(EmptyIteratorOnNotFoundOr404.class)
-   Iterator<ListPage<Operation>> list();
-
-   /**
-    * @see #list(org.jclouds.googlecomputeengine.options.ListOptions)
-    */
-   @Named("GlobalOperations:list")
-   @GET
-   @OAuthScopes(COMPUTE_READONLY_SCOPE)
-   @ResponseParser(ParseGlobalOperations.class)
-   @Transform(ParseGlobalOperations.ToIteratorOfListPage.class)
-   @Fallback(EmptyIteratorOnNotFoundOr404.class)
-   Iterator<ListPage<Operation>> list(ListOptions options);
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/InstanceApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/InstanceApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/InstanceApi.java
index 33ed393..41524ec 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/InstanceApi.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/InstanceApi.java
@@ -86,7 +86,7 @@ public interface InstanceApi {
    @Named("Instances:insert")
    @POST
    @Produces(APPLICATION_JSON)
-   @OAuthScopes({COMPUTE_SCOPE})
+   @OAuthScopes(COMPUTE_SCOPE)
    @MapBinder(InstanceBinder.class)
    Operation create(@PayloadParam("name") String instance, @PayloadParam("template") InstanceTemplate template);
 
@@ -150,7 +150,7 @@ public interface InstanceApi {
    @POST
    @Produces(APPLICATION_JSON)
    @Path("/{instance}/addAccessConfig")
-   @OAuthScopes({COMPUTE_SCOPE})
+   @OAuthScopes(COMPUTE_SCOPE)
    Operation addAccessConfigToNic(@PathParam("instance") String instance,
                                   @BinderParam(BindToJsonPayload.class)
                                   AccessConfig accessConfig,

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/NetworkApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/NetworkApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/NetworkApi.java
index 7b80233..ed917a2 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/NetworkApi.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/NetworkApi.java
@@ -77,7 +77,7 @@ public interface NetworkApi {
    @Named("Networks:insert")
    @POST
    @Produces(APPLICATION_JSON)
-   @OAuthScopes({COMPUTE_SCOPE})
+   @OAuthScopes(COMPUTE_SCOPE)
    @MapBinder(BindToJsonPayload.class)
    Operation createInIPv4Range(@PayloadParam("name") String networkName,
                                @PayloadParam("IPv4Range") String IPv4Range);
@@ -94,7 +94,7 @@ public interface NetworkApi {
    @Named("Networks:insert")
    @POST
    @Produces(APPLICATION_JSON)
-   @OAuthScopes({COMPUTE_SCOPE})
+   @OAuthScopes(COMPUTE_SCOPE)
    @MapBinder(BindToJsonPayload.class)
    Operation createInIPv4RangeWithGateway(@PayloadParam("name") String networkName,
                                           @PayloadParam("IPv4Range") String IPv4Range,

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/8895953d/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/OperationApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/OperationApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/OperationApi.java
new file mode 100644
index 0000000..49bbaf9
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/OperationApi.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.jclouds.googlecomputeengine.features;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_READONLY_SCOPE;
+import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_SCOPE;
+
+import java.net.URI;
+import java.util.Iterator;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.googlecomputeengine.GoogleComputeEngineFallbacks.EmptyIteratorOnNotFoundOr404;
+import org.jclouds.googlecomputeengine.GoogleComputeEngineFallbacks.EmptyListPageOnNotFoundOr404;
+import org.jclouds.googlecomputeengine.domain.ListPage;
+import org.jclouds.googlecomputeengine.domain.Operation;
+import org.jclouds.googlecomputeengine.functions.internal.ParseGlobalOperations;
+import org.jclouds.googlecomputeengine.functions.internal.ParseRegionOperations;
+import org.jclouds.googlecomputeengine.functions.internal.ParseZoneOperations;
+import org.jclouds.googlecomputeengine.options.ListOptions;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.oauth.v2.config.OAuthScopes;
+import org.jclouds.oauth.v2.filters.OAuthAuthenticationFilter;
+import org.jclouds.rest.annotations.EndpointParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SkipEncoding;
+import org.jclouds.rest.annotations.Transform;
+
+@SkipEncoding({'/', '='})
+@RequestFilters(OAuthAuthenticationFilter.class)
+@Consumes(APPLICATION_JSON)
+public interface OperationApi {
+
+   /** Returns an operation by self-link or null if not found. */
+   @Named("Operations:get")
+   @GET
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   Operation get(@EndpointParam URI operation);
+
+   /** Deletes an operation by name. */
+   @Named("Operations:delete")
+   @DELETE
+   @OAuthScopes(COMPUTE_SCOPE)
+   @Fallback(VoidOnNotFoundOr404.class)
+   void delete(@EndpointParam URI operation);
+
+   /**
+    * Retrieves the list of operation resources available to the specified project.
+    * By default the list as a maximum size of 100, if no options are provided or ListOptions#getMaxResults() has not
+    * been set.
+    *
+    * @param token       marks the beginning of the next list page
+    * @param listOptions listing options
+    * @return a page of the list
+    */
+   @Named("GlobalOperations:list")
+   @GET
+   @Path("/projects/{project}/global/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseGlobalOperations.class)
+   @Fallback(EmptyListPageOnNotFoundOr404.class)
+   ListPage<Operation> listPage(@Nullable @QueryParam("pageToken") String token, ListOptions listOptions);
+
+   /**
+    * @see #list(org.jclouds.googlecomputeengine.options.ListOptions)
+    */
+   @Named("GlobalOperations:list")
+   @GET
+   @Path("/projects/{project}/global/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseGlobalOperations.class)
+   @Transform(ParseGlobalOperations.ToIteratorOfListPage.class)
+   @Fallback(EmptyIteratorOnNotFoundOr404.class)
+   Iterator<ListPage<Operation>> list();
+
+   /**
+    * @see #listPage(String, ListOptions)
+    */
+   @Named("GlobalOperations:list")
+   @GET
+   @Path("/projects/{project}/global/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseGlobalOperations.class)
+   @Transform(ParseGlobalOperations.ToIteratorOfListPage.class)
+   @Fallback(EmptyIteratorOnNotFoundOr404.class)
+   Iterator<ListPage<Operation>> list(ListOptions options);
+
+   /**
+    * Retrieves the list of operation resources available in the specified region.
+    * By default the list as a maximum size of 100, if no options are provided or ListOptions#getMaxResults() has not
+    * been set.
+    *
+    * @param token       marks the beginning of the next list page
+    * @param listOptions listing options
+    * @return a page of the list
+    */
+   @Named("RegionOperations:list")
+   @GET
+   @Path("/projects/{project}/regions/{region}/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseRegionOperations.class)
+   @Fallback(EmptyListPageOnNotFoundOr404.class)
+   ListPage<Operation> listPageInRegion(@PathParam("region") String region,
+                                        @Nullable @QueryParam("pageToken") String token, ListOptions listOptions);
+
+   /**
+    * @see #listInRegion(String, org.jclouds.googlecomputeengine.options.ListOptions)
+    */
+   @Named("RegionOperations:list")
+   @GET
+   @Path("/projects/{project}/regions/{region}/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseRegionOperations.class)
+   @Transform(ParseRegionOperations.ToIteratorOfListPage.class)
+   @Fallback(EmptyIteratorOnNotFoundOr404.class)
+   Iterator<ListPage<Operation>> listInRegion(@PathParam("region") String region);
+
+   /**
+    * @see #listPageInRegion(String, String, ListOptions)
+    */
+   @Named("RegionOperations:list")
+   @GET
+   @Path("/projects/{project}/regions/{region}/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseRegionOperations.class)
+   @Transform(ParseRegionOperations.ToIteratorOfListPage.class)
+   @Fallback(EmptyIteratorOnNotFoundOr404.class)
+   Iterator<ListPage<Operation>> listInRegion(@PathParam("region") String region, ListOptions options);
+
+   /**
+    * Retrieves the list of operation resources available in the specified zone.
+    * By default the list as a maximum size of 100, if no options are provided or ListOptions#getMaxResults() has not
+    * been set.
+    *
+    * @param token       marks the beginning of the next list page
+    * @param listOptions listing options
+    * @return a page of the list
+    */
+   @Named("ZoneOperations:list")
+   @GET
+   @Path("/projects/{project}/zones/{zone}/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseZoneOperations.class)
+   @Fallback(EmptyListPageOnNotFoundOr404.class)
+   ListPage<Operation> listPageInZone(@PathParam("zone") String zone,
+         @Nullable @QueryParam("pageToken") String token, ListOptions listOptions);
+
+   /**
+    * @see #listInZone(String, org.jclouds.googlecomputeengine.options.ListOptions)
+    */
+   @Named("ZoneOperations:list")
+   @GET
+   @Path("/projects/{project}/zones/{zone}/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseZoneOperations.class)
+   @Transform(ParseZoneOperations.ToIteratorOfListPage.class)
+   @Fallback(EmptyIteratorOnNotFoundOr404.class)
+   Iterator<ListPage<Operation>> listInZone(@PathParam("zone") String zone);
+
+   /**
+    * @see #listPageInZone(String, String, ListOptions)
+    */
+   @Named("ZoneOperations:list")
+   @GET
+   @Path("/projects/{project}/zones/{zone}/operations")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(ParseZoneOperations.class)
+   @Transform(ParseZoneOperations.ToIteratorOfListPage.class)
+   @Fallback(EmptyIteratorOnNotFoundOr404.class)
+   Iterator<ListPage<Operation>> listInZone(@PathParam("zone") String zone, ListOptions options);
+}