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/09 19:17:06 UTC

[7/8] jclouds-labs-google git commit: * Rewrites InstanceTemplate as NewInstance, strictly from docs. * Rewrites Metadata to have the same shape as in json, avoid really complicated json parser. * Rewrites GoogleComputeEngineServiceAdapter.createNodeW

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/MachineTypeToHardware.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/MachineTypeToHardware.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/MachineTypeToHardware.java
new file mode 100644
index 0000000..a1eaf36
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/MachineTypeToHardware.java
@@ -0,0 +1,83 @@
+/*
+ * 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 java.net.URI;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Volume;
+import org.jclouds.compute.domain.VolumeBuilder;
+import org.jclouds.domain.Location;
+import org.jclouds.googlecomputeengine.domain.MachineType;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableSet;
+
+public final class MachineTypeToHardware implements Function<MachineType, Hardware> {
+
+   private final Supplier<Map<URI, Location>> locationsByUri;
+
+   @Inject MachineTypeToHardware(@Memoized Supplier<Map<URI, Location>> locationsByUri) {
+      this.locationsByUri = locationsByUri;
+   }
+
+   @Override
+   public Hardware apply(MachineType input) {
+      URI zoneLink = URI.create(
+            input.selfLink().toString().replace("/machineTypes/" + input.name(), ""));
+
+      Location zone = locationsByUri.get().get(zoneLink);
+      if (zone == null) {
+         throw new IllegalStateException(
+               String.format("zone %s not present in %s", zoneLink, locationsByUri.get().keySet()));
+      }
+      return new HardwareBuilder()
+              .id(input.selfLink().toString())
+              .providerId(input.id())
+              .location(zone)
+              .name(input.name())
+              .hypervisor("kvm")
+              .processor(new Processor(input.guestCpus(), 1.0))
+              .providerId(input.id())
+              .ram(input.memoryMb())
+              .uri(input.selfLink())
+              .volumes(collectVolumes(input))
+              .supportsImage(Predicates.<Image>alwaysTrue())
+              .build();
+   }
+
+   private Iterable<Volume> collectVolumes(MachineType input) {
+      ImmutableSet.Builder<Volume> volumes = ImmutableSet.builder();
+      for (MachineType.ScratchDisk disk : input.scratchDisks()) {
+         volumes.add(new VolumeBuilder()
+                 .type(Volume.Type.LOCAL)
+                 .size(Float.valueOf(disk.diskGb()))
+                 .bootDevice(true)
+                 .durable(false).build());
+      }
+      return volumes.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/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
deleted file mode 100644
index 6b16725..0000000
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/ResourceFunctions.java
+++ /dev/null
@@ -1,55 +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.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/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/Resources.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/Resources.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/Resources.java
new file mode 100644
index 0000000..34d7d54
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/Resources.java
@@ -0,0 +1,85 @@
+/*
+ * 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 static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_SCOPE;
+
+import java.net.URI;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+
+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.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 Resources {
+
+   /** Returns an image by self-link or null if not found. */
+   @Named("Images:get")
+   @GET
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @Fallback(NullOnNotFoundOr404.class) @Nullable Image image(@EndpointParam URI selfLink);
+
+   /** 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 network by self-link or null if not found. */
+   @Named("Networks:get")
+   @GET
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @Fallback(NullOnNotFoundOr404.class) @Nullable Network network(@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);
+
+   /** Deletes any resource by self-link and returns the operation in progress, or null if not found. */
+   @Named("Resources:delete")
+   @DELETE
+   @OAuthScopes(COMPUTE_SCOPE)
+   @Fallback(NullOnNotFoundOr404.class) @Nullable Operation delete(@EndpointParam URI selfLink);
+
+   /** Hard-resets the instance by self-link and returns the operation in progres */
+   @Named("Instances:reset")
+   @POST
+   @Path("/reset")
+   @OAuthScopes(COMPUTE_SCOPE) Operation resetInstance(@EndpointParam URI selfLink);
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java
index c6e5f69..3aabae3 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java
@@ -16,34 +16,22 @@
  */
 package org.jclouds.googlecomputeengine.compute.options;
 
-import static com.google.common.base.Optional.fromNullable;
-import static org.jclouds.googlecomputeengine.domain.Instance.ServiceAccount;
-
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
 
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.domain.LoginCredentials;
-import org.jclouds.googlecomputeengine.domain.Instance;
-import org.jclouds.googlecomputeengine.domain.templates.InstanceTemplate.PersistentDisk;
+import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.scriptbuilder.domain.Statement;
 
-import com.google.common.base.Optional;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
-/**
- * Instance options specific to Google Compute Engine.
- */
+/** Instance options specific to Google Compute Engine. */
 public final class GoogleComputeEngineTemplateOptions extends TemplateOptions {
 
-   private Optional<URI> network = Optional.absent();
-   private List<Instance.ServiceAccount> serviceAccounts = Lists.newArrayList();
-   private boolean enableNat = true;
-   private List<PersistentDisk> disks = Lists.newArrayList();
-   private Optional<Long> bootDiskSize = Optional.absent();
-   private boolean keepBootDisk = false;
+   private URI network = null;
+   private final List<URI> additionalDisks = Lists.newArrayList();
 
    @Override
    public GoogleComputeEngineTemplateOptions clone() {
@@ -57,89 +45,25 @@ public final class GoogleComputeEngineTemplateOptions extends TemplateOptions {
       super.copyTo(to);
       if (to instanceof GoogleComputeEngineTemplateOptions) {
          GoogleComputeEngineTemplateOptions eTo = GoogleComputeEngineTemplateOptions.class.cast(to);
-         eTo.network(getNetwork().orNull());
-         eTo.serviceAccounts(getServiceAccounts());
-         eTo.enableNat(isEnableNat());
-         eTo.disks(getDisks());
-         eTo.keepBootDisk(shouldKeepBootDisk());
+         eTo.network(network());
       }
    }
 
-   /**
-    * @deprecated See TemplateOptions#networks
-    * @see #getNetworkName()
-    */
-   @Deprecated
-   public GoogleComputeEngineTemplateOptions network(String networkName) {
-      return this.networks(networkName);
-   }
-
-   /**
-    * @see #getNetwork()
-    */
+   /** @see #network()  */
    public GoogleComputeEngineTemplateOptions network(URI network) {
-      this.network = fromNullable(network);
-      return this;
-   }
-
-   /**
-    * @see #getServiceAccounts()
-    * @see ServiceAccount
-    */
-   public GoogleComputeEngineTemplateOptions addServiceAccount(ServiceAccount serviceAccout) {
-      this.serviceAccounts.add(serviceAccout);
-      return this;
-   }
-
-   /**
-    * @see #getServiceAccounts()
-    * @see ServiceAccount
-    */
-   public GoogleComputeEngineTemplateOptions serviceAccounts(List<ServiceAccount> serviceAccounts) {
-      this.serviceAccounts = Lists.newArrayList(serviceAccounts);
+      this.network = network;
       return this;
    }
 
-   /**
-    * @see #getDisks()
-    * @see org.jclouds.googlecomputeengine.domain.templates.InstanceTemplate.PersistentDisk
-    */
-   public GoogleComputeEngineTemplateOptions addDisk(PersistentDisk disk) {
-      this.disks.add(disk);
-      return this;
-   }
-
-   /**
-    * @see #getDisks()
-    * @see org.jclouds.googlecomputeengine.domain.templates.InstanceTemplate.PersistentDisk
-    */
-   public GoogleComputeEngineTemplateOptions disks(List<PersistentDisk> disks) {
-      this.disks = Lists.newArrayList(disks);
-      return this;
-   }
-
-   /**
-    * @see #isEnableNat()
-    */
-   public GoogleComputeEngineTemplateOptions enableNat(boolean enableNat) {
-      this.enableNat = enableNat;
-      return this;
-   }
-
-   /**
-    * @see #getBootDiskSize()
-    */
-   public GoogleComputeEngineTemplateOptions bootDiskSize(Long bootDiskSize) {
-      this.bootDiskSize = fromNullable(bootDiskSize);
-      return this;
+   /** The network instances will attach to. When absent, a new network will be created for the project. */
+   @Nullable public URI network() {
+      return network;
    }
 
-   /**
-    * @see #shouldKeepBootDisk()
-    */
-   public GoogleComputeEngineTemplateOptions keepBootDisk(boolean keepBootDisk) {
-      this.keepBootDisk = keepBootDisk;
-      return this;
+   /** Additional disks to attach to this instance. */
+   // TODO: test me or remove me!
+   public List<URI> additionalDisks() {
+      return additionalDisks;
    }
 
    /**
@@ -325,57 +249,4 @@ public final class GoogleComputeEngineTemplateOptions extends TemplateOptions {
    public GoogleComputeEngineTemplateOptions blockOnComplete(boolean blockOnComplete) {
       return GoogleComputeEngineTemplateOptions.class.cast(super.blockOnComplete(blockOnComplete));
    }
-
-   /**
-    * @return the ServiceAccounts to enable in the instances.
-    */
-   public List<Instance.ServiceAccount> getServiceAccounts() {
-      return serviceAccounts;
-   }
-
-   /**
-    * @return the PersistentDisks for this instance.
-    */
-   public List<PersistentDisk> getDisks() {
-      return disks;
-   }
-
-   /**
-    * @return the URI of an existing network the instances will be attached to. If no network URI or network name are
-    *         provided a new network will be created for the project.
-    */
-   public Optional<URI> getNetwork() {
-      return network;
-   }
-
-   /**
-    * @return the name of an existing network the instances will be attached to, the network is assumed to belong to
-    *         user's project. If no network URI network name are provided a new network will be created for the project.
-    *         <b>Note that this is now pulling from the first element in the networks field from TemplateOptions.</b>
-    */
-   public Optional<String> getNetworkName() {
-      return fromNullable(Iterables.getFirst(getNetworks(), null));
-   }
-
-   /**
-    * @return whether an AccessConfig with Type ONE_TO_ONE_NAT should be enabled in the instances. When true
-    *         instances will have a NAT address that will be publicly accessible.
-    */
-   public boolean isEnableNat() {
-      return enableNat;
-   }
-
-   /**
-    * @return the boot disk size, if specified. Defaults to 10gb.
-    */
-   public Optional<Long> getBootDiskSize() {
-      return bootDiskSize;
-   }
-
-   /**
-    * @return whether we should keep the boot disk around when deleting the instance. Defaults to false.
-    */
-   public boolean shouldKeepBootDisk() {
-      return keepBootDisk;
-   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/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
index 09d4486..ab7a512 100644
--- 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
@@ -20,16 +20,16 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Inject;
 
-import org.jclouds.googlecomputeengine.compute.functions.ResourceFunctions;
+import org.jclouds.googlecomputeengine.compute.functions.Resources;
 import org.jclouds.googlecomputeengine.domain.Instance;
 
 import com.google.common.base.Predicate;
 
 public final class AtomicInstanceVisible implements Predicate<AtomicReference<Instance>> {
 
-   private final ResourceFunctions resources;
+   private final Resources resources;
 
-   @Inject AtomicInstanceVisible(ResourceFunctions resources) {
+   @Inject AtomicInstanceVisible(Resources resources) {
       this.resources = resources;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/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
index afe0dd0..1f917ae 100644
--- 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
@@ -23,16 +23,16 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Inject;
 
-import org.jclouds.googlecomputeengine.compute.functions.ResourceFunctions;
+import org.jclouds.googlecomputeengine.compute.functions.Resources;
 import org.jclouds.googlecomputeengine.domain.Operation;
 
 import com.google.common.base.Predicate;
 
 public final class AtomicOperationDone implements Predicate<AtomicReference<Operation>> {
 
-   private final ResourceFunctions resources;
+   private final Resources resources;
 
-   @Inject AtomicOperationDone(ResourceFunctions resources) {
+   @Inject AtomicOperationDone(Resources resources) {
       this.resources = resources;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/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 ee2c877..3900c46 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
@@ -19,6 +19,7 @@ package org.jclouds.googlecomputeengine.compute.strategy;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableList.of;
 
+import java.net.URI;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -118,9 +119,7 @@ public final class CreateNodesWithGroupEncodedIntoNameThenAddToSet extends
     * Try and find a network either previously created by jclouds or user defined.
     */
    private Network getOrCreateNetwork(GoogleComputeEngineTemplateOptions templateOptions, String sharedResourceName) {
-
-      String networkName = templateOptions.getNetworkName().or(sharedResourceName);
-
+      String networkName = templateOptions.network() != null ? toName(templateOptions.network()) : sharedResourceName;
       return networkMap.apply(NetworkAndAddressRange.create(networkName, DEFAULT_INTERNAL_NETWORK_RANGE, null));
    }
 
@@ -166,4 +165,9 @@ public final class CreateNodesWithGroupEncodedIntoNameThenAddToSet extends
                operation);
       }
    }
+
+   private static String toName(URI link) {
+      String path = link.getPath();
+      return path.substring(path.lastIndexOf('/') + 1);
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/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 20e26d7..4b14d7a 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
@@ -49,18 +49,12 @@ public final class GoogleComputeEngineHttpApiModule extends HttpApiModule<Google
    public GoogleComputeEngineHttpApiModule() {
    }
 
-   @Override
-   protected void bindErrorHandlers() {
+   @Override protected void bindErrorHandlers() {
       bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(GoogleComputeEngineErrorHandler.class);
       bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(GoogleComputeEngineErrorHandler.class);
       bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(GoogleComputeEngineErrorHandler.class);
    }
 
-   @Override
-   protected void installLocations() {
-      install(new GoogleComputeEngineLocationModule());
-   }
-
    /**
     * Since this is caching a direct api call, we memoize, but short-circuit on any auth exception. This prevents
     * excessive errors when things occur in parallel, or as peers on a function graph.

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineLocationModule.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineLocationModule.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineLocationModule.java
deleted file mode 100644
index 03d0e9d..0000000
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineLocationModule.java
+++ /dev/null
@@ -1,194 +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.config;
-
-import static com.google.common.base.Suppliers.compose;
-import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
-import static org.jclouds.googlecomputeengine.internal.ListPages.concat;
-
-import java.net.URI;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import org.jclouds.collect.Memoized;
-import org.jclouds.domain.Location;
-import org.jclouds.googlecomputeengine.GoogleComputeEngineApi;
-import org.jclouds.googlecomputeengine.domain.Region;
-import org.jclouds.location.config.LocationModule;
-import org.jclouds.location.predicates.LocationPredicates;
-import org.jclouds.location.reference.LocationConstants;
-import org.jclouds.location.suppliers.ImplicitLocationSupplier;
-import org.jclouds.location.suppliers.LocationsSupplier;
-import org.jclouds.location.suppliers.RegionIdToURISupplier;
-import org.jclouds.location.suppliers.RegionIdToZoneIdsSupplier;
-import org.jclouds.location.suppliers.RegionIdsSupplier;
-import org.jclouds.location.suppliers.ZoneIdToURISupplier;
-import org.jclouds.location.suppliers.ZoneIdsSupplier;
-import org.jclouds.location.suppliers.all.ZoneToRegionToProviderOrJustProvider;
-import org.jclouds.location.suppliers.derived.RegionIdsFromRegionIdToURIKeySet;
-import org.jclouds.location.suppliers.derived.ZoneIdsFromRegionIdToZoneIdsValues;
-import org.jclouds.location.suppliers.implicit.FirstZone;
-import org.jclouds.rest.AuthorizationException;
-import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;
-
-import com.google.common.base.Function;
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import com.google.inject.Provides;
-
-/**
- * This configures dynamic locations from {@link org.jclouds.googlecomputeengine.features.RegionApi#list}. As the only
- * assignable location for nodes are zones, this module does not directly expose regions. Rather, they can be found by
- * looking at {@link Location#getParent()} on a zone.
-
- * <p/> This does not yet support constraining region or zone lists via settings {@linkplain
- * LocationConstants#PROPERTY_REGIONS} or {@linkplain LocationConstants#PROPERTY_ZONES}.
- */
-public final class GoogleComputeEngineLocationModule extends LocationModule {
-
-   @Override protected void configure() {
-      super.configure();
-      // Unlike EC2, you cannot default GCE instances to a region. Hence, we constrain to zones.
-      bind(LocationsSupplier.class).to(OnlyZonesLocationSupplier.class);
-      bind(ImplicitLocationSupplier.class).to(FirstZone.class);
-
-      // Region and zones are derived from the same network request to RegionApi.list
-      // Using these suppliers will make that consistent and also cache timeout consistently
-      bind(RegionIdToZoneIdsSupplier.class).to(RegionIdToZoneIdsFromRegionList.class);
-      bind(RegionIdToURISupplier.class).to(RegionIdToURISupplierFromRegionList.class);
-      bind(ZoneIdToURISupplier.class).to(ZoneIdToURIFromRegionList.class);
-      bind(ZoneIdsSupplier.class).to(ZoneIdsFromRegionIdToZoneIdsValues.class);
-      bind(RegionIdsSupplier.class).to(RegionIdsFromRegionIdToURIKeySet.class);
-   }
-
-   /** Retain the metadata tree, including regions, just don't present anything except zones as assignable. */
-   static final class OnlyZonesLocationSupplier implements LocationsSupplier {
-      // This correctly links parents for zone -> region -> provider.
-      private final ZoneToRegionToProviderOrJustProvider delegate;
-
-      @Inject OnlyZonesLocationSupplier(ZoneToRegionToProviderOrJustProvider delegate) {
-         this.delegate = delegate;
-      }
-
-      @Override public Set<? extends Location> get() {
-         return Sets.filter(delegate.get(), LocationPredicates.isZone());
-      }
-   }
-
-   /**
-    * Since this is caching a direct api call, we memoize, but short-circuit on any auth exception. This prevents
-    * excessive errors when things occur in parallel, or as peers on a function graph.
-    */
-   @Provides @Singleton @Memoized Supplier<List<Region>> regions(@UserProject Supplier<String> project,
-         final GoogleComputeEngineApi api, AtomicReference<AuthorizationException> authException,
-         @Named(PROPERTY_SESSION_INTERVAL) long seconds) {
-      return MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier
-            .create(authException, compose(new Function<String, List<Region>>() {
-               public List<Region> apply(String project) {
-                  return ImmutableList.copyOf(concat(api.getRegionApi(project).list()));
-               }
-            }, project), seconds, TimeUnit.SECONDS);
-   }
-
-   @Provides @Singleton @Memoized Supplier<Map<URI, String>> selfLinkToNames(
-         AtomicReference<AuthorizationException> authException, @Memoized Supplier<List<Region>> regions,
-         @Named(PROPERTY_SESSION_INTERVAL) long seconds) {
-      return MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier
-            .create(authException, compose(new Function<List<Region>, Map<URI, String>>() {
-               public Map<URI, String> apply(List<Region> regions) {
-                  ImmutableMap.Builder<URI, String> selfLinkToName = ImmutableMap.builder();
-                  for (Region region : regions) {
-                     selfLinkToName.put(region.selfLink(), region.name());
-                     for (URI zoneSelfLink : region.zones()) {
-                        selfLinkToName.put(zoneSelfLink, toName(zoneSelfLink));
-                     }
-                  }
-                  return selfLinkToName.build();
-               }
-            }, regions), seconds, TimeUnit.SECONDS);
-   }
-
-   static final class RegionIdToZoneIdsFromRegionList implements RegionIdToZoneIdsSupplier {
-      private final Supplier<List<Region>> regions;
-
-      @Inject RegionIdToZoneIdsFromRegionList(@Memoized Supplier<List<Region>> regions) {
-         this.regions = regions;
-      }
-
-      @Override public Map<String, Supplier<Set<String>>> get() {
-         ImmutableMap.Builder<String, Supplier<Set<String>>> result = ImmutableMap.builder();
-         for (org.jclouds.googlecomputeengine.domain.Region region : regions.get()) {
-            ImmutableSet.Builder<String> zoneIds = ImmutableSet.builder();
-            for (URI uri : region.zones()) {
-               zoneIds.add(toName(uri));
-            }
-            result.put(region.name(), Suppliers.<Set<String>>ofInstance(zoneIds.build()));
-         }
-         return result.build();
-      }
-   }
-
-   static final class RegionIdToURISupplierFromRegionList implements RegionIdToURISupplier {
-      private final Supplier<List<Region>> regions;
-
-      @Inject RegionIdToURISupplierFromRegionList(@Memoized Supplier<List<Region>> regions) {
-         this.regions = regions;
-      }
-
-      @Override public Map<String, Supplier<URI>> get() {
-         ImmutableMap.Builder<String, Supplier<URI>> result = ImmutableMap.builder();
-         for (org.jclouds.googlecomputeengine.domain.Region region : regions.get()) {
-            result.put(region.name(), Suppliers.ofInstance(region.selfLink()));
-         }
-         return result.build();
-      }
-   }
-
-   static final class ZoneIdToURIFromRegionList implements ZoneIdToURISupplier {
-      private final Supplier<List<Region>> regions;
-
-      @Inject ZoneIdToURIFromRegionList(@Memoized Supplier<List<Region>> regions) {
-         this.regions = regions;
-      }
-
-      @Override public Map<String, Supplier<URI>> get() {
-         ImmutableMap.Builder<String, Supplier<URI>> result = ImmutableMap.builder();
-         for (Region region : regions.get()) {
-            for (URI input : region.zones()) {
-               result.put(toName(input), Suppliers.ofInstance(input));
-            }
-         }
-         return result.build();
-      }
-   }
-
-   private static String toName(URI link) {
-      String path = link.getPath();
-      return path.substring(path.lastIndexOf('/') + 1);
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineParserModule.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineParserModule.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineParserModule.java
index 815ccf1..892da57 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineParserModule.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/config/GoogleComputeEngineParserModule.java
@@ -19,6 +19,7 @@ package org.jclouds.googlecomputeengine.config;
 import static org.jclouds.googlecomputeengine.domain.Firewall.Rule;
 
 import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.Map;
 import java.util.Set;
@@ -26,12 +27,12 @@ import java.util.Set;
 import javax.inject.Singleton;
 
 import org.jclouds.googlecomputeengine.domain.Firewall;
-import org.jclouds.googlecomputeengine.domain.Metadata;
-import org.jclouds.googlecomputeengine.domain.templates.InstanceTemplate;
+import org.jclouds.googlecomputeengine.domain.ListPage;
 import org.jclouds.googlecomputeengine.options.FirewallOptions;
 import org.jclouds.googlecomputeengine.options.RouteOptions;
 import org.jclouds.json.config.GsonModule;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.gson.Gson;
@@ -45,6 +46,7 @@ import com.google.gson.TypeAdapter;
 import com.google.gson.TypeAdapterFactory;
 import com.google.gson.reflect.TypeToken;
 import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
 import com.google.gson.stream.JsonWriter;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
@@ -57,119 +59,13 @@ public final class GoogleComputeEngineParserModule extends AbstractModule {
 
    @Provides @Singleton Map<Type, Object> typeAdapters() {
       return new ImmutableMap.Builder<Type, Object>()
-            .put(InstanceTemplate.class, new InstanceTemplateTypeAdapter())
             .put(FirewallOptions.class, new FirewallOptionsTypeAdapter())
             .put(RouteOptions.class, new RouteOptionsTypeAdapter()).build();
    }
 
    // TODO: change jclouds core to use collaborative set bindings
    @Provides @Singleton Set<TypeAdapterFactory> typeAdapterFactories() {
-      return ImmutableSet.<TypeAdapterFactory>of(new MetadataTypeAdapter());
-   }
-
-   private static final class InstanceTemplateTypeAdapter implements JsonSerializer<InstanceTemplate> {
-
-      @Override public JsonElement serialize(InstanceTemplate src, Type typeOfSrc, JsonSerializationContext context) {
-         InstanceTemplateInternal template = new InstanceTemplateInternal(src);
-         JsonObject instance = (JsonObject) context.serialize(template, InstanceTemplateInternal.class);
-
-         // deal with network
-         JsonArray networkInterfaces = new JsonArray();
-         for (InstanceTemplate.NetworkInterface networkInterface : template.networkInterfaces()) {
-            networkInterfaces.add(context.serialize(networkInterface, InstanceTemplate.NetworkInterface.class));
-         }
-         instance.add("networkInterfaces", networkInterfaces);
-
-         // deal with persistent disks
-         if (!src.disks().isEmpty()) {
-            instance.add("disks", context.serialize(src.disks()));
-         }
-
-         // deal with metadata
-         if (!src.metadata().isEmpty()) {
-            Metadata metadata = Metadata.create(null, src.metadata());
-            JsonObject metadataJson = (JsonObject) context.serialize(metadata);
-            instance.add("metadata", metadataJson);
-            return instance;
-         }
-
-         return instance;
-      }
-
-      private static final class InstanceTemplateInternal extends InstanceTemplate {
-         private InstanceTemplateInternal(InstanceTemplate template) {
-            machineType(template.machineType());
-            name(template.name());
-            description(template.description());
-            image(template.image());
-            serviceAccounts(template.serviceAccounts());
-            networkInterfaces(template.networkInterfaces());
-         }
-      }
-   }
-
-   private static final class MetadataTypeAdapter extends TypeAdapter<Metadata> implements TypeAdapterFactory {
-
-      @Override public void write(JsonWriter out, Metadata src) throws IOException {
-         out.beginObject();
-         out.name("kind").value("compute#metadata");
-         out.name("items");
-         out.beginArray();
-         for (Map.Entry<String, String> entry : src.items().entrySet()) {
-            out.beginObject();
-            out.name("key").value(entry.getKey());
-            out.name("value").value(entry.getValue());
-            out.endObject();
-         }
-         out.endArray();
-         if (src.fingerprint() != null) {
-            out.name("fingerprint").value(src.fingerprint());
-         }
-         out.endObject();
-      }
-
-      @Override public Metadata read(JsonReader in) throws IOException {
-         String fingerprint = null;
-         ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
-         in.beginObject();
-         while (in.hasNext()) {
-            String name = in.nextName();
-            if (name.equals("items")) {
-               in.beginArray();
-               while (in.hasNext()) {
-                  in.beginObject();
-                  String key = null;
-                  String value = null;
-                  while (in.hasNext()) {
-                     name = in.nextName();
-                     if (name.equals("key")) {
-                        key = in.nextString();
-                     } else if (name.equals("value")) {
-                        value = in.nextString();
-                     } else {
-                        in.skipValue();
-                     }
-                  }
-                  builder.put(key, value);
-                  in.endObject();
-               }
-               in.endArray();
-            } else if (name.equals("fingerprint")) {
-               fingerprint = in.nextString();
-            } else {
-               in.skipValue();
-            }
-         }
-         in.endObject();
-         return Metadata.create(fingerprint, builder.build());
-      }
-
-      @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
-         if (!(Metadata.class.isAssignableFrom(typeToken.getRawType()))) {
-            return null;
-         }
-         return (TypeAdapter<T>) this;
-      }
+      return ImmutableSet.<TypeAdapterFactory>of(new ListPageAdapterFactory());
    }
 
    private static final class FirewallOptionsTypeAdapter implements JsonSerializer<FirewallOptions> {
@@ -247,4 +143,79 @@ public final class GoogleComputeEngineParserModule extends AbstractModule {
       }
       return array;
    }
+
+   static final class ListPageAdapterFactory implements TypeAdapterFactory {
+      static final class ListPageAdapter extends TypeAdapter<ListPage<?>> {
+         private final TypeAdapter<?> itemAdapter;
+
+         ListPageAdapter(TypeAdapter<?> itemAdapter) {
+            this.itemAdapter = itemAdapter;
+            nullSafe();
+         }
+
+         public void write(JsonWriter out, ListPage<?> value) throws IOException {
+            throw new UnsupportedOperationException("We only read ListPages!");
+         }
+
+         public ListPage<?> read(JsonReader in) throws IOException {
+            ImmutableList.Builder<Object> items = ImmutableList.builder();
+            String nextPageToken = null;
+            in.beginObject();
+            while (in.hasNext()) {
+               String name = in.nextName();
+               if (name.equals("items")) {
+                  if (in.peek() == JsonToken.BEGIN_ARRAY) {
+                     readItems(in, items);
+                  } else { // aggregated
+                     readAggregate(in, items);
+                  }
+               } else if (name.equals("nextPageToken")) {
+                  nextPageToken = in.nextString();
+               } else {
+                  in.skipValue();
+               }
+            }
+            in.endObject();
+            return ListPage.create(items.build(), nextPageToken);
+         }
+
+         private void readItems(JsonReader in, ImmutableList.Builder<Object> items) throws IOException {
+            in.beginArray();
+            while (in.hasNext()) {
+               Object item = itemAdapter.read(in);
+               if (item != null) {
+                  items.add(item);
+               }
+            }
+            in.endArray();
+         }
+
+         private void readAggregate(JsonReader in, ImmutableList.Builder<Object> items) throws IOException {
+            in.beginObject(); // enter zone name -> type -> items map
+            while (in.hasNext()) {
+               String scope = in.nextName(); // skip zone name
+               in.beginObject(); // enter zone map
+               while (in.hasNext()) {
+                  String resourceTypeOrWarning = in.nextName();
+                  if (!resourceTypeOrWarning.equals("warning")) {
+                     readItems(in, items);
+                  } else {
+                     in.skipValue();
+                  }
+               }
+               in.endObject(); // end zone map
+            }
+            in.endObject(); // end item wrapper
+         }
+      }
+
+      @SuppressWarnings("unchecked") public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> ownerType) {
+         Type type = ownerType.getType();
+         if (ownerType.getRawType() != ListPage.class || !(type instanceof ParameterizedType))
+            return null;
+         Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
+         TypeAdapter<?> itemAdapter = gson.getAdapter(TypeToken.get(elementType));
+         return (TypeAdapter<T>) new ListPageAdapter(itemAdapter);
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Metadata.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Metadata.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Metadata.java
index 19b55a8..25f39f0 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Metadata.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Metadata.java
@@ -16,27 +16,115 @@
  */
 package org.jclouds.googlecomputeengine.domain;
 
-import static org.jclouds.googlecomputeengine.internal.NullSafeCopies.copyOf;
-
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
 
 import com.google.auto.value.AutoValue;
 
-/** Metadata for an instance or project, with their fingerprint. */
+/**
+ * Metadata for an instance or project, with their fingerprint.
+ * <p/>
+ * This object is mutable and not thread-safe.
+ */
 @AutoValue
-public abstract class Metadata {
+public abstract class Metadata implements Cloneable {
+
+   @AutoValue
+   abstract static class Entry {
+      abstract String key();
+
+      abstract String value();
+
+      @SerializedNames({ "key", "value" })
+      static Entry create(String key, String value) {
+         return new AutoValue_Metadata_Entry(key, value);
+      }
+   }
+
    /** The fingerprint for the items - needed for updating them. */
    @Nullable public abstract String fingerprint();
 
-   public abstract Map<String, String> items();
+   /** Adds or replaces a metadata entry. */
+   public Metadata put(String key, String value) {
+      remove(key);
+      items().add(Entry.create(key, value));
+      return this;
+   }
+
+   /** Adds or replaces metadata entries. */
+   public Metadata putAll(Map<String, String> input) {
+      for (Map.Entry<String, String> entry : input.entrySet()) {
+         put(entry.getKey(), entry.getValue());
+      }
+      return this;
+   }
+
+   /** Removes any entry with the supplied key. */
+   public Metadata remove(String key) {
+      for (int i = 0, length = items().size(); i < length; i++) {
+         if (items().get(i).key().equals(key)) {
+            items().remove(i);
+            return this;
+         }
+      }
+      return this;
+   }
+
+   /** Copies the metadata into a new mutable map. */
+   public Map<String, String> asMap() {
+      Map<String, String> result = new LinkedHashMap<String, String>();
+      ArrayList<Entry> items = items();
+      for (int i = 0, length = items.size(); i < length; i++) {
+         Entry item = items.get(i);
+         result.put(item.key(), item.value());
+      }
+      return result;
+   }
 
-   // No SerializedNames as custom-parsed.
-   public static Metadata create(String fingerprint, Map<String, String> items) {
-      return new AutoValue_Metadata(fingerprint, copyOf(items));
+   /** Returns the value with the supplied key, or null. */
+   @Nullable public String get(String key) {
+      ArrayList<Entry> items = items();
+      for (int i = 0, length = items.size(); i < length; i++) {
+         Entry item = items.get(i);
+         if (item.key().equals(key)) {
+            return item.value();
+         }
+      }
+      return null;
+   }
+
+   public boolean containsKey(String key) {
+      return get(key) != null;
+   }
+
+   public int size() {
+      return items().size();
+   }
+
+   /** Mutable list of metadata. */
+   abstract ArrayList<Entry> items();
+
+   public static Metadata create() {
+      return Metadata.create(null, null);
+   }
+
+   public static Metadata create(String fingerprint) {
+      return Metadata.create(fingerprint, null);
+   }
+
+   @SerializedNames({ "fingerprint", "items" })
+   static Metadata create(String fingerprint, ArrayList<Entry> items) { // Dictates the type when created from json!
+      return new AutoValue_Metadata(fingerprint, items != null ? items : new ArrayList<Entry>());
    }
 
    Metadata() {
    }
+
+   @Override public Metadata clone() {
+      return Metadata.create(fingerprint(), new ArrayList<Entry>(items()));
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/NewInstance.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/NewInstance.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/NewInstance.java
new file mode 100644
index 0000000..73d3c4b
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/NewInstance.java
@@ -0,0 +1,157 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.googlecomputeengine.domain.Instance.AttachedDisk.Type.PERSISTENT;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import org.jclouds.googlecomputeengine.domain.Instance.AttachedDisk;
+import org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+/** Parameter to {@linkplain org.jclouds.googlecomputeengine.features.InstanceApi#create(NewInstance)}. */
+@AutoValue
+public abstract class NewInstance {
+   @AutoValue
+   abstract static class NetworkInterface {
+      abstract URI network();
+
+      abstract List<AccessConfig.Type> accessConfigs();
+
+      static NetworkInterface create(URI network) {
+         return create(network, Arrays.asList(AccessConfig.Type.ONE_TO_ONE_NAT));
+      }
+
+      @SerializedNames({ "network", "accessConfigs" })
+      static NetworkInterface create(URI network, List<AccessConfig.Type> accessConfigs) {
+         return new AutoValue_NewInstance_NetworkInterface(network, accessConfigs);
+      }
+
+      NetworkInterface() {
+      }
+   }
+
+   @AutoValue
+   public abstract static class Disk {
+      @AutoValue
+      abstract static class InitializeParams {
+         /** Override the default naming convention. */
+         @Nullable public abstract String diskName();
+
+         /** Set to use a size larger than the {@link #sourceImage()}. You need to repartition when set. */
+         @Nullable public abstract Long diskSizeGb();
+
+         /** The {@link org.jclouds.googlecomputeengine.domain.Image#selfLink() source image}. */
+         public abstract URI sourceImage();
+
+         static InitializeParams create(URI sourceImage) {
+            return create(null, null, sourceImage);
+         }
+
+         @SerializedNames({ "diskName", "diskSizeGb", "sourceImage" })
+         static InitializeParams create(String diskName, Long diskSizeGb, URI sourceImage) {
+            return new AutoValue_NewInstance_Disk_InitializeParams(diskName, diskSizeGb, sourceImage);
+         }
+
+         InitializeParams() {
+         }
+      }
+
+      public abstract AttachedDisk.Type type();
+
+      /** Use an existingBootDisk {@link org.jclouds.googlecomputeengine.domain.Disk#selfLink() boot disk}. */
+      @Nullable public abstract URI source();
+
+      /** Set to automatically create a boot disk */
+      @Nullable public abstract InitializeParams initializeParams();
+
+      public abstract boolean boot();
+
+      public abstract boolean autoDelete();
+
+      public static Disk existingBootDisk(URI existingBootDisk) {
+         return create(PERSISTENT, existingBootDisk, null, true, false);
+      }
+
+      public static Disk newBootDisk(URI sourceImage) {
+         return create(PERSISTENT, null, InitializeParams.create(sourceImage), true, true);
+      }
+
+      public static Disk existingDisk(URI existingDisk) {
+         return create(PERSISTENT, existingDisk, null, false, false);
+      }
+
+      @SerializedNames({ "type", "source", "initializeParams", "boot", "autoDelete" })
+      static Disk create(AttachedDisk.Type type, URI source, InitializeParams initializeParams, boolean boot,
+            boolean autoDelete) {
+         return new AutoValue_NewInstance_Disk(type, source, initializeParams, boot, autoDelete);
+      }
+
+      Disk() {
+      }
+   }
+
+   public abstract URI machineType();
+
+   public abstract String name();
+
+   public abstract List<NetworkInterface> networkInterfaces();
+
+   public abstract List<Disk> disks();
+
+   @Nullable public abstract String description();
+
+   public abstract Tags tags();
+
+   /** Add metadata via {@link Metadata#items()}. */
+   public abstract Metadata metadata();
+
+   public static NewInstance create(URI machineType, String name, URI network, Disk bootDisk, String description) {
+      return create(machineType, name, network, Arrays.asList(checkNotNull(bootDisk, "bootDisk")), description);
+   }
+
+   public static NewInstance create(URI machineType, String name, URI network, List<Disk> disks, String description) {
+      checkArgument(disks.get(0).boot(), "disk 0 must be a boot disk! %s", disks);
+      boolean foundBoot = false;
+      for (Disk disk : disks) {
+         if (disk.boot()) {
+            checkArgument(!foundBoot, "There must be only one boot disk! %s", disks);
+            foundBoot = true;
+         }
+      }
+      return create(machineType, name, ImmutableList.of(NetworkInterface.create(network)), ImmutableList.copyOf(disks),
+            description, Tags.create(), Metadata.create());
+   }
+
+   @SerializedNames({ "machineType", "name", "networkInterfaces", "disks", "description", "tags", "metadata" })
+   static NewInstance create(URI machineType, String name, List<NetworkInterface> networkInterfaces, List<Disk> disks,
+         String description, Tags tags, Metadata metadata) {
+      return new AutoValue_NewInstance(machineType, name, networkInterfaces, disks, description, tags, metadata);
+   }
+
+   NewInstance() {
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/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 cc1c880..23c7228 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
@@ -16,8 +16,7 @@
  */
 package org.jclouds.googlecomputeengine.domain;
 
-import static org.jclouds.googlecomputeengine.internal.NullSafeCopies.copyOf;
-
+import java.util.ArrayList;
 import java.util.List;
 
 import org.jclouds.javax.annotation.Nullable;
@@ -25,19 +24,43 @@ import org.jclouds.json.SerializedNames;
 
 import com.google.auto.value.AutoValue;
 
-/** Each tag must be unique, must be 1-63 characters long, and comply with RFC1035. */
+/**
+ * Tags for an instance or project, with their fingerprint. Each tag must be unique, must be 1-63 characters long, and
+ * comply with RFC1035.
+ * <p/>
+ * This object is mutable and not thread-safe.
+ */
 @AutoValue
-public abstract class Tags {
+public abstract class Tags implements Cloneable {
    /** The fingerprint for the items - needed for updating them. */
    @Nullable public abstract String fingerprint();
 
+   /** Mutable list of tags. */
    public abstract List<String> items();
 
+   /** Convenience method for chaining adds. */
+   public Tags add(String tag) {
+      items().add(tag);
+      return this;
+   }
+
+   public static Tags create() {
+      return Tags.create(null, null);
+   }
+
+   public static Tags create(String fingerprint) {
+      return Tags.create(fingerprint, null);
+   }
+
    @SerializedNames({ "fingerprint", "items" })
-   public static Tags create(String fingerprint, List<String> items) {
-      return new AutoValue_Tags(fingerprint, copyOf(items));
+   static Tags create(String fingerprint, ArrayList<String> items) { // Dictates the type when created from json!
+      return new AutoValue_Tags(fingerprint, items != null ? items : new ArrayList<String>());
    }
 
    Tags() {
    }
+
+   @Override public Tags clone() {
+      return Tags.create(fingerprint(), new ArrayList<String>(items()));
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/templates/InstanceTemplate.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/templates/InstanceTemplate.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/templates/InstanceTemplate.java
deleted file mode 100644
index c544453..0000000
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/templates/InstanceTemplate.java
+++ /dev/null
@@ -1,258 +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.domain.templates;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.jclouds.googlecomputeengine.domain.Instance.AttachedDisk;
-import static org.jclouds.googlecomputeengine.domain.Instance.AttachedDisk.Mode;
-
-import java.net.URI;
-import java.util.List;
-import java.util.Map;
-
-import org.jclouds.googlecomputeengine.domain.Image;
-import org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig;
-import org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig.Type;
-import org.jclouds.googlecomputeengine.domain.Instance.ServiceAccount;
-import org.jclouds.javax.annotation.Nullable;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-/** Optional information for creating an instance. */
-// TODO! this is dangerously similarly named to the InstanceTemplate resource!
-public class InstanceTemplate {
-
-   public static final class PersistentDisk {
-
-      private final AttachedDisk.Type type = AttachedDisk.Type.PERSISTENT;
-      private final AttachedDisk.Mode mode;
-      private final URI source;
-      private final String deviceName;
-      private final boolean autoDelete;
-      private final boolean boot;
-
-      public PersistentDisk(AttachedDisk.Mode mode, URI source, String deviceName, boolean autoDelete, boolean boot) {
-         this.mode = checkNotNull(mode, "mode");
-         this.source = checkNotNull(source, "source");
-         this.deviceName = deviceName;
-         this.autoDelete = autoDelete;
-         this.boot = boot;
-      }
-
-      public AttachedDisk.Mode mode() {
-         return mode;
-      }
-
-      public URI source() {
-         return source;
-      }
-
-      @Nullable public String deviceName() {
-         return deviceName;
-      }
-
-      public boolean autoDelete() {
-         return autoDelete;
-      }
-
-      public boolean boot() {
-         return boot;
-      }
-   }
-
-   public static final class NetworkInterface {
-
-      private final URI network;
-      private final String networkIP;
-      private final List<AccessConfig> accessConfigs;
-
-      public NetworkInterface(URI network, String networkIP, List<AccessConfig> accessConfigs) {
-         this.network = network;
-         this.networkIP = networkIP;
-         this.accessConfigs = accessConfigs;
-      }
-
-      public URI network() {
-         return network;
-      }
-
-      @Nullable public String networkIP() {
-         return networkIP;
-      }
-
-      @Nullable public List<AccessConfig> accessConfigs() {
-         return accessConfigs;
-      }
-   }
-
-   private String name;
-   private String description;
-   private URI machineType;
-   private URI image;
-   private List<ServiceAccount> serviceAccounts = Lists.newArrayList();
-   private List<PersistentDisk> disks = Lists.newArrayList();
-   private List<NetworkInterface> networkInterfaces = Lists.newArrayList();
-   private Map<String, String> metadata = Maps.newLinkedHashMap();
-
-   /**
-    * @see org.jclouds.googlecomputeengine.domain.Instance#name()
-    */
-   public String name() {
-      return name;
-   }
-
-   public InstanceTemplate name(String name) {
-      this.name = name;
-      return this;
-   }
-
-   /**
-    * @see org.jclouds.googlecomputeengine.domain.Instance#description()
-    */
-   public String description() {
-      return description;
-   }
-
-   public InstanceTemplate description(String description) {
-      this.description = description;
-      return this;
-   }
-
-   /**
-    * @see Image#selfLink()
-    */
-   public URI image() {
-      return image;
-   }
-
-   public InstanceTemplate image(URI image) {
-      this.image = image;
-      return this;
-   }
-
-   /**
-    * @see org.jclouds.googlecomputeengine.domain.Instance#machineType()
-    */
-   public URI machineType() {
-      return machineType;
-   }
-
-   public InstanceTemplate machineType(URI machineType) {
-      this.machineType = machineType;
-      return this;
-   }
-
-   /**
-    * @see org.jclouds.googlecomputeengine.domain.Instance#disks()
-    */
-   public List<PersistentDisk> disks() {
-      return disks;
-   }
-
-   public InstanceTemplate addDisk(Mode mode, URI source) {
-      this.disks.add(new PersistentDisk(mode, source, null, false, false));
-      return this;
-   }
-
-   public InstanceTemplate addDisk(Mode mode, URI source, boolean autoDelete) {
-      this.disks.add(new PersistentDisk(mode, source, null, autoDelete, false));
-      return this;
-   }
-
-   public InstanceTemplate addDisk(Mode mode, URI source, String deviceName, boolean autoDelete) {
-      this.disks.add(new PersistentDisk(mode, source, deviceName, autoDelete, false));
-      return this;
-   }
-
-   public InstanceTemplate addDisk(Mode mode, URI source, String deviceName, boolean autoDelete, boolean boot) {
-      this.disks.add(new PersistentDisk(mode, source, deviceName, autoDelete, boot));
-      return this;
-   }
-
-   public InstanceTemplate disks(List<PersistentDisk> disks) {
-      this.disks = Lists.newArrayList();
-      this.disks.addAll(checkNotNull(disks, "disks"));
-      return this;
-   }
-
-   /**
-    * @see org.jclouds.googlecomputeengine.domain.Instance#networkInterfaces()
-    */
-   public List<NetworkInterface> networkInterfaces() {
-      return networkInterfaces;
-   }
-
-   public InstanceTemplate addNetworkInterface(URI network) {
-      this.networkInterfaces.add(new NetworkInterface(network, null, null));
-      return this;
-   }
-
-   public InstanceTemplate addNetworkInterface(URI network, Type type) {
-      this.networkInterfaces
-            .add(new NetworkInterface(network, null, ImmutableList.of(AccessConfig.create(null, type, null))));
-      return this;
-   }
-
-   public InstanceTemplate addNetworkInterface(NetworkInterface networkInterface) {
-      this.networkInterfaces.add(networkInterface);
-      return this;
-   }
-
-   public InstanceTemplate networkInterfaces(List<NetworkInterface> networkInterfaces) {
-      this.networkInterfaces = Lists.newArrayList(networkInterfaces);
-      return this;
-   }
-
-   /**
-    * @see org.jclouds.googlecomputeengine.domain.Instance#metadata()
-    */
-   public Map<String, String> metadata() {
-      return metadata;
-   }
-
-   public InstanceTemplate addMetadata(String key, String value) {
-      this.metadata.put(checkNotNull(key, "key"), checkNotNull(value, "value of %", key));
-      return this;
-   }
-
-   public InstanceTemplate metadata(Map<String, String> metadata) {
-      this.metadata = Maps.newLinkedHashMap();
-      this.metadata.putAll(checkNotNull(metadata, "metadata"));
-      return this;
-   }
-
-   /**
-    * @see org.jclouds.googlecomputeengine.domain.Instance#serviceAccounts()
-    */
-   public List<ServiceAccount> serviceAccounts() {
-      return serviceAccounts;
-   }
-
-   public InstanceTemplate addServiceAccount(ServiceAccount serviceAccount) {
-      this.serviceAccounts.add(checkNotNull(serviceAccount, "serviceAccount"));
-      return this;
-   }
-
-   public InstanceTemplate serviceAccounts(List<ServiceAccount> serviceAccounts) {
-      this.serviceAccounts = Lists.newArrayList();
-      this.serviceAccounts.addAll(checkNotNull(serviceAccounts, "serviceAccounts"));
-      return this;
-   }
-
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AggregatedListApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AggregatedListApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AggregatedListApi.java
new file mode 100644
index 0000000..37a778a
--- /dev/null
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/AggregatedListApi.java
@@ -0,0 +1,177 @@
+/*
+ * 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.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_READONLY_SCOPE;
+
+import java.util.Iterator;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+
+import org.jclouds.googlecomputeengine.GoogleComputeEngineApi;
+import org.jclouds.googlecomputeengine.domain.Instance;
+import org.jclouds.googlecomputeengine.domain.ListPage;
+import org.jclouds.googlecomputeengine.domain.MachineType;
+import org.jclouds.googlecomputeengine.functions.internal.BaseToIteratorOfListPage;
+import org.jclouds.googlecomputeengine.options.ListOptions;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.Json;
+import org.jclouds.oauth.v2.config.OAuthScopes;
+import org.jclouds.oauth.v2.filters.OAuthAuthenticationFilter;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SkipEncoding;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.inject.TypeLiteral;
+
+@SkipEncoding({'/', '='})
+@RequestFilters(OAuthAuthenticationFilter.class)
+@Path("/aggregated")
+@Consumes(APPLICATION_JSON)
+public interface AggregatedListApi {
+
+   /**
+    * Retrieves the list of machine type 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("MachineTypes:aggregatedList")
+   @GET
+   @Path("/machineTypes")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   ListPage<MachineType> pageOfMachineTypes(@Nullable @QueryParam("pageToken") String token, ListOptions listOptions);
+
+   /**
+    * @see #pageOfMachineTypes(String, ListOptions)
+    */
+   @Named("MachineTypes:aggregatedList")
+   @GET
+   @Path("/machineTypes")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(MachineTypePage.class)
+   @Transform(MachineTypePages.class)
+   Iterator<ListPage<MachineType>> machineTypes();
+
+   /**
+    * @see #pageOfMachineTypes(String, ListOptions)
+    */
+   @Named("MachineTypes:aggregatedList")
+   @GET
+   @Path("/machineTypes")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(MachineTypePage.class)
+   @Transform(MachineTypePages.class)
+   Iterator<ListPage<MachineType>> machineTypes(ListOptions options);
+
+   static final class MachineTypePage extends ParseJson<ListPage<MachineType>> {
+      @Inject MachineTypePage(Json json) {
+         super(json, new TypeLiteral<ListPage<MachineType>>() {
+         });
+      }
+   }
+
+   static final class MachineTypePages extends BaseToIteratorOfListPage<MachineType, MachineTypePages> {
+      private final GoogleComputeEngineApi api;
+
+      @Inject MachineTypePages(GoogleComputeEngineApi api) {
+         this.api = api;
+      }
+
+      @Override
+      protected Function<String, ListPage<MachineType>> fetchNextPage(final String project, final ListOptions options) {
+         return new Function<String, ListPage<MachineType>>() {
+            @Override public ListPage<MachineType> apply(String input) {
+               return api.aggregatedList(project).pageOfMachineTypes(input, options);
+            }
+         };
+      }
+   }
+
+   /**
+    * Retrieves the list of instance 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("Instances:aggregatedList")
+   @GET
+   @Path("/instances")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   ListPage<Instance> pageOfInstances(@Nullable @QueryParam("pageToken") String token, ListOptions listOptions);
+
+   /**
+    * @see #pageOfInstances(String, ListOptions)
+    */
+   @Named("Instances:aggregatedList")
+   @GET
+   @Path("/instances")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(InstancePage.class)
+   @Transform(InstancePages.class)
+   Iterator<ListPage<Instance>> instances();
+
+   /**
+    * @see #pageOfInstances(String, ListOptions)
+    */
+   @Named("Instances:aggregatedList")
+   @GET
+   @Path("/instances")
+   @OAuthScopes(COMPUTE_READONLY_SCOPE)
+   @ResponseParser(InstancePage.class)
+   @Transform(InstancePages.class)
+   Iterator<ListPage<Instance>> instances(ListOptions options);
+
+   static final class InstancePage extends ParseJson<ListPage<Instance>> {
+      @Inject InstancePage(Json json) {
+         super(json, new TypeLiteral<ListPage<Instance>>() {
+         });
+      }
+   }
+
+   static final class InstancePages extends BaseToIteratorOfListPage<Instance, InstancePages> {
+      private final GoogleComputeEngineApi api;
+
+      @Inject InstancePages(GoogleComputeEngineApi api) {
+         this.api = api;
+      }
+
+      @Override
+      protected Function<String, ListPage<Instance>> fetchNextPage(final String project, final ListOptions options) {
+         return new Function<String, ListPage<Instance>>() {
+            @Override public ListPage<Instance> apply(String input) {
+               return api.aggregatedList(project).pageOfInstances(input, options);
+            }
+         };
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/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 17ec3e8..5c107fa 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
@@ -23,7 +23,6 @@ import static org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.A
 import static org.jclouds.googlecomputeengine.domain.Instance.SerialPortOutput;
 
 import java.util.Iterator;
-import java.util.Map;
 
 import javax.inject.Named;
 import javax.ws.rs.Consumes;
@@ -38,11 +37,11 @@ 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.binders.MetadataBinder;
 import org.jclouds.googlecomputeengine.domain.Instance;
 import org.jclouds.googlecomputeengine.domain.ListPage;
+import org.jclouds.googlecomputeengine.domain.Metadata;
+import org.jclouds.googlecomputeengine.domain.NewInstance;
 import org.jclouds.googlecomputeengine.domain.Operation;
-import org.jclouds.googlecomputeengine.domain.templates.InstanceTemplate;
 import org.jclouds.googlecomputeengine.functions.internal.ParseInstances;
 import org.jclouds.googlecomputeengine.options.AttachDiskOptions;
 import org.jclouds.googlecomputeengine.options.ListOptions;
@@ -85,7 +84,7 @@ public interface InstanceApi {
    @POST
    @Produces(APPLICATION_JSON)
    @OAuthScopes(COMPUTE_SCOPE)
-   Operation create(@BinderParam(BindToJsonPayload.class) InstanceTemplate template);
+   Operation create(@BinderParam(BindToJsonPayload.class) NewInstance template);
 
    /** Deletes an instance by name and returns the operation in progress, or null if not found. */
    @Named("Instances:delete")
@@ -231,17 +230,15 @@ public interface InstanceApi {
     * Sets metadata for an instance using the data included in the request.
     * <p/>
     * NOTE: This *sets* metadata items on the project (vs *adding* items to metadata),
-    * if there are pre-existing metadata items that must be kept these must be fetched first and then re-set on the
-    * new Metadata, e.g.
+    * if there are existing metadata that must be kept these must be fetched first and then re-sent on update.
     * <pre><tt>
-    *    Metadata.Builder current = instanceApi.get("us-central1-a", "myInstance").getMetadata().toBuilder();
-    *    current.addItem("newItem","newItemValue");
-    *    instanceApi.setMetadata("us-central1-a", "myInstance", current.build());
+    *    Metadata update = instanceApi.get("myInstance").metadata().clone();
+    *    update.put("newItem","newItemValue");
+    *    instanceApi.setMetadata("myInstance", update);
     * </tt></pre>
     *
     * @param instance The name of the instance
     * @param metadata the metadata to set
-    * @param fingerprint The current fingerprint for the items
     *
     * @return an Operations resource. To check on the status of an operation, poll the Operations resource returned
     *         to you, and look for the status field.
@@ -251,10 +248,8 @@ public interface InstanceApi {
    @Path("/{instance}/setMetadata")
    @OAuthScopes(COMPUTE_SCOPE)
    @Produces(APPLICATION_JSON)
-   @MapBinder(MetadataBinder.class)
    Operation setMetadata(@PathParam("instance") String instance,
-                         @PayloadParam("items") Map<String, String> metadata,
-                         @PayloadParam("fingerprint") String fingerprint);
+                         @BinderParam(BindToJsonPayload.class) Metadata metadata);
 
    /**
     * Lists items for an instance

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/ProjectApi.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/ProjectApi.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/ProjectApi.java
index 2a9c1a1..cc10657 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/ProjectApi.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/features/ProjectApi.java
@@ -20,8 +20,6 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_READONLY_SCOPE;
 import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.COMPUTE_SCOPE;
 
-import java.util.Map;
-
 import javax.inject.Named;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -31,16 +29,16 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 
 import org.jclouds.googlecomputeengine.GoogleComputeEngineFallbacks.NullOn400or404;
-import org.jclouds.googlecomputeengine.binders.MetadataBinder;
+import org.jclouds.googlecomputeengine.domain.Metadata;
 import org.jclouds.googlecomputeengine.domain.Operation;
 import org.jclouds.googlecomputeengine.domain.Project;
 import org.jclouds.oauth.v2.config.OAuthScopes;
 import org.jclouds.oauth.v2.filters.OAuthAuthenticationFilter;
+import org.jclouds.rest.annotations.BinderParam;
 import org.jclouds.rest.annotations.Fallback;
-import org.jclouds.rest.annotations.MapBinder;
-import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.SkipEncoding;
+import org.jclouds.rest.binders.BindToJsonPayload;
 
 @SkipEncoding({'/', '='})
 @RequestFilters(OAuthAuthenticationFilter.class)
@@ -60,17 +58,15 @@ public interface ProjectApi {
     * Sets metadata common to all instances within the specified project using the data included in the request.
     * <p/>
     * NOTE: This *sets* metadata items on the project (vs *adding* items to metadata),
-    * if there are pre-existing metadata items that must be kept these must be fetched first and then re-set on the
-    * new Metadata, e.g.
+    * if there are existing metadata that must be kept these must be fetched first and then re-sent on update.
     * <pre><tt>
-    *    Metadata.Builder current = projectApi.get("myProject").getCommonInstanceMetadata().toBuilder();
-    *    current.addItem("newItem","newItemValue");
-    *    projectApi.setCommonInstanceMetadata(current.build());
+    *    Metadata update = projectApi.get("myProject").getCommonInstanceMetadata().clone();
+    *    update.put("newItem","newItemValue");
+    *    projectApi.setCommonInstanceMetadata("myProject", update);
     * </tt></pre>
     *
-    * @param projectName            name of the project to return
-    * @param metadata the metadata to set
-    * @param fingerprint  The current fingerprint for the metadata
+    * @param projectName   name of the project to return
+    * @param metadata      the metadata to set
     * @return an Operations resource. To check on the status of an operation, poll the Operations resource returned
     *         to you, and look for the status field.
     */
@@ -79,8 +75,6 @@ public interface ProjectApi {
    @Path("/{project}/setCommonInstanceMetadata")
    @OAuthScopes(COMPUTE_SCOPE)
    @Produces(APPLICATION_JSON)
-   @MapBinder(MetadataBinder.class)
    Operation setCommonInstanceMetadata(@PathParam("project") String projectName,
-                                       @PayloadParam("items") Map<String, String> metadata,
-                                       @PayloadParam("fingerprint") String fingerprint);
+                                       @BinderParam(BindToJsonPayload.class) Metadata metadata);
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/functions/internal/BaseToIteratorOfListPage.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/functions/internal/BaseToIteratorOfListPage.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/functions/internal/BaseToIteratorOfListPage.java
index 66b9d75..ec07bb5 100644
--- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/functions/internal/BaseToIteratorOfListPage.java
+++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/functions/internal/BaseToIteratorOfListPage.java
@@ -20,7 +20,6 @@ import static com.google.common.base.Predicates.instanceOf;
 import static com.google.common.collect.Iterables.tryFind;
 
 import java.util.Iterator;
-import java.util.List;
 
 import org.jclouds.googlecomputeengine.domain.ListPage;
 import org.jclouds.googlecomputeengine.options.ListOptions;
@@ -34,7 +33,7 @@ import com.google.common.base.Optional;
 import com.google.common.collect.Iterators;
 
 @Beta
-abstract class BaseToIteratorOfListPage<T, I extends BaseToIteratorOfListPage<T, I>>
+public abstract class BaseToIteratorOfListPage<T, I extends BaseToIteratorOfListPage<T, I>>
       implements Function<ListPage<T>, Iterator<ListPage<T>>>, InvocationContext<I> {
 
    private GeneratedHttpRequest request;
@@ -42,18 +41,13 @@ abstract class BaseToIteratorOfListPage<T, I extends BaseToIteratorOfListPage<T,
    @Override
    public Iterator<ListPage<T>> apply(ListPage<T> input) {
       if (input.nextPageToken() == null) {
-         return Iterators.singletonIterator(input);
+         return input.isEmpty() ? Iterators.<ListPage<T>>emptyIterator() : Iterators.singletonIterator(input);
       }
 
-      List<Object> callerArgs = request.getCaller().get().getArgs();
-
-      assert callerArgs.size() == 1 : String.format("programming error, method %s should have 1 arg: project",
-            request.getCaller().get().getInvokable());
-
       Optional<Object> listOptions = tryFind(request.getInvocation().getArgs(), instanceOf(ListOptions.class));
 
       return new AdvancingIterator<T>(input,
-            fetchNextPage((String) callerArgs.get(0), (ListOptions) listOptions.orNull()));
+            fetchNextPage((String) request.getCaller().get().getArgs().get(0), (ListOptions) listOptions.orNull()));
    }
 
    protected abstract Function<String, ListPage<T>> fetchNextPage(String projectName, ListOptions listOptions);

http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/6b5643c9/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/binders/ForwardingRuleCreationBinderTest.java
----------------------------------------------------------------------
diff --git a/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/binders/ForwardingRuleCreationBinderTest.java b/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/binders/ForwardingRuleCreationBinderTest.java
index 3a480d8..d45a6c3 100644
--- a/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/binders/ForwardingRuleCreationBinderTest.java
+++ b/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/binders/ForwardingRuleCreationBinderTest.java
@@ -38,8 +38,7 @@ public class ForwardingRuleCreationBinderTest extends BaseGoogleComputeEngineExp
    private static String DESCRIPTION = "This is a test!";
    private static String IP_ADDRESS = "1.2.1.1.1";
    private static String PORT_RANGE = "1.2.3.4.1";
-   private static URI TARGET = URI.create("https://www.googleapis.com/compute/v1/projects/myproject/regions/"
-                                        + "europe-west1/targetPools/test-target-pool");
+   private static URI TARGET = URI.create(BASE_URL + "/party/regions/europe-west1/targetPools/test-target-pool");
 
    Json json = new GsonWrapper(new Gson());