From 9de090d2807ecf13def5460f42c66aa1c45f73ec Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Fri, 21 Jun 2013 16:36:25 -0700
Subject: [PATCH 01/14] Initial commit of support for Google Compute Engine.
 The unit tests are still in development and will be coming in a separate
 commit.

---
 demos/gce_demo.py                    |  236 +++++
 demos/secrets.py-dist                |    4 +
 libcloud/common/google.py            |  466 +++++++++
 libcloud/compute/drivers/__init__.py |    1 +
 libcloud/compute/drivers/gce.py      | 1854 ++++++++++++++++++++++++++++++++++
 libcloud/compute/providers.py        |    2 +
 libcloud/compute/types.py            |    2 +
 7 files changed, 2565 insertions(+)
 create mode 100755 demos/gce_demo.py
 create mode 100644 libcloud/common/google.py
 create mode 100644 libcloud/compute/drivers/gce.py

diff --git a/demos/gce_demo.py b/demos/gce_demo.py
new file mode 100755
index 0000000..c4fdb8e
--- /dev/null
+++ b/demos/gce_demo.py
@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+# 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.
+
+
+# This example performs several tasks on Google Compute Engine.  It should be
+# run directly. This can also serve as an integration test for the GCE
+# Node Driver.
+
+import os.path
+import sys
+
+try:
+    import secrets
+except ImportError:
+    secrets = None
+
+# Add parent dir of this file's dir to sys.path (OS-agnostically)
+sys.path.append(os.path.normpath(os.path.join(os.path.dirname(__file__),
+                                 os.path.pardir)))
+
+from libcloud.compute.types import Provider
+from libcloud.compute.providers import get_driver
+
+# Maximum number of 1-CPU nodes to allow to run simultaneously
+MAX_NODES = 5
+
+# String that all resource names created by the demo will start with
+# WARNING: Any resource that has a matching name will be destroyed.
+DEMO_BASE_NAME = 'libcloud-demo'
+
+# Datacenter to create resources in
+DATACENTER = 'us-central1-a'
+
+# Clean up resources at the end (can be set to false in order to
+# inspect resources at the end of the run). Resources will be cleaned
+# at the beginning regardless.
+CLEANUP = True
+
+args = getattr(secrets, 'GCE_PARAMS', ())
+kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
+
+gce = get_driver(Provider.GCE)(*args,
+                               datacenter=DATACENTER,
+                               **kwargs)
+
+
+# ==== HELPER FUNCTIONS ====
+def display(title, resource_list):
+    """
+    Display a list of resources.
+
+    @param  title: String to be printed at the heading of the list.
+    @type   title: C{str}
+
+    @param  resource_list: List of resources to display
+    @type   resource_list: Any C{object} with a C{name} attribute
+    """
+    print('%s:' % title)
+    for item in resource_list[:10]:
+        print('   %s' % item.name)
+
+
+def clean_up(base_name, node_list=None, resource_list=None):
+    """
+    Destroy all resources that have a name beginning with 'base_name'.
+
+    @param  base_name: String with the first part of the name of resources
+                       to destroy
+    @type   base_name: C{str}
+
+    @keyword  node_list: List of nodes to consider for deletion
+    @type     node_list: C{list} of L{Node}
+
+    @keyword  resource_list: List of resources to consider for deletion
+    @type     resource_list: C{list} of I{Resource Objects}
+    """
+    if node_list is None:
+        node_list = []
+    if resource_list is None:
+        resource_list = []
+    # Use ex_destroy_multiple_nodes to destroy nodes
+    del_nodes = []
+    for node in node_list:
+        if node.name.startswith(base_name):
+            del_nodes.append(node)
+
+    result = gce.ex_destroy_multiple_nodes(del_nodes)
+    for i, success in enumerate(result):
+        if success:
+            print('   Deleted %s' % del_nodes[i].name)
+        else:
+            print('   Failed to delete %s' % del_nodes[i].name)
+
+    # Destroy everything else with just the destroy method
+    for resource in resource_list:
+        if resource.name.startswith(base_name):
+            if resource.destroy():
+                print('   Deleted %s' % resource.name)
+            else:
+                print('   Failed to Delete %s' % resource.name)
+
+# ==== DEMO CODE STARTS HERE ====
+
+# Get project info and print name
+project = gce.ex_get_project()
+print('Project: %s' % project.name)
+
+# == Get Lists of Everything and Display the lists (up to 10) ==
+# These can either just return values for the current datacenter (zone)
+# or for everything.
+all_nodes = gce.list_nodes(ex_zone='all')
+display('Nodes', all_nodes)
+
+all_addresses = gce.ex_list_addresses(region='all')
+display('Addresses', all_addresses)
+
+all_volumes = gce.ex_list_volumes(ex_zone='all')
+display('Volumes', all_volumes)
+
+# This can return everything, but there is a large amount of overlap,
+# so we'll just get the sizes from the current zone.
+sizes = gce.list_sizes()
+display('Sizes', sizes)
+
+# These are global
+firewalls = gce.ex_list_firewalls()
+display('Firewalls', firewalls)
+
+networks = gce.ex_list_networks()
+display('Networks', networks)
+
+images = gce.list_images()
+display('Images', images)
+
+locations = gce.list_locations()
+display('Locations', locations)
+
+zones = gce.ex_list_zones()
+display('Zones', zones)
+
+# == Clean up any old demo resources ==
+print('Cleaning up any "%s" resources:' % DEMO_BASE_NAME)
+clean_up(DEMO_BASE_NAME, all_nodes,
+         all_addresses + all_volumes + firewalls + networks)
+
+# == Create Node with non-persistent disk ==
+if MAX_NODES > 1:
+    print('Creating Node with non-persistent disk:')
+    name = '%s-np-node' % DEMO_BASE_NAME
+    node_1 = gce.create_node(name, 'n1-standard-1', 'debian-7',
+                             ex_tags=['libcloud'])
+    print('   Node %s created' % name)
+
+# == Create Node with persistent disk ==
+print('Creating Node with Persistent disk:')
+name = '%s-persist-node' % DEMO_BASE_NAME
+# Use objects this time instead of names
+# Get latest Debian 7 image
+image = gce.ex_get_image('debian-7')
+# Get Machine Size
+size = gce.ex_get_size('n1-standard-1')
+# Create Disk.  Size is None to just take default of image
+volume_name = '%s-boot-disk' % DEMO_BASE_NAME
+volume = gce.create_volume(None, volume_name, image=image)
+# Create Node with Disk
+node_2 = gce.create_node(name, size, image, ex_tags=['libcloud'],
+                         ex_boot_disk=volume)
+print('   Node %s created with attached disk %s' % (node_2.name, volume.name))
+
+# == Create Multiple nodes at once ==
+base_name = '%s-muliple-nodes' % DEMO_BASE_NAME
+number = MAX_NODES - 2
+if number > 0:
+    print('Creating Multiple Nodes (%s):' % number)
+    multi_nodes = gce.ex_create_multiple_nodes(base_name, size, image, number,
+                                               ex_tags=['libcloud'])
+    for node in multi_nodes:
+        print('   Node %s created.' % node.name)
+
+# == Create a Network ==
+print('Creating Network:')
+name = '%s-network' % DEMO_BASE_NAME
+cidr = '10.10.0.0/16'
+network_1 = gce.ex_create_network(name, cidr)
+print('   Network %s created' % network_1.name)
+
+# == Create a Firewall ==
+print('Creating a Firewall:')
+name = '%s-firewall' % DEMO_BASE_NAME
+allowed = [{'IPProtocol': 'tcp',
+            'ports': ['3141']}]
+firewall_1 = gce.ex_create_firewall(name, allowed, network=network_1,
+                                    source_tags=['libcloud'])
+print('   Firewall %s created' % firewall_1.name)
+
+# == Create a Static Address ==
+print('Creating an Address:')
+name = '%s-address' % DEMO_BASE_NAME
+address_1 = gce.ex_create_address(name)
+print('   Address %s created with IP %s' % (address_1.name, address_1.address))
+
+# == List Updated Resources in current zone/region ==
+print('Updated Resources in current zone/region:')
+nodes = gce.list_nodes()
+display('Nodes', nodes)
+
+addresses = gce.ex_list_addresses()
+display('Addresses', addresses)
+
+volumes = gce.ex_list_volumes()
+display('Volumes', volumes)
+
+firewalls = gce.ex_list_firewalls()
+display('Firewalls', firewalls)
+
+networks = gce.ex_list_networks()
+display('Networks', networks)
+
+
+if CLEANUP:
+    print('Cleaning up %s resources created.' % DEMO_BASE_NAME)
+    clean_up(DEMO_BASE_NAME, nodes,
+             addresses + volumes + firewalls + networks)
diff --git a/demos/secrets.py-dist b/demos/secrets.py-dist
index f7e3fc6..2de4ba7 100644
--- a/demos/secrets.py-dist
+++ b/demos/secrets.py-dist
@@ -22,6 +22,10 @@ DREAMHOST_PARAMS = ('key',)
 EC2_PARAMS = ('access_id', 'secret')
 ECP_PARAMS = ('user_name', 'password')
 GANDI_PARAMS = ('user',)
+GCE_PARAMS = ('email_address', 'key') # Service Account Authentication
+#GCE_PARAMS = ('client_id', 'client_secret') # Installed App Authentication
+GCE_KEYWORD_PARAMS = ({'project': 'project_name', 'ssh_username': None,
+                       'ssh_private_key_file': None})
 HOSTINGCOM_PARAMS = ('user', 'secret')
 IBM_PARAMS = ('user', 'secret')
 # OPENSTACK_PARAMS = ('user_name', 'api_key', secure_bool, 'host', port_int)
diff --git a/libcloud/common/google.py b/libcloud/common/google.py
new file mode 100644
index 0000000..98ee641
--- /dev/null
+++ b/libcloud/common/google.py
@@ -0,0 +1,466 @@
+# 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.
+
+"""
+Module for Google Connection and Authentication classes.
+"""
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
+import base64
+import calendar
+import errno
+import time
+import datetime
+import os
+import socket
+import urllib
+import urlparse
+
+
+from libcloud.common.base import (ConnectionUserAndKey, JsonResponse,
+                                  PollingConnection)
+from libcloud.compute.types import (InvalidCredsError,
+                                    MalformedResponseError,
+                                    LibcloudError)
+
+try:
+    from Crypto.Hash import SHA256
+    from Crypto.PublicKey import RSA
+    from Crypto.Signature import PKCS1_v1_5
+except ImportError:
+    # The pycrypto library is unavailable
+    SHA256 = None
+    RSA = None
+    PKCS1_v1_5 = None
+
+TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
+
+
+class GoogleAuthError(LibcloudError):
+    """Generic Error class for various authentication errors."""
+    def __init__(self, value):
+        self.value = value
+
+    def __repr__(self):
+        return repr(self.value)
+
+
+class GoogleResponse(JsonResponse):
+    pass
+
+
+class GoogleBaseDriver(object):
+    name = "Google API"
+
+
+class GoogleBaseAuthConnection(ConnectionUserAndKey):
+    """
+    Base class for Google Authentication.  Should be subclassed for specific
+    types of authentication.
+    """
+    driver = GoogleBaseDriver
+    responseCls = GoogleResponse
+    name = 'Google Auth'
+    host = 'accounts.google.com'
+    auth_path = '/o/oauth2/auth'
+
+    def add_default_headers(self, headers):
+        headers['Content-Type'] = "application/x-www-form-urlencoded"
+        headers['Host'] = self.host
+        return headers
+
+    def _token_request(self, request_body):
+        """
+        Return an updated token from a token request body.
+
+        @param  request_body: A dictionary of values to send in the body of the
+                              token request.
+        @type   request_body: C{dict}
+
+        @return:  A dictionary with updated token information
+        @rtype:   C{dict}
+        """
+        data = urllib.urlencode(request_body)
+        now = datetime.datetime.utcnow()
+        response = self.request('/o/oauth2/token', method='POST', data=data)
+        token_info = response.object
+        if 'expires_in' in token_info:
+            expire_time = now + datetime.timedelta(
+                seconds=token_info['expires_in'])
+            token_info['expire_time'] = expire_time.strftime(TIMESTAMP_FORMAT)
+        return token_info
+
+    def __init__(self, user_id, key, scope,
+                 redirect_uri='urn:ietf:wg:oauth:2.0:oob',
+                 login_hint=None, **kwargs):
+        """
+        @param  user_id: The email address (for service accounts) or Client ID
+                         (for installed apps) to be used for authentication.
+        @type   user_id: C{str}
+
+        @param  key: The RSA Key (for service accounts) or file path containing
+                     key or Client Secret (for installed apps) to be used for
+                     authentication.
+        @type   key: C{str}
+
+        @param  scope: A list of urls defining the scope of authentication
+                       to grant.
+        @type   scope: C{list}
+
+        @keyword  redirect_uri: The Redirect URI for the authentication
+                                request.  See Google OAUTH2 documentation for
+                                more info.
+        @type     redirect_uri: C{str}
+
+        @keyword  login_hint: Login hint for authentication request.  Useful
+                              for Installed Application authentication.
+        @type     login_hint: C{str}
+        """
+
+        self.scope = " ".join(scope)
+        self.redirect_uri = redirect_uri
+        self.login_hint = login_hint
+
+        super(GoogleBaseAuthConnection, self).__init__(user_id, key, **kwargs)
+
+
+class GoogleInstalledAppAuthConnection(GoogleBaseAuthConnection):
+    """Authentication connection for "Installed Application" authentication."""
+    def get_code(self):
+        """
+        Give the user a URL that they can visit to authenticate and obtain a
+        code.  This method will ask for that code that the user can paste in.
+
+        @return:  Code supplied by the user after authenticating
+        @rtype:   C{str}
+        """
+        auth_params = {'response_type': 'code',
+                       'client_id': self.user_id,
+                       'redirect_uri': self.redirect_uri,
+                       'scope': self.scope,
+                       'state': 'Libcloud Request'}
+        if self.login_hint:
+            auth_params['login_hint'] = self.login_hint
+
+        data = urllib.urlencode(auth_params)
+
+        url = 'https://%s%s?%s' % (self.host, self.auth_path, data)
+        print('Please Go to the following URL and sign in:')
+        print(url)
+        code = raw_input('Enter Code:')
+        return code
+
+    def get_new_token(self):
+        """
+        Get a new token. Generally used when no previous token exists or there
+        is no refresh token
+
+        @return:  Dictionary containing token information
+        @rtype:   C{dict}
+        """
+        # Ask the user for a code
+        code = self.get_code()
+
+        token_request = {'code': code,
+                         'client_id': self.user_id,
+                         'client_secret': self.key,
+                         'redirect_uri': self.redirect_uri,
+                         'grant_type': 'authorization_code'}
+
+        return self._token_request(token_request)
+
+    def refresh_token(self, token_info):
+        """
+        Use the refresh token supplied in the token info to get a new token.
+
+        @param  token_info: Dictionary containing current token information
+        @type   token_info: C{dict}
+
+        @return:  A dictionary containing updated token information.
+        @rtype:   C{dict}
+        """
+        if 'refresh_token' not in token_info:
+            return self.get_new_token()
+        refresh_request = {'refresh_token': token_info['refresh_token'],
+                           'client_id': self.user_id,
+                           'client_secret': self.key,
+                           'grant_type': 'refresh_token'}
+
+        new_token = self._token_request(refresh_request)
+        if 'refresh_token' not in new_token:
+            new_token['refresh_token'] = token_info['refresh_token']
+        return new_token
+
+
+class GoogleServiceAcctAuthConnection(GoogleBaseAuthConnection):
+    """Authentication class for "Service Account" authentication."""
+    def __init__(self, user_id, key, *args, **kwargs):
+        """
+        Check to see if PyCrypto is available, and convert key file path into a
+        key string if the key is in a file.
+
+        @param  user_id: Email address to be used for Service Account
+                authentication.
+        @type   user_id: C{str}
+
+        @param  key: The RSA Key or path to file containing the key.
+        @type   key: C{str}
+        """
+        if SHA256 is None:
+            raise GoogleAuthError('PyCrypto library required for '
+                                  'Service Accout Authentication.')
+        keypath = os.path.expanduser(key)
+        try:
+            key = open(keypath).read()
+        except IOError:
+            pass
+        super(GoogleServiceAcctAuthConnection, self).__init__(
+            user_id, key, *args, **kwargs)
+
+    def get_new_token(self):
+        """
+        Get a new token using the email address and RSA Key.
+
+        @return:  Dictionary containing token information
+        @rtype:   C{dict}
+        """
+        # The header is always the same
+        header = {'alg': 'RS256', 'typ': 'JWT'}
+        header_enc = base64.urlsafe_b64encode(json.dumps(header))
+
+        # Construct a claim set
+        claim_set = {'iss': self.user_id,
+                     'scope': self.scope,
+                     'aud': 'https://accounts.google.com/o/oauth2/token',
+                     'exp': int(time.time()) + 3600,
+                     'iat': int(time.time())}
+        claim_set_enc = base64.urlsafe_b64encode(json.dumps(claim_set))
+
+        # The message contains both the header and claim set
+        message = '%s.%s' % (header_enc, claim_set_enc)
+        # Then the message is signed using the key supplied
+        key = RSA.importKey(self.key)
+        hash_func = SHA256.new(message)
+        signer = PKCS1_v1_5.new(key)
+        signature = base64.urlsafe_b64encode(signer.sign(hash_func))
+
+        # Finally the message and signature are sent to get a token
+        jwt = '%s.%s' % (message, signature)
+        request = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
+                   'assertion': jwt}
+
+        return self._token_request(request)
+
+    def refresh_token(self, token_info):
+        """
+        Refresh the current token.
+
+        Service Account authentication doesn't supply a "refresh token" so
+        this simply gets a new token using the email address/key.
+
+        @param  token_info: Dictionary contining token information.
+                            (Not used, but here for compatibility)
+        @type   token_info: C{dict}
+
+        @return:  A dictionary containing updated token information.
+        @rtype:   C{dict}
+        """
+        return self.get_new_token()
+
+
+class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
+    """Base connection class for interacting with Google APIs."""
+    driver = GoogleBaseDriver
+    responseCls = GoogleResponse
+    poll_interval = 2.0
+    timeout = 60
+
+    def __init__(self, user_id, key, auth_type=None,
+                 credential_file=None, **kwargs):
+        """
+        Determine authentication type, set up appropriate authentication
+        connection and get initial authentication information.
+
+        @param  user_id: The email address (for service accounts) or Client ID
+                         (for installed apps) to be used for authentication.
+        @type   user_id: C{str}
+
+        @param  key: The RSA Key (for service accounts) or file path containing
+                     key or Client Secret (for installed apps) to be used for
+                     authentication.
+        @type   key: C{str}
+
+        @keyword  auth_type: Accepted values are "SA" or "IA"
+                             ("Service Account" or "Installed Application").
+                             If not supplied, auth_type will be guessed based
+                             on value of user_id.
+        @type     auth_type: C{str}
+
+        @keyword  credential_file: Path to file for caching authentication
+                                   information.
+        @type     credential_file: C{str}
+        """
+        self.credential_file = credential_file or '~/.gce_libcloud_auth'
+
+        if auth_type is None:
+            # Try to guess.  Service accounts use an email address
+            # as the user id.
+            if '@' in user_id:
+                auth_type = 'SA'
+            else:
+                auth_type = 'IA'
+
+        self.token_info = self._get_token_info_from_file()
+        if auth_type == 'SA':
+            self.auth_conn = GoogleServiceAcctAuthConnection(
+                user_id, key, self.scope, **kwargs)
+        elif auth_type == 'IA':
+            self.auth_conn = GoogleInstalledAppAuthConnection(
+                user_id, key, self.scope, **kwargs)
+        else:
+            raise GoogleAuthError('auth_type should be \'SA\' or \'IA\'')
+
+        if self.token_info is None:
+            self.token_info = self.auth_conn.get_new_token()
+            self._write_token_info_to_file()
+
+        self.token_expire_time = datetime.datetime.strptime(
+            self.token_info['expire_time'], TIMESTAMP_FORMAT)
+
+        super(GoogleBaseConnection, self).__init__(user_id, key, **kwargs)
+
+    def add_default_headers(self, headers):
+        """
+        @inherits: L{Connection.add_default_headers}
+        """
+        headers['Content-Type'] = "application/json"
+        headers['Host'] = self.host
+        return headers
+
+    def pre_connect_hook(self, params, headers):
+        """
+        Check to make sure that token hasn't expired.  If it has, get an
+        updated token.  Also, add the token to the headers.
+
+        @inherits: L{Connection.pre_connect_hook}
+        """
+        now = datetime.datetime.utcnow()
+        if self.token_expire_time < now:
+            self.token_info = self.auth_conn.refresh_token(self.token_info)
+            self.token_expire_time = datetime.datetime.strptime(
+                self.token_info['expire_time'], TIMESTAMP_FORMAT)
+            self._write_token_info_to_file()
+        headers['Authorization'] = 'Bearer %s' % (
+            self.token_info['access_token'])
+
+        return params, headers
+
+    def encode_data(self, data):
+        """Encode data to JSON"""
+        return json.dumps(data)
+
+    def request(self, *args, **kwargs):
+        """
+        @inherits: L{Connection.request}
+        """
+        # Adds some retry logic for the occasional
+        # "Connection Reset by peer" error.
+        retries = 4
+        tries = 0
+        while tries < (retries - 1):
+            try:
+                return super(GoogleBaseConnection, self).request(
+                    *args, **kwargs)
+            except socket.error as e:
+                if e.errno == 104:
+                    tries = tries + 1
+                else:
+                    raise
+        # One more time, then give up.
+        return super(GoogleBaseConnecion, self).request(*args, **kwargs)
+
+    def _get_token_info_from_file(self):
+        """
+        Read credential file and return token information.
+
+        @return:  Token information dictionary, or None
+        @rtype:   C{dict} or C{None}
+        """
+        token_info = None
+        filename = os.path.realpath(os.path.expanduser(self.credential_file))
+        try:
+            f = open(filename)
+            data = f.read()
+            f.close()
+            token_info = json.loads(data)
+        except IOError:
+            pass
+        return token_info
+
+    def _write_token_info_to_file(self):
+        """
+        Write token_info to credential file.
+        """
+        filename = os.path.realpath(os.path.expanduser(self.credential_file))
+        f = open(filename, 'w')
+        data = json.dumps(self.token_info)
+        f.write(data)
+        f.close()
+
+    def has_completed(self, response):
+        """
+        Determine if operation has completed based on response.
+
+        @param  response: JSON response
+        @type   response: I{responseCls}
+
+        @return:  True if complete, False otherwise
+        @rtype:   C{bool}
+        """
+        if response.object['status'] == 'DONE':
+            return True
+        else:
+            return False
+
+    def get_poll_request_kwargs(self, response, context, request_kwargs):
+        """
+        @inherits: L{PollingConnection.get_poll_request_kwargs}
+        """
+        return {'action': response.object['selfLink']}
+
+    def morph_action_hook(self, action):
+        """
+        Update action to correct request path.
+
+        In many places, the Google API returns a full URL to a resource.
+        This will strip the scheme and host off of the path and just return
+        the request.  Otherwise, it will append the base request_path to
+        the action.
+
+        @param  action: The action to be called in the http request
+        @type   action: C{str}
+
+        @return:  The modified request based on the action
+        @rtype:   C{str}
+        """
+        if action.startswith('https://'):
+            u = urlparse.urlsplit(action)
+            request = urlparse.urlunsplit((None, None, u[2], u[3], u[4]))
+        else:
+            request = self.request_path + action
+        return request
diff --git a/libcloud/compute/drivers/__init__.py b/libcloud/compute/drivers/__init__.py
index 68f273a..9c6c078 100644
--- a/libcloud/compute/drivers/__init__.py
+++ b/libcloud/compute/drivers/__init__.py
@@ -27,6 +27,7 @@ __all__ = [
     'elasticstack',
     'elastichosts',
     'cloudsigma',
+    'gce',
     'gogrid',
     'hostvirtual',
     'ibm_sce',
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
new file mode 100644
index 0000000..1a15048
--- /dev/null
+++ b/libcloud/compute/drivers/gce.py
@@ -0,0 +1,1854 @@
+# 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.
+"""
+Module for Google Compute Engine Driver.
+"""
+
+import datetime
+import time
+import sys
+import urlparse
+import os
+import getpass
+
+try:
+    import paramiko
+except ImportError:
+    paramiko = None
+
+from libcloud.common.google import GoogleResponse
+from libcloud.common.google import GoogleBaseConnection
+
+from libcloud.compute.base import Node, NodeDriver, NodeImage, NodeLocation
+from libcloud.compute.base import NodeSize, StorageVolume, UuidMixin
+from libcloud.compute.providers import Provider
+from libcloud.compute.types import NodeState, LibcloudError
+
+API_VERSION = 'v1beta15'
+DEFAULT_TASK_COMPLETION_TIMEOUT = 180
+
+
+def timestamp_to_datetime(timestamp):
+    """
+    Return a datetime object that corresponds to the time in an RFC3339
+    timestamp.
+
+    @param  timestamp: RFC3339 timestamp string
+    @type   timestamp: C{str}
+
+    @return:  Datetime object corresponding to timestamp
+    @rtype:   C{datetime}
+    """
+    ts = datetime.datetime.strptime(timestamp[:-6], '%Y-%m-%dT%H:%M:%S.%f')
+    if timestamp[-1] == 'Z':
+        return ts
+    else:
+        tz_hours = int(timestamp[-5:-3])
+        tz_mins = int(timestamp[-2:]) * int(timestamp[-6:-5] + '1')
+        tz_delta = datetime.timedelta(hours=tz_hours, minutes=tz_mins)
+        return ts + tz_delta
+
+
+class GCEError(LibcloudError):
+    """Base class for general GCE Errors"""
+    def __init__(self, code, value):
+        self.code = code
+        self.value = value
+
+    def __repr__(self):
+        return repr(self.code) + ": " + repr(self.value)
+
+
+class GCEKnownError(GCEError):
+    """Base class for GCE Errors that can be classified"""
+    def __init__(self, value):
+        self.value = value
+
+    def __repr__(self):
+        return repr(self.value)
+
+
+class QuotaExceededError(GCEKnownError):
+    pass
+
+
+class ResourceExistsError(GCEKnownError):
+    pass
+
+
+class GCEResponse(GoogleResponse):
+    pass
+
+
+class GCEConnection(GoogleBaseConnection):
+    """Connection class for the GCE driver."""
+    host = 'www.googleapis.com'
+    responseCls = GCEResponse
+
+    def __init__(self, user_id, key, secure, auth_type=None,
+                 credential_file=None, project=None, **kwargs):
+        self.scope = ['https://www.googleapis.com/auth/compute']
+        super(GCEConnection, self).__init__(user_id, key, secure=secure,
+                                            auth_type=auth_type,
+                                            credential_file=credential_file,
+                                            **kwargs)
+        self.request_path = '/compute/%s/projects/%s' % (API_VERSION,
+                                                         project)
+
+
+class GCEAddress(UuidMixin):
+    """A GCE Static address."""
+    def __init__(self, id, name, address, region, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.address = address
+        self.region = region
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCEAddress id="%s" name="%s" address="%s">' % (
+            self.id, self.name, self.address)
+
+    def destroy(self):
+        """
+        Destroy this address.
+
+        @return: True if successful
+        @rtype:  C{bool}
+        """
+        return self.driver.ex_destroy_address(address=self)
+
+
+class GCEFailedNode(object):
+    """Dummy Node object for nodes that are not created."""
+    def __init__(self, name, error):
+        self.name = name
+        self.error = error
+
+    def __repr__(self):
+        return '<GCEFailedNode name="%s" error_code="%s">' % (
+            self.name, self.error['code'])
+
+
+class GCEFirewall(UuidMixin):
+    """A GCE Firewall rule class."""
+    def __init__(self, id, name, allowed, network, source_ranges, source_tags,
+                 driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.network = network
+        self.allowed = allowed
+        self.source_ranges = source_ranges
+        self.source_tags = source_tags
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCEFirewall id="%s" name="%s" network="%s">' % (
+            self.id, self.name, self.network.name)
+
+    def destroy(self):
+        """
+        Destroy this firewall.
+
+        @return: True if successful
+        @rtype:  C{bool}
+        """
+        return self.driver.ex_destroy_firewall(firewall=self)
+
+
+class GCENetwork(UuidMixin):
+    """A GCE Network object class."""
+    def __init__(self, id, name, cidr, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.cidr = cidr
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCENetwork id="%s" name="%s" cidr="%s">' % (
+            self.id, self.name, self.cidr)
+
+    def destroy(self):
+        """
+        Destroy this newtwork
+
+        @return: True if successful
+        @rtype:  C{bool}
+        """
+        return self.driver.ex_destroy_network(network=self)
+
+
+class GCENodeSize(NodeSize):
+    """A GCE Node Size (MachineType) class."""
+    def __init__(self, id, name, ram, disk, bandwidth, price, driver,
+                 extra=None):
+        self.extra = extra
+        super(GCENodeSize, self).__init__(id, name, ram, disk, bandwidth,
+                                          price, driver)
+
+
+class GCEProject(UuidMixin):
+    """GCE Project information."""
+    def __init__(self, id, name, metadata, quotas, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.metadata = metadata
+        self.quotas = quotas
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def _repr__(self):
+        return '<GCEProject id="%s" name="%s">' % (self.id, self.name)
+
+
+class GCEZone(NodeLocation):
+    """Subclass of NodeLocation to provide additional information."""
+    def __init__(self, id, name, status, maintenance_windows, quotas,
+                 deprecated, driver, extra=None):
+        self.status = status
+        self.maintenance_windows = maintenance_windows
+        self.quotas = quotas
+        self.deprecated = deprecated
+        self.extra = extra
+        country = name.split('-')[0]
+        super(GCEZone, self).__init__(id=str(id), name=name, country=country,
+                                      driver=driver)
+
+    def _get_next_maint(self):
+        """
+        Returns the next Maintenance Window.
+
+        @return:  A dictionary containing maintenance window info
+        @rtype:   C{dict}
+        """
+        begin = None
+        next_window = None
+        if len(self.maintenance_windows) == 1:
+            return self.maintenance_windows[0]
+        for mw in self.maintenance_windows:
+            begin_next = timestamp_to_datetime(mw['beginTime'])
+            if (not begin) or (begin_next < begin):
+                begin = begin_next
+                next_window = mw
+        return next_window
+
+    def _get_time_until_mw(self):
+        """
+        Returns time until next maintenance window.
+
+        @return:  Time until next maintenance window
+        @rtype:   C{datetime.timedelta}
+        """
+        next_window = self._get_next_maint()
+        now = datetime.datetime.utcnow()
+        next_begin = timestamp_to_datetime(next_window['beginTime'])
+        return next_begin - now
+
+    def _get_next_mw_duration(self):
+        """
+        Returns the duration of the next maintenance window.
+
+        @return:  Duration of next maintenance window
+        @rtype:   C{datetime.timedelta}
+        """
+        next_window = self._get_next_maint()
+        next_begin = timestamp_to_datetime(next_window['beginTime'])
+        next_end = timestamp_to_datetime(next_window['endTime'])
+        return next_end - next_begin
+
+    @property
+    def time_until_mw(self):
+        """
+        Returns the time until the next Maintenance Window as a
+        datetime.timedelta object.
+        """
+        return self._get_time_until_mw()
+
+    @property
+    def next_mw_duration(self):
+        """
+        Returns the duration of the next Maintenance Window as a
+        datetime.timedelta object.
+        """
+        return self._get_next_mw_duration()
+
+    def __repr__(self):
+        return '<GCEZone id="%s" name="%s" status="%s">' % (self.id, self.name,
+                                                            self.status)
+
+
+class GCENodeDriver(NodeDriver):
+    """
+    Base class for GCE Node Driver.
+    """
+    connectionCls = GCEConnection
+    api_name = 'googleapis'
+    name = "Google Compute Engine"
+    type = Provider.GCE
+    website = 'https://www.googleapis.com/'
+
+    NODE_STATE_MAP = {
+        "PROVISIONING": NodeState.PENDING,
+        "STAGING": NodeState.PENDING,
+        "RUNNING": NodeState.RUNNING,
+        "STOPPED": NodeState.TERMINATED,
+        "TERMINATED": NodeState.TERMINATED
+    }
+
+    def __init__(self, user_id, key, datacenter=None,
+                 project=None, ssh_username=None, ssh_private_key_file=None,
+                 auth_type=None, **kwargs):
+        """
+        @param  user_id: The email address (for service accounts) or Client ID
+                         (for installed apps) to be used for authentication.
+        @type   user_id: C{str}
+
+        @param  key: The RSA Key (for service accounts) or file path containing
+                     key or Client Secret (for installed apps) to be used for
+                     authentication.
+        @type   key: C{str}
+
+        @keyword  datacenter: The name of the datacenter (zone) used for
+                              operations.
+        @type     datacenter: C{str}
+
+        @keyword  project: Your GCE project name. (required)
+        @type     project: C{str}
+
+        @keyword  ssh_username: SSH Username that has permission to do a
+                                password-less reboot of an instance with sudo.
+        @type     ssh_username: C{str}
+
+        @keyword  ssh_private_key_file: Name of file containing ssh key that
+                                        corresponds to ssh_username.
+        @type     ssh_private_key_file: C{str}
+
+        @keyword  auth_type: Accepted values are "SA" or "IA"
+                             ("Service Account" or "Installed Application").
+                             If not supplied, auth_type will be guessed based
+                             on value of user_id.
+        @type     auth_type: C{str}
+        """
+        self.auth_type = auth_type
+        self.project = project
+        super(GCENodeDriver, self).__init__(user_id, key, **kwargs)
+        if project:
+            self.project = project
+        else:
+            print "Please specify the project in your Driver's constructor"
+            sys.exit(1)
+
+        # Cache Zone information to reduce API calls and increase speed
+        self.base_path = '/compute/%s/projects/%s' % (API_VERSION,
+                                                      self.project)
+        self.zone_list = self.ex_list_zones()
+        self.zone_dict = {}
+        for zone in self.zone_list:
+            self.zone_dict[zone.name] = zone
+        if datacenter:
+            self.zone = self.ex_get_zone(datacenter)
+        else:
+            self.zone = None
+        self.ssh_username = ssh_username
+        self.ssh_private_key_file = ssh_private_key_file
+        if paramiko:
+            self.SSHClient = paramiko.SSHClient()
+
+    def _ex_connection_class_kwargs(self):
+        return {'auth_type': self.auth_type,
+                'project': self.project}
+
+    def _categorize_error(self, error):
+        """
+        Parse error message returned from GCE operation and raise the
+        appropriate Exception.
+
+        @param  error: Error dictionary from a GCE Operations response
+        @type   error: C{dict}
+        """
+        err = error['errors'][0]
+        message = err['message']
+        code = err['code']
+        if code == 'QUOTA_EXCEEDED':
+            raise QuotaExceededError(message)
+        elif code == 'RESOURCE_ALREADY_EXISTS':
+            raise ResourceExistsError(message)
+        else:
+            raise GCEError(code, message)
+
+    def _find_zone(self, name, res_type, region=False):
+        """
+        Find the zone for a named resource.
+
+        @param  name: Name of resource to find
+        @type   name: C{str}
+
+        @param  res_type: Type of resource to find.
+                          Examples include: 'disks', 'instances' or 'addresses'
+        @type   res_type: C{str}
+
+        @keyword  region: If True, find a region instead of a zone.
+        @keyword  region: C{bool}
+
+        @return:  Name of zone (or region) that the resource is in.
+        @rtype:   C{str}
+        """
+        request = '/aggregated/%s' % res_type
+        res_list = self.connection.request(request).object
+        for k, v in res_list['items'].items():
+            if res_type in v:
+                for res in v[res_type]:
+                    if res['name'] == name:
+                        if region:
+                            return k.lstrip('regions/')
+                        else:
+                            return k.lstrip('zones/')
+
+    def _match_images(self, project, partial_name):
+        """
+        Find the latest image, given a partial name.
+
+        For example, providing 'debian-7' will return the image object for the
+        most recent image with a name that starts with 'debian-7' in the
+        supplied project.  If no project is given, it will search your own
+        project.
+
+        @param  project:  The name of the project to search for images.
+                          Examples include: 'debian-cloud' and 'centos-cloud'.
+        @type   project:  C{str} or C{None}
+
+        @param  partial_name: The full name or beginning of a name for an
+                              image.
+        @type   partial_name: C{str}
+
+        @return:  The latest image object that maches the partial name.
+        @rtype:   L{NodeImage}
+        """
+        project_images = self.list_images(project)
+        partial_match = []
+        for image in project_images:
+            if image.name == partial_name:
+                return image
+            if image.name.startswith(partial_name):
+                ts = timestamp_to_datetime(image.extra['creationTimestamp'])
+                if not partial_match or partial_match[0] < ts:
+                    partial_match = [ts, image]
+
+        if partial_match:
+            return partial_match[1]
+
+    def ex_list_addresses(self, region=None):
+        """
+        Return a list of static addreses for a region or all.
+
+        @keyword  region: The region to return addresses from. For example:
+                          'us-central1'.  If None, will return addresses from
+                          region of self.zone.  If 'all', will return all
+                          addresses.
+        @type     region: C{str} or C{None}
+
+        @return: A list of static address objects.
+        @rtype: C{list} of L{GCEAddress}
+        """
+        list_addresses = []
+        if region is None and self.zone:
+            region = '-'.join(self.zone.name.split('-')[:-1])
+        elif region == 'all':
+            region = None
+
+        if region is None:
+            request = '/aggregated/addresses'
+        else:
+            request = '/regions/%s/addresses' % region
+
+        response = self.connection.request(request, method='GET').object
+
+        if 'items' in response:
+            # The aggregated result returns dictionaries for each region
+            if region is None:
+                for k, v in response['items'].items():
+                    if 'addresses' in v:
+                        for address in v['addresses']:
+                            address_obj = self._to_address(address)
+                            list_addresses.append(address_obj)
+
+            else:
+                for address in response['items']:
+                    address_obj = self._to_address(address)
+                    list_addresses.append(address_obj)
+
+        return list_addresses
+
+    def ex_list_firewalls(self):
+        """
+        Return the list of firewalls.
+
+        @return: A list of firewall objects.
+        @rtype: C{list} of L{GCEFirewall}
+        """
+        list_firewalls = []
+        request = '/global/firewalls'
+        response = self.connection.request(request, method='GET').object
+        if 'items' in response:
+            for firewall in response['items']:
+                list_firewalls.append(self._to_firewall(firewall))
+
+        return list_firewalls
+
+    def list_images(self, ex_project=None):
+        """
+        Return a list of image objects for a project.
+
+        @keyword  ex_project: Optional alternate project name.
+        @type     ex_project: C{str} or C{None}
+
+        @return:  List of NodeImage objects
+        @rtype:   C{list} of L{NodeImage}
+        """
+        list_images = []
+        request = '/global/images'
+        if ex_project is None:
+            res = self.connection.request(request, method='GET').object
+        else:
+            save_request_path = self.connection.request_path
+            new_request_path = save_request_path.replace(self.project,
+                                                         ex_project)
+            self.connection.request_path = new_request_path
+            #new_base = self.base_path.replace(self.project, ex_project)
+            #uri = new_base + request
+            response = self.connection.request(request, method='GET')
+            res = response.object
+            self.connection.request_path = save_request_path
+
+        if 'items' in res:
+            for image in res['items']:
+                node_image = self._to_node_image(image)
+                list_images.append(node_image)
+        return list_images
+
+    def list_locations(self):
+        """
+        Return a list of locations (zones).
+
+        The L{ex_list_zones} method returns more comprehensive results, but
+        this is here for compatibility.
+
+        @return: List of NodeLocation objects
+        @rtype: C{list} of L{NodeLocation}
+        """
+        list_locations = []
+        request = '/zones'
+        response = self.connection.request(request, method='GET').object
+        for location in response['items']:
+            list_locations.append(self._to_node_location(location))
+
+        return list_locations
+
+    def ex_list_networks(self):
+        """
+        Return the list of networks.
+
+        @return: A list of network objects.
+        @rtype: C{list} of L{GCENetwork}
+        """
+        list_networks = []
+        request = '/global/networks'
+        response = self.connection.request(request, method='GET').object
+        for network in response['items']:
+            list_networks.append(self._to_network(network))
+
+        return list_networks
+
+    def list_nodes(self, ex_zone=None):
+        """
+        Return a list of nodes in the current zone or all zones.
+
+        @keyword  ex_zone:  Optional zone name or 'all'
+        @type     ex_zone:  C{str} or L{GCEZone} or L{NodeLocation} or C{None}
+
+        @return:  List of Node objects
+        @rtype:   C{list} of L{Node}
+        """
+        list_nodes = []
+        # Use provided zone or default zone
+        zone = ex_zone or self.zone
+        # Setting ex_zone to 'all' overrides the default zone
+        if zone == 'all':
+            zone = None
+        if zone is None:
+            request = '/aggregated/instances'
+        elif hasattr(zone, 'name'):
+            request = '/zones/%s/instances' % zone.name
+        else:
+            request = '/zones/%s/instances' % zone
+
+        response = self.connection.request(request, method='GET').object
+
+        if 'items' in response:
+            # The aggregated response returns a dict for each zone
+            if zone is None:
+                for k, v in response['items'].items():
+                    if 'instances' in v:
+                        for instance in v['instances']:
+                            node = self._to_node(instance)
+                            list_nodes.append(node)
+
+            else:
+                for instance in response['items']:
+                    node = self._to_node(instance)
+                    list_nodes.append(node)
+
+        return list_nodes
+
+    def list_sizes(self, location=None):
+        """
+        Return a list of sizes (machineTypes) in a zone.
+
+        @keyword  location: Location or Zone for sizes
+        @type     location: C{str} or L{GCEZone} or L{NodeLocation} or C{None}
+
+        @return:  List of GCENodeSize objects
+        @rtype:   C{list} of L{GCENodeSize}
+        """
+        list_sizes = []
+        location = location or self.zone
+        if location is None:
+            request = '/aggregated/machineTypes'
+        elif hasattr(location, 'name'):
+            request = '/zones/%s/machineTypes' % location.name
+        else:
+            request = '/zones/%s/machineTypes' % location
+
+        response = self.connection.request(request, method='GET').object
+
+        if 'items' in response:
+            # The aggregated response returns a dict for each zone
+            if location is None:
+                for k, v in response['items'].items():
+                    if 'machineTypes' in v:
+                        for machine_type in v['machineTypes']:
+                            node_size = self._to_node_size(machine_type)
+                            list_sizes.append(node_size)
+
+            else:
+                for machine_type in response['items']:
+                    node_size = self._to_node_size(machine_type)
+                    list_sizes.append(node_size)
+
+        return list_sizes
+
+    def ex_list_volumes(self, ex_zone=None):
+        """
+        Return a list of volumes for a zone or all.
+
+        Will return list from provided zone, or from the default zone unless
+        given the value of 'all'.
+
+        @keyword  region: The zone to return volumes from.
+        @type     region: C{str} or L{GCEZone} or L{NodeLocation} or C{None}
+
+        @return: A list of volume objects.
+        @rtype: C{list} of L{StorageVolume}
+        """
+        list_volumes = []
+        zone = ex_zone or self.zone
+        if zone == 'all':
+            zone = None
+        if zone is None:
+            request = '/aggregated/disks'
+        elif hasattr(zone, 'name'):
+            request = '/zones/%s/disks' % zone.name
+        else:
+            request = '/zones/%s/disks' % zone
+
+        response = self.connection.request(request, method='GET').object
+        if 'items' in response:
+            # The aggregated response returns a dict for each zone
+            if zone is None:
+                for k, v in response['items'].items():
+                    if 'disks' in v:
+                        for disk in v['disks']:
+                            volume = self._to_storage_volume(disk)
+                            list_volumes.append(volume)
+
+            else:
+                for disk in response['items']:
+                    volume = self._to_storage_volume(disk)
+                    list_volumes.append(volume)
+
+        return list_volumes
+
+    def ex_list_zones(self):
+        """
+        Return the list of zones.
+
+        @return: A list of zone objects.
+        @rtype: C{list} of L{GCEZone}
+        """
+        list_zones = []
+        request = '/zones'
+        response = self.connection.request(request, method='GET').object
+        for zone in response['items']:
+            list_zones.append(self._to_zone(zone))
+
+        return list_zones
+
+    def ex_create_address(self, name, region=None):
+        """
+        Create a static address in a region.
+
+        @param  name: Name of static address
+        @type   name: C{str}
+
+        @param  region: Name of region for the addres (e.g. 'us-central1')
+        @type   region: C{str}
+
+        @return:  Static Address object
+        @rtype:   L{GCEAddress}
+        """
+        if region is None and self.zone:
+            region = '-'.join(self.zone.name.split('-')[:-1])
+        elif region is None:
+            raise GCEError('REGION_NOT_SPECIFIED',
+                           'Region must be provided for an address')
+        address_data = {'name': name}
+        request = '/regions/%s/addresses' % region
+        response = self.connection.async_request(request, method='POST',
+                                                 data=address_data).object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        return self.ex_get_address(name, region=region)
+
+    def ex_create_firewall(self, name, allowed, network='default',
+                           source_ranges=None, source_tags=None):
+        """
+        Create a firewall on a network.
+
+        Firewall rules should be supplied in the "allowed" field.  This is a
+        list of dictionaries formated like so ("ports" is optional)::
+            [{"IPProtocol": "<protocol string or number>",
+              "ports": [ "<port_numbers or ranges>"}]
+
+        For example, to allow tcp on port 8080 and udp on all ports, 'allowed'
+        would be::
+            [{"IPProtocol": "tcp",
+              "ports": ["8080"]},
+             {"IPProtocol": "udp"}]
+        See U{Firewall Reference<https://developers.google.com/compute/docs/
+        reference/latest/firewalls/insert>} for more information.
+
+        @param  name: Name of the firewall to be created
+        @type   name: C{str}
+
+        @param  allowed: List of dictionaries with rules
+        @type   allowed: C{list} of C{dict}
+
+        @keyword  network: The network that the firewall applies to.
+        @type     network: C{str} or L{GCENetwork}
+
+        @keyword  source_ranges: A list of IP ranges in CIDR format that the
+                                 firewall should apply to.
+        @type     source_ranges: C{list} of C{str}
+
+        @keyword  source_tags: A list of instance tags which the rules apply
+        @type     source_tags: C{list} of C{str}
+
+        @return:  Firewall object
+        @rtype:   L{GCEFirewall}
+        """
+        firewall_data = {}
+        if not hasattr(network, 'name'):
+            nw = self.ex_get_network(network)
+        else:
+            nw = network
+
+        firewall_data['name'] = name
+        firewall_data['allowed'] = allowed
+        firewall_data['network'] = nw.extra['selfLink']
+        if source_ranges is not None:
+            firewall_data['sourceRanges'] = source_ranges
+        if source_tags is not None:
+            firewall_data['sourceTags'] = source_tags
+
+        request = '/global/firewalls'
+
+        response = self.connection.async_request(request, method='POST',
+                                                 data=firewall_data).object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        return self.ex_get_firewall(name)
+
+    def ex_create_network(self, name, cidr):
+        """
+        Create a network.
+
+        @param  name: Name of network to be created
+        @type   name: C{str}
+
+        @param  cidr: Address range of network in CIDR format.
+        @type  cidr: C{str}
+
+        @return:  Network object
+        @rtype:   L{GCENetwork}
+        """
+        network_data = {}
+        network_data['name'] = name
+        network_data['IPv4Range'] = cidr
+
+        request = '/global/networks'
+
+        response = self.connection.async_request(request, method='POST',
+                                                 data=network_data).object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+
+        return self.ex_get_network(name)
+
+    def _create_node_req(self, name, size, image, location, network,
+                         tags=None, metadata=None, boot_disk=None):
+        """
+        Returns a request and body to create a new node.  This is a helper
+        method to suppor both L{create_node} and L{ex_create_multiple_nodes}.
+
+        @param  name: The name of the node to create.
+        @type   name: C{str}
+
+        @param  size: The machine type to use.
+        @type   size: L{GCENodeSize}
+
+        @param  image: The image to use to create the node (or, if using a
+                       persistent disk, the image the disk was created from).
+        @type   image: L{NodeImage}
+
+        @param  location: The location (zone) to create the node in.
+        @type   location: L{NodeLocation} or L{GCEZone}
+
+        @param  network: The network to associate with the node.
+        @type   network: L{GCENetwork}
+
+        @keyword  tags: A list of tags to assiciate with the node.
+        @type     tags: C{list} of C{str}
+
+        @keyword  metadata: Metadata dictionary for instance.
+        @type     metadata: C{dict}
+
+        @keyword  boot_disk:  Persistent boot disk to attach
+        @type     L{StorageVolume}
+
+        @return:  A tuple containing a request string and a node_data dict.
+        @rtype:   C{tuple} of C{str} and C{dict}
+        """
+        node_data = {}
+        node_data['machineType'] = size.extra['selfLink']
+        node_data['name'] = name
+        if tags:
+            node_data['tags'] = {'items': tags}
+        if metadata:
+            node_data['metadata'] = metadata
+        if boot_disk:
+            disks = [{'kind': 'compute#attachedDisk',
+                      'boot': True,
+                      'type': 'PERSISTENT',
+                      'mode': 'READ_WRITE',
+                      'deviceName': boot_disk.name,
+                      'zone': boot_disk.extra['zone'].extra['selfLink'],
+                      'source': boot_disk.extra['selfLink']}]
+            node_data['disks'] = disks
+            node_data['kernel'] = image.extra['preferredKernel']
+        else:
+            node_data['image'] = image.extra['selfLink']
+
+        ni = [{'kind': 'compute#instanceNetworkInterface',
+               'accessConfigs': [{'name': 'External NAT',
+                                  'type': 'ONE_TO_ONE_NAT'}],
+               'network': network.extra['selfLink']}]
+        node_data['networkInterfaces'] = ni
+
+        request = '/zones/%s/instances' % location.name
+
+        return request, node_data
+
+    def create_node(self, name, size, image, location=None,
+                    ex_network='default', ex_tags=None, ex_metadata=None,
+                    ex_boot_disk=None):
+        """
+        Create a new node and return a node object for the node.
+
+        @param  name: The name of the node to create.
+        @type   name: C{str}
+
+        @param  size: The machine type to use.
+        @type   size: C{str} or L{GCENodeSize}
+
+        @param  image: The image to use to create the node (or, if attaching
+                       a persistent disk, the image used to create the disk)
+        @type   image: C{str} or L{NodeImage}
+
+        @keyword  location: The location (zone) to create the node in.
+        @type     location: C{str} or L{NodeLocation} or L{GCEZone} or C{None}
+
+        @keyword  ex_network: The network to associate with the node.
+        @type     ex_network: C{str} or L{GCENetwork}
+
+        @keyword  ex_tags: A list of tags to assiciate with the node.
+        @type     ex_tags: C{list} of C{str} or C{None}
+
+        @keyword  ex_metadata: Metadata dictionary for instance.
+        @type     ex_metadata: C{dict} or C{None}
+
+        @keyword  ex_boot_disk: The boot disk to attach to the instance.
+        @type     ex_boot_disk: L{StorageVolume}
+
+        @return:  A Node object for the new node.
+        @rtype:   L{Node}
+        """
+        location = location or self.zone
+        if not hasattr(location, 'name'):
+            location = self.ex_get_zone(location)
+        if not hasattr(size, 'name'):
+            size = self.ex_get_size(size, location)
+        if not hasattr(ex_network, 'name'):
+            ex_network = self.ex_get_network(ex_network)
+        if not hasattr(image, 'name'):
+            image = self.ex_get_image(image)
+
+        request, node_data = self._create_node_req(name, size, image,
+                                                   location, ex_network,
+                                                   ex_tags, ex_metadata,
+                                                   ex_boot_disk)
+        response = self.connection.async_request(request, method='POST',
+                                                 data=node_data).object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+
+        return self.ex_get_node(name, location.name)
+
+    def ex_create_multiple_nodes(self, base_name, size, image, number,
+                                 location=None, ex_network='default',
+                                 ex_tags=None, ex_metadata=None,
+                                 ignore_errors=True,
+                                 timeout=DEFAULT_TASK_COMPLETION_TIMEOUT):
+        """
+        Create multiple nodes and return a list of Node objects.
+
+        Nodes will be named with the base name and a number.  For example, if
+        the base name is 'libcloud' and you create 3 nodes, they will be
+        named::
+            libcloud-000
+            libcloud-001
+            libcloud-002
+
+        @param  base_name: The base name of the nodes to create.
+        @type   base_name: C{str}
+
+        @param  size: The machine type to use.
+        @type   size: C{str} or L{GCENodeSize}
+
+        @param  image: The image to use to create the nodes.
+        @type   image: C{str} or L{NodeImage}
+
+        @param  number: The number of nodes to create.
+        @type   number: C{int}
+
+        @keyword  location: The location (zone) to create the nodes in.
+        @type     location: C{str} or L{NodeLocation} or L{GCEZone} or C{None}
+
+        @keyword  ex_network: The network to associate with the nodes.
+        @type     ex_network: C{str} or L{GCENetwork}
+
+        @keyword  ex_tags: A list of tags to assiciate with the nodes.
+        @type     ex_tags: C{list} of C{str} or C{None}
+
+        @keyword  ex_metadata: Metadata dictionary for instances.
+        @type     ex_metadata: C{dict} or C{None}
+
+        @keyword  ignore_errors: If True, don't raise Exceptions if one or
+                                 more nodes fails.
+        @type     ignore_errors: C{bool}
+
+        @keyword  timeout: The number of seconds to wait for all nodes to be
+                           created before timing out.
+
+        @return:  A list of Node objects for the new nodes.
+        @rtype:   C{list} of L{Node}
+        """
+        node_data = {}
+        location = location or self.zone
+        if not hasattr(location, 'name'):
+            location = self.ex_get_zone(location)
+        if not hasattr(size, 'name'):
+            size = self.ex_get_size(size, location)
+        if not hasattr(ex_network, 'name'):
+            ex_network = self.ex_get_network(ex_network)
+        if not hasattr(image, 'name'):
+            image = self.ex_get_image(image)
+
+        node_list = [None] * number
+        responses = []
+        for i in xrange(number):
+            name = '%s-%03d' % (base_name, i)
+            request, node_data = self._create_node_req(name, size, image,
+                                                       location, ex_network,
+                                                       ex_tags, ex_metadata)
+            response = self.connection.request(request, method='POST',
+                                               data=node_data)
+            responses.append(response.object)
+
+        start_time = time.time()
+        complete = False
+        while not complete:
+            if (time.time() - start_time >= timeout):
+                raise Exception("Timeout (%s sec) while waiting for multiple "
+                                "instances")
+            time.sleep(3)
+            complete = True
+            for i, operation in enumerate(responses):
+                if operation is None:
+                    continue
+                response = self.connection.request(
+                    operation['selfLink']).object
+                if response['status'] == 'DONE':
+                    responses[i] = None
+                    name = '%s-%03d' % (base_name, i)
+                    if 'error' in response:
+                        if ignore_errors:
+                            error = response['error']['errors'][0]
+                            node_list[i] = GCEFailedNode(name, error)
+                        else:
+                            self._categorize_error(response['error'])
+                    else:
+                        node_list[i] = self.ex_get_node(name, location.name)
+                else:
+                    complete = False
+        return node_list
+
+    def create_volume(self, size, name, location=None, image=None,
+                      snapshot=None):
+        """
+        Create a volume (disk).
+
+        @param  size: Size of volume to create (in GB). Can be None if image
+                      or snapshot is supplied.
+        @type   size: C{int} or C{str} or C{None}
+
+        @param  name: Name of volume to create
+        @type   name: C{str}
+
+        @keyword  location: Location (zone) to create the volume in
+        @type     location: C{str} or L{GCEZone} or L{NodeLocation} or C{None}
+
+        @keyword  image: Image to create disk from.
+        @type     image: L{NodeImage} or C{str} or C{None}
+
+        @keyword  snapshot: Snapshot to create image from
+        @type     snapshot: C{str}
+
+        @return:  Storage Volume object
+        @rtype:   L{StorageVolume}
+        """
+        volume_data = {}
+        params = None
+        volume_data['name'] = name
+        if size:
+            volume_data['sizeGb'] = str(size)
+        if image:
+            if not hasattr(image, 'name'):
+                image = self.ex_get_image(image)
+            params = {'sourceImage': image.extra['selfLink']}
+        if snapshot:
+            volume_data['sourceSnapshot'] = snapshot
+        location = location or self.zone
+        if not hasattr(location, 'name'):
+            location = self.ex_get_zone(location)
+        request = '/zones/%s/disks' % location.name
+        response = self.connection.async_request(request, method='POST',
+                                                 data=volume_data,
+                                                 params=params).object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+
+        return self.ex_get_volume(name)
+
+    def ex_update_firewall(self, firewall):
+        """
+        Update a firewall with new values.
+
+        To update, change the attributes of the firewall object and pass the
+        updated object to the method.
+
+        @param  firewall: A firewall object with updated values.
+        @type   firewall: L{GCEFirewall}
+
+        @return:  An object representing the new state of the firewall.
+        @rtype:   L{GCEFirewall}
+        """
+        firewall_data = {}
+        firewall_data['name'] = firewall.name
+        firewall_data['allowed'] = firewall.allowed
+        firewall_data['network'] = firewall.network.extra['selfLink']
+        if firewall.source_ranges:
+            firewall_data['sourceRanges'] = firewall.source_ranges
+        if firewall.source_tags:
+            firewall_data['sourceTags'] = firewall.source_tags
+        if firewall.extra['description']:
+            firewall_data['description'] = firewall.extra['description']
+
+        request = '/global/firewalls/%s' % firewall.name
+
+        response = self.connection.async_request(request, method='PUT',
+                                                 data=firewall_data).object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+
+        return self.ex_get_firewall(firewall.name)
+
+    def reboot_node(self, node):
+        """
+        Reboot a node.
+
+        Requires SSH access and will fail if ssh_username &
+        ssh_private_key_file were not set in the driver constructor.
+
+        @param  node: Node to be rebooted
+        @type   node: L{Node}
+
+        @return:  True if successful, False if not
+        @rtype:   C{bool}
+        """
+        ssh_username = self.ssh_username
+        if ssh_username is None:
+            return False
+        ssh_private_key = self.ssh_private_key_file
+        if ssh_private_key is None:
+            return False
+        if not paramiko:
+            return False
+        ssh_host = node.private_ips[0]
+        ssh_private_key_file = os.path.expanduser(ssh_private_key)
+        ssh_private_key_pass = ''
+
+        try:
+            pkey = paramiko.RSAKey.from_private_key_file(ssh_private_key_file,
+                                                         ssh_private_key_pass)
+        except paramiko.SSHException:
+            prompt = ('Enter passphrase for key \'' + ssh_private_key_file
+                      + '\': ')
+            ssh_private_key_pass = getpass.getpass(prompt=prompt)
+            pkey = paramiko.RSAKey.from_private_key_file(ssh_private_key_file,
+                                                         ssh_private_key_pass)
+
+        try:
+            ssh_client = self.SSHClient
+            ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+            ssh_client.connect(ssh_host, username=ssh_username, pkey=pkey)
+            ssh_client.exec_command('sudo reboot')
+            ssh_client.close()
+            return True
+
+        except:
+            return False
+
+    def ex_set_node_tags(self, node, tags):
+        """
+        Set the tags on a Node instance.
+
+        Note that this updates the node object directly.
+
+        @param  node: Node object
+        @type   node: L{Node}
+
+        @param  tags: List of tags to apply to the object
+        @type   tags: C{list} of C{str}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/zones/%s/instances/%s/setTags' % (node.extra['zone'].name,
+                                                      node.name)
+
+        tags_data = {}
+        tags_data['items'] = tags
+        tags_data['fingerprint'] = node.extra['tags_fingerprint']
+
+        response = self.connection.async_request(request, method='POST',
+                                                 data=tags_data).object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        else:
+            new_node = self.ex_get_node(node.name)
+            node.extra['tags'] = new_node.extra['tags']
+            node.extra['tags_fingerprint'] = new_node.extra['tags_fingerprint']
+            return True
+
+    def deploy_node(self, name, size, image, script, location=None,
+                    ex_network='default', ex_tags=None):
+        """
+        Create a new node and run a script on start-up.
+
+        @param  name: The name of the node to create.
+        @type   name: C{str}
+
+        @param  size: The machine type to use.
+        @type   size: C{str} or L{GCENodeSize}
+
+        @param  image: The image to use to create the node.
+        @type   image: C{str} or L{NodeImage}
+
+        @param  script: File path to start-up script
+        @type   script: C{str}
+
+        @keyword  location: The location (zone) to create the node in.
+        @type     location: C{str} or L{NodeLocation} or L{GCEZone} or C{None}
+
+        @keyword  ex_network: The network to associate with the node.
+        @type     ex_network: C{str} or L{GCENetwork}
+
+        @keyword  ex_tags: A list of tags to assiciate with the node.
+        @type     ex_tags: C{list} of C{str} or C{None}
+
+        @return:  A Node object for the new node.
+        @rtype:   L{Node}
+        """
+        metadata = {'items': [{'key': 'startup-script',
+                               'value': open(script).read()}]}
+
+        return self.create_node(name, size, image, location=location,
+                                ex_network=ex_network, ex_tags=ex_tags,
+                                ex_metadata=metadata)
+
+    def attach_volume(self, node, volume, device=None, ex_mode=None,
+                      ex_boot=False):
+        """
+        Attach a volume to a node.
+
+        If volume is None, a scratch disk will be created and attached.
+
+        @param  node: The node to attach the volume to
+        @type   node: L{Node}
+
+        @param  volume: The volume to attach. If none, a scratch disk will be
+                        attached.
+        @type   volume: L{StorageVolume} or C{None}
+
+        @keyword  device: The device name to attach the volume as. Defaults to
+                          volume name.
+        @type     device: C{str}
+
+        @keyword  ex_mode: Either 'READ_WRITE' or 'READ_ONLY'
+        @type     ex_mode: C{str}
+
+        @keyword  ex_boot: If true, disk will be attached as a boot disk
+        @type     ex_boot: C{bool}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        volume_data = {}
+        if volume is None:
+            volume_data['type'] = 'SCRATCH'
+        else:
+            volume_data['type'] = 'PERSISTENT'
+            volume_data['source'] = volume.extra['selfLink']
+        volume_data['kind'] = 'compute#attachedDisk'
+        volume_data['mode'] = ex_mode or 'READ_WRITE'
+
+        if device:
+            volume_data['deviceName'] = device
+        else:
+            volume_data['deviceName'] = volume.name
+
+        volume_data['boot'] = ex_boot
+
+        request = '/zones/%s/instances/%s/attachDisk' % (
+            node.extra['zone'].name, node.name)
+        response = self.connection.request(request, method='POST',
+                                           data=volume_data).object
+        if error in response:
+            self._cateforize_error(response['error'])
+        else:
+            return True
+
+    def detach_volume(self, volume, ex_node=None):
+        """
+        Detach a volume from a node.
+
+        @param  volume: Volume object to detach
+        @type   volume: L{StorageVolume}
+
+        @keyword  ex_node: Node object to detach volume from (required)
+        @type     ex_node: L{Node}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        if not ex_node:
+            return False
+        request = '/zones/%s/instances/%s/detachDisk?deviceName=%s' % (
+            ex_node.extra['zone'].name, ex_node.name, volume.name)
+
+        response = self.connection.async_request(request, method='POST',
+                                                 data='ignored').object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        else:
+            return True
+
+    def ex_destroy_address(self, address):
+        """
+        Destroy a static address.
+
+        @param  address: Address object to destroy
+        @type   address: L{GCEAddress}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/regions/%s/addresses/%s' % (address.region, address.name)
+
+        response = self.connection.async_request(request,
+                                                 method='DELETE').object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        else:
+            return True
+
+    def ex_destroy_firewall(self, firewall):
+        """
+        Destroy a firewall.
+
+        @param  firewall: Firewall object to destroy
+        @type   firewall: L{GCEFirewall}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/global/firewalls/%s' % firewall.name
+        response = self.connection.async_request(request,
+                                                 method='DELETE').object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        else:
+            return True
+
+    def ex_destroy_network(self, network):
+        """
+        Destroy a network.
+
+        @param  network: Network object to destroy
+        @type   network: L{GCENetwork}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/global/networks/%s' % network.name
+        response = self.connection.async_request(request,
+                                                 method='DELETE').object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        else:
+            return True
+
+    def destroy_node(self, node):
+        """
+        Destroy a node.
+
+        @param  node: Node object to destroy
+        @type   node: L{Node}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/zones/%s/instances/%s' % (node.extra['zone'].name,
+                                              node.name)
+        response = self.connection.async_request(request,
+                                                 method='DELETE').object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        else:
+            return True
+
+    def ex_destroy_multiple_nodes(self, nodelist, ignore_errors=True,
+                                  timeout=DEFAULT_TASK_COMPLETION_TIMEOUT):
+        """
+        Destroy multiple nodes at once.
+
+        @param  nodelist: List of nodes to destroy
+        @type   nodelist: C{list} of L{Node}
+
+        @keyword  ignore_errors: If true, don't raise an exception if one or
+                                 more nodes fails to be destroyed.
+        @type     ignore_errors: C{bool}
+
+        @keyword  timeout: Number of seconds to wait for all nodes to be
+                           destroyed.
+        @type     timeout: C{int}
+
+        @return:  A list of boolean values.  One for each node.  True means
+                  that the node was successfully destroyed.
+        @rtype:   C{list} of C{bool}
+        """
+        responses = []
+        success = [False] * len(nodelist)
+        complete = False
+        start_time = time.time()
+        for node in nodelist:
+            request = '/zones/%s/instances/%s' % (node.extra['zone'].name,
+                                                  node.name)
+            response = self.connection.request(request, method='DELETE').object
+            responses.append(response)
+
+        while not complete:
+            if (time.time() - start_time >= timeout):
+                raise Exception("Timeout (%s sec) while waiting to delete "
+                                "multiple instances")
+            time.sleep(3)
+            complete = True
+            for i, operation in enumerate(responses):
+                if operation is None:
+                    continue
+                response = self.connection.request(
+                    operation['selfLink']).object
+                if response['status'] == 'DONE':
+                    responses[i] = None
+                    if 'error' in response:
+                        if ignore_errors:
+                            success[i] = False
+                        else:
+                            self._categorize_error(response['error'])
+                    else:
+                        success[i] = True
+                else:
+                    complete = False
+        return success
+
+    def destroy_volume(self, volume):
+        """
+        Destroy a volume.
+
+        @param  volume: Volume object to destroy
+        @type   volume: L{StorageVolume}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/zones/%s/disks/%s' % (volume.extra['zone'].name,
+                                          volume.name)
+        response = self.connection.async_request(request,
+                                                 method='DELETE').object
+        if 'error' in response:
+            self._categorize_error(response['error'])
+        else:
+            return True
+
+    def ex_get_address(self, name, region=None):
+        """
+        Return an Address object based on an address name and optional region.
+
+        @param  name: The name of the address
+        @type   name: C{str}
+
+        @keyword  region: The region to search for the address in
+        @type     region: C{str} or C{None}
+
+        @return:  An Address object for the address
+        @rtype:   L{GCEAddress}
+        """
+        address_region = region or self._find_zone(name, 'addresses',
+                                                   region=True)
+        request = '/regions/%s/addresses/%s' % (address_region, name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_address(response)
+
+    def ex_get_firewall(self, name):
+        """
+        Return a Firewall object based on the firewall name.
+
+        @param  name: The name of the firewall
+        @type   name: C{str}
+
+        @return:  A GCEFirewall object
+        @rtype:   L{GCEFirewall}
+        """
+        request = '/global/firewalls/%s' % name
+        response = self.connection.request(request, method='GET').object
+        return self._to_firewall(response)
+
+    def ex_get_image(self, partial_name):
+        """
+        Return an NodeImage object based on the name or link provided.
+
+        @param  partial_name: The name, partial name, or full path of a GCE
+                              image.
+        @type   partial_name: C{str}
+
+        @return:  NodeImage object based on provided information
+        @rtype:   L{NodeImage}
+        """
+        if partial_name.startswith('https://'):
+            response = self.connection.request(partial_name, method='GET')
+            return self._to_node_image(response.object)
+        image = self._match_images(None, partial_name)
+        if not image:
+            if partial_name.startswith('debian'):
+                image = self._match_images('debian-cloud', partial_name)
+            elif partial_name.startswith('centos'):
+                image = self._match_images('centos-cloud', partial_name)
+
+        return image
+
+    def ex_get_network(self, name):
+        """
+        Return a Network object based on a network name.
+
+        @param  name: The name of the network
+        @type   name: C{str}
+
+        @return:  A Network object for the network
+        @rtype:   L{GCENetwork}
+        """
+        request = '/global/networks/%s' % name
+        response = self.connection.request(request, method='GET').object
+        return self._to_network(response)
+
+    def ex_get_node(self, name, zone=None):
+        """
+        Return a Node object based on a node name and optional zone.
+
+        @param  name: The name of the node
+        @type   name: C{str}
+
+        @keyword  zone: The zone to search for the node in
+        @type     zone: C{str} or L{GCEZone} or L{NodeLocation} or C{None}
+
+        @return:  A Node object for the node
+        @rtype:   L{Node}
+        """
+        zone = zone or self.zone or self._find_zone(name, 'instances')
+        if not hasattr(zone, 'name'):
+            zone = self.ex_get_zone(zone)
+        request = '/zones/%s/instances/%s' % (zone.name, name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_node(response)
+
+    def ex_get_project(self):
+        """
+        Return a Project object with project-wide information.
+
+        @return:  A GCEProject object
+        @rtype:   L{GCEProject}
+        """
+        response = self.connection.request('', method='GET').object
+        return self._to_project(response)
+
+    def ex_get_size(self, name, zone=None):
+        """
+        Return a size object based on a machine type name and zone.
+
+        @param  name: The name of the node
+        @type   name: C{str}
+
+        @keyword  zone: The zone to search for the machine type in
+        @type     zone: C{str} or L{GCEZone} or L{NodeLocation} or C{None}
+
+        @return:  A GCENodeSize object for the machine type
+        @rtype:   L{GCENodeSize}
+        """
+        zone = zone or self.zone
+        if not hasattr(zone, 'name'):
+            zone = self.ex_get_zone(zone)
+        request = '/zones/%s/machineTypes/%s' % (zone.name, name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_node_size(response)
+
+    def ex_get_volume(self, name, zone=None):
+        """
+        Return a Volume object based on a volume name and optional zone.
+
+        @param  name: The name of the volume
+        @type   name: C{str}
+
+        @keyword  zone: The zone to search for the volume in
+        @type     zone: C{str} or L{GCEZone} or L{NodeLocation} or C{None}
+
+        @return:  A StorageVolume object for the volume
+        @rtype:   L{StorageVolume}
+        """
+        zone = zone or self.zone or self.find_zone(name, 'disks')
+        if not hasattr(zone, 'name'):
+            zone = self.ex_get_zone(zone)
+        request = '/zones/%s/disks/%s' % (zone.name, name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_storage_volume(response)
+
+    def ex_get_zone(self, name):
+        """
+        Return a Zone object based on the zone name.
+
+        @param  name: The name of the zone.
+        @type   name: C{str}
+
+        @return:  A GCEZone object for the zone
+        @rtype:   L{GCEZone}
+        """
+        if name.startswith('https://'):
+            short_name = name.split('/')[-1]
+            request = name
+        else:
+            short_name = name
+            request = '/zones/%s' % name
+        # Check zone cache first
+        if short_name in self.zone_dict:
+            return self.zone_dict[short_name]
+        # Otherwise, look up zone information
+        response = self.connection.request(request, method='GET').object
+        return self._to_zone(response)
+
+    def _to_address(self, address):
+        """
+        Return an Address object from the json-response dictionary.
+
+        @param  address: The dictionary describing the address.
+        @type   address: C{dict}
+
+        @return: Address object
+        @rtype: L{GCEAddress}
+        """
+        extra = {}
+
+        extra['selfLink'] = address['selfLink']
+        extra['status'] = address['status']
+        extra['region'] = address['region']
+        extra['creationTimestamp'] = address['creationTimestamp']
+        region = address['region'].split('/')[-1]
+
+        return GCEAddress(id=address['id'], name=address['name'],
+                          address=address['address'],
+                          region=region, driver=self, extra=extra)
+
+    def _to_firewall(self, firewall):
+        """
+        Return a Firewall object from the json-response dictionary.
+
+        @param  firewall: The dictionary describing the firewall.
+        @type   firewall: C{dict}
+
+        @return: Firewall object
+        @rtype: L{GCEFirewall}
+        """
+        extra = {}
+        extra['selfLink'] = firewall['selfLink']
+        extra['creationTimestamp'] = firewall['creationTimestamp']
+        extra['description'] = firewall.get('description')
+        extra['network_name'] = firewall['network'].split('/')[-1]
+
+        network = self.ex_get_network(extra['network_name'])
+        source_ranges = firewall.get('sourceRanges')
+        source_tags = firewall.get('sourceTags')
+
+        return GCEFirewall(id=firewall['id'], name=firewall['name'],
+                           allowed=firewall['allowed'], network=network,
+                           source_ranges=source_ranges,
+                           source_tags=source_tags,
+                           driver=self, extra=extra)
+
+    def _to_network(self, network):
+        """
+        Return a Network object from the json-response dictionary.
+
+        @param  network: The dictionary describing the network.
+        @type   network: C{dict}
+
+        @return: Network object
+        @rtype: L{GCENetwork}
+        """
+        extra = {}
+
+        extra['selfLink'] = network['selfLink']
+        extra['gatewayIPv4'] = network['gatewayIPv4']
+        extra['description'] = network.get('description')
+        extra['creationTimestamp'] = network['creationTimestamp']
+
+        return GCENetwork(id=network['id'], name=network['name'],
+                          cidr=network['IPv4Range'],
+                          driver=self, extra=extra)
+
+    def _to_node_image(self, image):
+        """
+        Return an Image object from the json-response dictionary.
+
+        @param  image: The dictionary describing the image.
+        @type   image: C{dict}
+
+        @return: Image object
+        @rtype: L{NodeImage}
+        """
+        extra = {}
+        extra['preferredKernel'] = image['preferredKernel']
+        extra['description'] = image['description']
+        extra['creationTimestamp'] = image['creationTimestamp']
+        extra['selfLink'] = image['selfLink']
+        return NodeImage(id=image['id'], name=image['name'], driver=self,
+                         extra=extra)
+
+    def _to_node_location(self, location):
+        """
+        Return a Location object from the json-response dictionary.
+
+        @param  location: The dictionary describing the location.
+        @type   location: C{dict}
+
+        @return: Location object
+        @rtype: L{NodeLocation}
+        """
+        return NodeLocation(id=location['id'], name=location['name'],
+                            country=location['name'].split('-')[0],
+                            driver=self)
+
+    def _to_node(self, node):
+        """
+        Return a Node object from the json-response dictionary.
+
+        @param  node: The dictionary describing the node.
+        @type   node: C{dict}
+
+        @return: Node object
+        @rtype: L{Node}
+        """
+        public_ips = []
+        private_ips = []
+        extra = {}
+
+        extra['status'] = node['status']
+        extra['description'] = node.get('description')
+        extra['zone'] = self.ex_get_zone(node['zone'])
+        extra['image'] = node.get('image')
+        extra['disks'] = node['disks']
+        extra['networkInterfaces'] = node['networkInterfaces']
+        extra['id'] = node['id']
+        extra['selfLink'] = node['selfLink']
+        extra['name'] = node['name']
+        extra['metadata'] = node['metadata']
+        extra['tags_fingerprint'] = node['tags']['fingerprint']
+
+        if 'items' in node['tags']:
+            tags = node['tags']['items']
+        else:
+            tags = []
+        extra['tags'] = tags
+
+        for network_interface in node['networkInterfaces']:
+            private_ips.append(network_interface['networkIP'])
+            for access_config in network_interface['accessConfigs']:
+                public_ips.append(access_config['natIP'])
+
+        return Node(id=node['id'], name=node['name'],
+                    state=self.NODE_STATE_MAP[node['status']],
+                    public_ips=public_ips, private_ips=private_ips,
+                    driver=self, size=node['machineType'],
+                    image=node.get('image'), extra=extra)
+
+    def _to_node_size(self, machine_type):
+        """
+        Return a Size object from the json-response dictionary.
+
+        @param  machine_type: The dictionary describing the machine.
+        @type   machine_type: C{dict}
+
+        @return: Size object
+        @rtype: L{GCENodeSize}
+        """
+        extra = {}
+        extra['selfLink'] = machine_type['selfLink']
+        extra['zone'] = machine_type['zone']
+        extra['description'] = machine_type['description']
+        extra['guestCpus'] = machine_type['guestCpus']
+        extra['creationTimestamp'] = machine_type['creationTimestamp']
+        try:
+            price = self._get_size_price(size_id=machine_type['name'])
+        except KeyError:
+            price = None
+
+        return GCENodeSize(id=machine_type['id'], name=machine_type['name'],
+                           ram=machine_type['memoryMb'],
+                           disk=machine_type['imageSpaceGb'],
+                           bandwidth=0, price=price, driver=self, extra=extra)
+
+    def _to_project(self, project):
+        """
+        Return a Project object from the json-response dictionary.
+
+        @param  project: The dictionary describing the project.
+        @type   project: C{dict}
+
+        @return: Project object
+        @rtype: L{GCEProject}
+        """
+        extra = {}
+        extra['selfLink'] = project['selfLink']
+        extra['creationTimestamp'] = project['creationTimestamp']
+        extra['description'] = project['description']
+        metadata = project['commonInstanceMetadata'].get('items')
+
+        return GCEProject(id=project['id'], name=project['name'],
+                          metadata=metadata, quotas=project['quotas'],
+                          driver=self, extra=extra)
+
+    def _to_storage_volume(self, volume):
+        """
+        Return a Volume object from the json-response dictionary.
+
+        @param  volume: The dictionary describing the volume.
+        @type   volume: C{dict}
+
+        @return: Volume object
+        @rtype: L{StorageVolume}
+        """
+        extra = {}
+        extra['selfLink'] = volume['selfLink']
+        extra['zone'] = self.ex_get_zone(volume['zone'])
+        extra['status'] = volume['status']
+        extra['creationTimestamp'] = volume['creationTimestamp']
+
+        return StorageVolume(id=volume['id'], name=volume['name'],
+                             size=volume['sizeGb'], driver=self, extra=extra)
+
+    def _to_zone(self, zone):
+        """
+        Return a Zone object from the json-response dictionary.
+
+        @param  zone: The dictionary describing the zone.
+        @type   zone: C{dict}
+
+        @return: Zone object
+        @rtype: L{GCEZone}
+        """
+        extra = {}
+        extra['selfLink'] = zone['selfLink']
+        extra['creationTimestamp'] = zone['creationTimestamp']
+        extra['description'] = zone['description']
+
+        deprecated = zone.get('deprecated')
+
+        return GCEZone(id=zone['id'], name=zone['name'], status=zone['status'],
+                       maintenance_windows=zone['maintenanceWindows'],
+                       quotas=zone['quotas'], deprecated=deprecated,
+                       driver=self, extra=extra)
diff --git a/libcloud/compute/providers.py b/libcloud/compute/providers.py
index d4f7a11..fbc9c63 100644
--- a/libcloud/compute/providers.py
+++ b/libcloud/compute/providers.py
@@ -65,6 +65,8 @@ DRIVERS = {
         ('libcloud.compute.drivers.cloudsigma', 'CloudSigmaZrhNodeDriver'),
     Provider.CLOUDSIGMA_US:
         ('libcloud.compute.drivers.cloudsigma', 'CloudSigmaLvsNodeDriver'),
+    Provider.GCE:
+        ('libcloud.compute.drivers.gce', 'GCENodeDriver'),
     Provider.GOGRID:
         ('libcloud.compute.drivers.gogrid', 'GoGridNodeDriver'),
     Provider.RACKSPACE:
diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py
index 15cb266..c8e2fb4 100644
--- a/libcloud/compute/types.py
+++ b/libcloud/compute/types.py
@@ -46,6 +46,7 @@ class Provider(object):
     @cvar RACKSPACE: Rackspace next-gen OpenStack based Cloud Servers
     @cvar RACKSPACE_FIRST_GEN: Rackspace First Gen Cloud Servers
     @cvar SLICEHOST: Slicehost.com
+    @cvar GCE: Google Compute Engine
     @cvar GOGRID: GoGrid
     @cvar VPSNET: VPS.net
     @cvar LINODE: Linode.com
@@ -75,6 +76,7 @@ class Provider(object):
     EC2 = 'ec2_us_east'
     RACKSPACE = 'rackspace'
     SLICEHOST = 'slicehost'
+    GCE = 'gce'
     GOGRID = 'gogrid'
     VPSNET = 'vpsnet'
     LINODE = 'linode'
-- 
1.8.3


From 3af900d12d6df667bde7f3d76e4af89cf738af6a Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Mon, 24 Jun 2013 13:20:20 -0700
Subject: [PATCH 02/14] Address specific issues highlighted in github review.
 Includes: Using the with statement Using urlencode and urlparse from
 libcloud.utils.py3 Clean-up of nested for loops Some additional documentation
 Other minor fixes

---
 demos/secrets.py-dist           |   4 +-
 libcloud/common/google.py       |  31 +++++-----
 libcloud/compute/drivers/gce.py | 129 ++++++++++++++++------------------------
 3 files changed, 70 insertions(+), 94 deletions(-)

diff --git a/demos/secrets.py-dist b/demos/secrets.py-dist
index 2de4ba7..636d123 100644
--- a/demos/secrets.py-dist
+++ b/demos/secrets.py-dist
@@ -24,8 +24,8 @@ ECP_PARAMS = ('user_name', 'password')
 GANDI_PARAMS = ('user',)
 GCE_PARAMS = ('email_address', 'key') # Service Account Authentication
 #GCE_PARAMS = ('client_id', 'client_secret') # Installed App Authentication
-GCE_KEYWORD_PARAMS = ({'project': 'project_name', 'ssh_username': None,
-                       'ssh_private_key_file': None})
+GCE_KEYWORD_PARAMS = {'project': 'project_name', 'ssh_username': None,
+                      'ssh_private_key_file': None}
 HOSTINGCOM_PARAMS = ('user', 'secret')
 IBM_PARAMS = ('user', 'secret')
 # OPENSTACK_PARAMS = ('user_name', 'api_key', secure_bool, 'host', port_int)
diff --git a/libcloud/common/google.py b/libcloud/common/google.py
index 98ee641..38789a5 100644
--- a/libcloud/common/google.py
+++ b/libcloud/common/google.py
@@ -16,6 +16,8 @@
 """
 Module for Google Connection and Authentication classes.
 """
+from __future__ import with_statement
+
 try:
     import simplejson as json
 except ImportError:
@@ -28,10 +30,9 @@ import time
 import datetime
 import os
 import socket
-import urllib
-import urlparse
-
 
+from libcloud.utils.py3 import urlencode
+from libcloud.utils.py3 import urlparse
 from libcloud.common.base import (ConnectionUserAndKey, JsonResponse,
                                   PollingConnection)
 from libcloud.compute.types import (InvalidCredsError,
@@ -95,7 +96,7 @@ class GoogleBaseAuthConnection(ConnectionUserAndKey):
         @return:  A dictionary with updated token information
         @rtype:   C{dict}
         """
-        data = urllib.urlencode(request_body)
+        data = urlencode(request_body)
         now = datetime.datetime.utcnow()
         response = self.request('/o/oauth2/token', method='POST', data=data)
         token_info = response.object
@@ -157,7 +158,7 @@ class GoogleInstalledAppAuthConnection(GoogleBaseAuthConnection):
         if self.login_hint:
             auth_params['login_hint'] = self.login_hint
 
-        data = urllib.urlencode(auth_params)
+        data = urlencode(auth_params)
 
         url = 'https://%s%s?%s' % (self.host, self.auth_path, data)
         print('Please Go to the following URL and sign in:')
@@ -224,11 +225,12 @@ class GoogleServiceAcctAuthConnection(GoogleBaseAuthConnection):
         if SHA256 is None:
             raise GoogleAuthError('PyCrypto library required for '
                                   'Service Accout Authentication.')
+        # Check to see if 'key' is a file and read the file if it is.
         keypath = os.path.expanduser(key)
-        try:
-            key = open(keypath).read()
-        except IOError:
-            pass
+        is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+        if is_file_path:
+            with open(keypath, 'r') as f:
+                key = f.read()
         super(GoogleServiceAcctAuthConnection, self).__init__(
             user_id, key, *args, **kwargs)
 
@@ -403,10 +405,10 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
         """
         token_info = None
         filename = os.path.realpath(os.path.expanduser(self.credential_file))
+
         try:
-            f = open(filename)
-            data = f.read()
-            f.close()
+            with open(filename, 'r') as f:
+                data = f.read()
             token_info = json.loads(data)
         except IOError:
             pass
@@ -417,10 +419,9 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
         Write token_info to credential file.
         """
         filename = os.path.realpath(os.path.expanduser(self.credential_file))
-        f = open(filename, 'w')
         data = json.dumps(self.token_info)
-        f.write(data)
-        f.close()
+        with open(filename, 'w') as f:
+          f.write(data)
 
     def has_completed(self, response):
         """
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 1a15048..bba9793 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -19,7 +19,6 @@ Module for Google Compute Engine Driver.
 import datetime
 import time
 import sys
-import urlparse
 import os
 import getpass
 
@@ -238,6 +237,11 @@ class GCEZone(NodeLocation):
         Returns the next Maintenance Window.
 
         @return:  A dictionary containing maintenance window info
+                  The dictionary contains 4 keys with values of type C{str}
+                      - C{name}: The name of the maintence window
+                      - C{description}: Description of the maintenance window
+                      - C{beginTime}: RFC3339 Timestamp
+                      - C{endTime}: RFC3339 Timestamp
         @rtype:   C{dict}
         """
         begin = None
@@ -350,12 +354,10 @@ class GCENodeDriver(NodeDriver):
         """
         self.auth_type = auth_type
         self.project = project
+        if not self.project:
+            raise ValueError('Project name must be specified using '
+                             '"project" keyword.')
         super(GCENodeDriver, self).__init__(user_id, key, **kwargs)
-        if project:
-            self.project = project
-        else:
-            print "Please specify the project in your Driver's constructor"
-            sys.exit(1)
 
         # Cache Zone information to reduce API calls and increase speed
         self.base_path = '/compute/%s/projects/%s' % (API_VERSION,
@@ -415,13 +417,12 @@ class GCENodeDriver(NodeDriver):
         request = '/aggregated/%s' % res_type
         res_list = self.connection.request(request).object
         for k, v in res_list['items'].items():
-            if res_type in v:
-                for res in v[res_type]:
-                    if res['name'] == name:
-                        if region:
-                            return k.lstrip('regions/')
-                        else:
-                            return k.lstrip('zones/')
+            for res in v.get(res_type, []):
+                if res['name'] == name:
+                    if region:
+                        return k.lstrip('regions/')
+                    else:
+                        return k.lstrip('zones/')
 
     def _match_images(self, project, partial_name):
         """
@@ -485,17 +486,13 @@ class GCENodeDriver(NodeDriver):
         if 'items' in response:
             # The aggregated result returns dictionaries for each region
             if region is None:
-                for k, v in response['items'].items():
-                    if 'addresses' in v:
-                        for address in v['addresses']:
-                            address_obj = self._to_address(address)
-                            list_addresses.append(address_obj)
-
+                for v in response['items'].values():
+                    region_addresses = [self._to_address(a) for a in
+                                        v.get('addresses', [])]
+                    list_addresses.extend(region_addresses)
             else:
-                for address in response['items']:
-                    address_obj = self._to_address(address)
-                    list_addresses.append(address_obj)
-
+                list_addresses = [self._to_address(a) for a in
+                                  response['items']]
         return list_addresses
 
     def ex_list_firewalls(self):
@@ -508,10 +505,8 @@ class GCENodeDriver(NodeDriver):
         list_firewalls = []
         request = '/global/firewalls'
         response = self.connection.request(request, method='GET').object
-        if 'items' in response:
-            for firewall in response['items']:
-                list_firewalls.append(self._to_firewall(firewall))
-
+        list_firewalls = [self._to_firewall(f) for f in
+                          response.get('items', [])]
         return list_firewalls
 
     def list_images(self, ex_project=None):
@@ -527,22 +522,19 @@ class GCENodeDriver(NodeDriver):
         list_images = []
         request = '/global/images'
         if ex_project is None:
-            res = self.connection.request(request, method='GET').object
+            response = self.connection.request(request, method='GET').object
         else:
+            # Save the connection request_path
             save_request_path = self.connection.request_path
+            # Override the connection request path
             new_request_path = save_request_path.replace(self.project,
                                                          ex_project)
             self.connection.request_path = new_request_path
-            #new_base = self.base_path.replace(self.project, ex_project)
-            #uri = new_base + request
-            response = self.connection.request(request, method='GET')
-            res = response.object
+            response = self.connection.request(request, method='GET').object
+            # Restore the connection request_path
             self.connection.request_path = save_request_path
-
-        if 'items' in res:
-            for image in res['items']:
-                node_image = self._to_node_image(image)
-                list_images.append(node_image)
+        list_images = [self._to_node_image(i) for i in
+                       response.get('items', [])]
         return list_images
 
     def list_locations(self):
@@ -558,9 +550,7 @@ class GCENodeDriver(NodeDriver):
         list_locations = []
         request = '/zones'
         response = self.connection.request(request, method='GET').object
-        for location in response['items']:
-            list_locations.append(self._to_node_location(location))
-
+        list_locations = [self._to_node_location(l) for l in response['items']]
         return list_locations
 
     def ex_list_networks(self):
@@ -573,9 +563,8 @@ class GCENodeDriver(NodeDriver):
         list_networks = []
         request = '/global/networks'
         response = self.connection.request(request, method='GET').object
-        for network in response['items']:
-            list_networks.append(self._to_network(network))
-
+        list_networks = [self._to_network(n) for n in
+                         response.get('items', [])]
         return list_networks
 
     def list_nodes(self, ex_zone=None):
@@ -606,17 +595,12 @@ class GCENodeDriver(NodeDriver):
         if 'items' in response:
             # The aggregated response returns a dict for each zone
             if zone is None:
-                for k, v in response['items'].items():
-                    if 'instances' in v:
-                        for instance in v['instances']:
-                            node = self._to_node(instance)
-                            list_nodes.append(node)
-
+                for v in response['items'].values():
+                    zone_nodes = [self._to_node(i) for i in
+                                  v.get('instances', [])]
+                    list_nodes.extend(zone_nodes)
             else:
-                for instance in response['items']:
-                    node = self._to_node(instance)
-                    list_nodes.append(node)
-
+                list_nodes = [self._to_node(i) for i in response['items']]
         return list_nodes
 
     def list_sizes(self, location=None):
@@ -643,17 +627,12 @@ class GCENodeDriver(NodeDriver):
         if 'items' in response:
             # The aggregated response returns a dict for each zone
             if location is None:
-                for k, v in response['items'].items():
-                    if 'machineTypes' in v:
-                        for machine_type in v['machineTypes']:
-                            node_size = self._to_node_size(machine_type)
-                            list_sizes.append(node_size)
-
+                for v in response['items'].values():
+                    zone_sizes = [self._to_node_size(s) for s in
+                                  v.get('machineTypes', [])]
+                    list_sizes.extend(zone_sizes)
             else:
-                for machine_type in response['items']:
-                    node_size = self._to_node_size(machine_type)
-                    list_sizes.append(node_size)
-
+                list_sizes = [self._to_node_size(s) for s in response['items']]
         return list_sizes
 
     def ex_list_volumes(self, ex_zone=None):
@@ -684,17 +663,13 @@ class GCENodeDriver(NodeDriver):
         if 'items' in response:
             # The aggregated response returns a dict for each zone
             if zone is None:
-                for k, v in response['items'].items():
-                    if 'disks' in v:
-                        for disk in v['disks']:
-                            volume = self._to_storage_volume(disk)
-                            list_volumes.append(volume)
-
+                for v in response['items'].values():
+                    zone_volumes = [self._to_storage_volume(d) for d in
+                                    v.get('disks', [])]
+                    list_volumes.extend(zone_volumes)
             else:
-                for disk in response['items']:
-                    volume = self._to_storage_volume(disk)
-                    list_volumes.append(volume)
-
+                list_volumes = [self._to_storage_volume(d) for d in
+                                response['items']]
         return list_volumes
 
     def ex_list_zones(self):
@@ -707,9 +682,7 @@ class GCENodeDriver(NodeDriver):
         list_zones = []
         request = '/zones'
         response = self.connection.request(request, method='GET').object
-        for zone in response['items']:
-            list_zones.append(self._to_zone(zone))
-
+        list_zones = [self._to_zone(z) for z in response['items']]
         return list_zones
 
     def ex_create_address(self, name, region=None):
@@ -1228,8 +1201,10 @@ class GCENodeDriver(NodeDriver):
         @return:  A Node object for the new node.
         @rtype:   L{Node}
         """
+        with open(script, 'r') as f:
+            script_data = f.read()
         metadata = {'items': [{'key': 'startup-script',
-                               'value': open(script).read()}]}
+                               'value': script_data}]}
 
         return self.create_node(name, size, image, location=location,
                                 ex_network=ex_network, ex_tags=ex_tags,
-- 
1.8.3


From 9b5190d968b02a5dd603178f7de693cace38b10d Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 08:59:27 -0700
Subject: [PATCH 03/14] Fixes for handling socket error.

---
 libcloud/common/google.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/libcloud/common/google.py b/libcloud/common/google.py
index 38789a5..2939a23 100644
--- a/libcloud/common/google.py
+++ b/libcloud/common/google.py
@@ -388,11 +388,12 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
             try:
                 return super(GoogleBaseConnection, self).request(
                     *args, **kwargs)
-            except socket.error as e:
-                if e.errno == 104:
+            except socket.error:
+                e = sys.exc_info()[1]
+                if e.errno == errno.ECONNRESET:
                     tries = tries + 1
                 else:
-                    raise
+                    raise e
         # One more time, then give up.
         return super(GoogleBaseConnecion, self).request(*args, **kwargs)
 
-- 
1.8.3


From cb551ff5b52d44d136b7caa66516785199b62611 Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 09:12:28 -0700
Subject: [PATCH 04/14] Rewrite reboot_node. Google just added reboot support
 to the API, so use the API instead of SSH.

---
 libcloud/compute/drivers/gce.py | 64 +++++++----------------------------------
 1 file changed, 10 insertions(+), 54 deletions(-)

diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index bba9793..37f31da 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -15,6 +15,7 @@
 """
 Module for Google Compute Engine Driver.
 """
+from __future__ import with_statement
 
 import datetime
 import time
@@ -22,11 +23,6 @@ import sys
 import os
 import getpass
 
-try:
-    import paramiko
-except ImportError:
-    paramiko = None
-
 from libcloud.common.google import GoogleResponse
 from libcloud.common.google import GoogleBaseConnection
 
@@ -318,8 +314,7 @@ class GCENodeDriver(NodeDriver):
         "TERMINATED": NodeState.TERMINATED
     }
 
-    def __init__(self, user_id, key, datacenter=None,
-                 project=None, ssh_username=None, ssh_private_key_file=None,
+    def __init__(self, user_id, key, datacenter=None, project=None,
                  auth_type=None, **kwargs):
         """
         @param  user_id: The email address (for service accounts) or Client ID
@@ -338,14 +333,6 @@ class GCENodeDriver(NodeDriver):
         @keyword  project: Your GCE project name. (required)
         @type     project: C{str}
 
-        @keyword  ssh_username: SSH Username that has permission to do a
-                                password-less reboot of an instance with sudo.
-        @type     ssh_username: C{str}
-
-        @keyword  ssh_private_key_file: Name of file containing ssh key that
-                                        corresponds to ssh_username.
-        @type     ssh_private_key_file: C{str}
-
         @keyword  auth_type: Accepted values are "SA" or "IA"
                              ("Service Account" or "Installed Application").
                              If not supplied, auth_type will be guessed based
@@ -370,10 +357,6 @@ class GCENodeDriver(NodeDriver):
             self.zone = self.ex_get_zone(datacenter)
         else:
             self.zone = None
-        self.ssh_username = ssh_username
-        self.ssh_private_key_file = ssh_private_key_file
-        if paramiko:
-            self.SSHClient = paramiko.SSHClient()
 
     def _ex_connection_class_kwargs(self):
         return {'auth_type': self.auth_type,
@@ -1098,47 +1081,20 @@ class GCENodeDriver(NodeDriver):
         """
         Reboot a node.
 
-        Requires SSH access and will fail if ssh_username &
-        ssh_private_key_file were not set in the driver constructor.
-
         @param  node: Node to be rebooted
         @type   node: L{Node}
 
         @return:  True if successful, False if not
         @rtype:   C{bool}
         """
-        ssh_username = self.ssh_username
-        if ssh_username is None:
-            return False
-        ssh_private_key = self.ssh_private_key_file
-        if ssh_private_key is None:
-            return False
-        if not paramiko:
-            return False
-        ssh_host = node.private_ips[0]
-        ssh_private_key_file = os.path.expanduser(ssh_private_key)
-        ssh_private_key_pass = ''
-
-        try:
-            pkey = paramiko.RSAKey.from_private_key_file(ssh_private_key_file,
-                                                         ssh_private_key_pass)
-        except paramiko.SSHException:
-            prompt = ('Enter passphrase for key \'' + ssh_private_key_file
-                      + '\': ')
-            ssh_private_key_pass = getpass.getpass(prompt=prompt)
-            pkey = paramiko.RSAKey.from_private_key_file(ssh_private_key_file,
-                                                         ssh_private_key_pass)
-
-        try:
-            ssh_client = self.SSHClient
-            ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-            ssh_client.connect(ssh_host, username=ssh_username, pkey=pkey)
-            ssh_client.exec_command('sudo reboot')
-            ssh_client.close()
-            return True
-
-        except:
-            return False
+        request = '/zones/%s/instances/%s/reset' % (node.extra['zone'].name,
+                                                    node.name)
+        response = self.connection.async_request(request, method='POST',
+                                                 data='ignored').object
+        if 'error' in response:
+          self._categorize_error(response['error'])
+        else:
+          return True
 
     def ex_set_node_tags(self, node, tags):
         """
-- 
1.8.3


From 9c7b85936fe565af5716aa7451e8e0cdf029f540 Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 09:21:35 -0700
Subject: [PATCH 05/14] Various minor fixes that were discovered during
 unittest development.

---
 libcloud/compute/drivers/gce.py | 39 +++++++++++++++++++++++----------------
 1 file changed, 23 insertions(+), 16 deletions(-)

diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 37f31da..4f86ef7 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -47,13 +47,10 @@ def timestamp_to_datetime(timestamp):
     @rtype:   C{datetime}
     """
     ts = datetime.datetime.strptime(timestamp[:-6], '%Y-%m-%dT%H:%M:%S.%f')
-    if timestamp[-1] == 'Z':
-        return ts
-    else:
-        tz_hours = int(timestamp[-5:-3])
-        tz_mins = int(timestamp[-2:]) * int(timestamp[-6:-5] + '1')
-        tz_delta = datetime.timedelta(hours=tz_hours, minutes=tz_mins)
-        return ts + tz_delta
+    tz_hours = int(timestamp[-5:-3])
+    tz_mins = int(timestamp[-2:]) * int(timestamp[-6:-5] + '1')
+    tz_delta = datetime.timedelta(hours=tz_hours, minutes=tz_mins)
+    return ts + tz_delta
 
 
 class GCEError(LibcloudError):
@@ -228,6 +225,14 @@ class GCEZone(NodeLocation):
         super(GCEZone, self).__init__(id=str(id), name=name, country=country,
                                       driver=driver)
 
+    def _now(self):
+        """
+        Returns current UTC time.
+
+        Can be overridden in unittests.
+        """
+        return datetime.datetime.utcnow()
+
     def _get_next_maint(self):
         """
         Returns the next Maintenance Window.
@@ -259,7 +264,7 @@ class GCEZone(NodeLocation):
         @rtype:   C{datetime.timedelta}
         """
         next_window = self._get_next_maint()
-        now = datetime.datetime.utcnow()
+        now = self._now()
         next_begin = timestamp_to_datetime(next_window['beginTime'])
         return next_begin - now
 
@@ -403,9 +408,9 @@ class GCENodeDriver(NodeDriver):
             for res in v.get(res_type, []):
                 if res['name'] == name:
                     if region:
-                        return k.lstrip('regions/')
+                        return k.replace('regions/', '')
                     else:
-                        return k.lstrip('zones/')
+                        return k.replace('zones/', '')
 
     def _match_images(self, project, partial_name):
         """
@@ -598,6 +603,8 @@ class GCENodeDriver(NodeDriver):
         """
         list_sizes = []
         location = location or self.zone
+        if location == 'all':
+            location = None
         if location is None:
             request = '/aggregated/machineTypes'
         elif hasattr(location, 'name'):
@@ -975,7 +982,6 @@ class GCENodeDriver(NodeDriver):
             if (time.time() - start_time >= timeout):
                 raise Exception("Timeout (%s sec) while waiting for multiple "
                                 "instances")
-            time.sleep(3)
             complete = True
             for i, operation in enumerate(responses):
                 if operation is None:
@@ -995,6 +1001,7 @@ class GCENodeDriver(NodeDriver):
                         node_list[i] = self.ex_get_node(name, location.name)
                 else:
                     complete = False
+                    time.sleep(2)
         return node_list
 
     def create_volume(self, size, name, location=None, image=None,
@@ -1211,9 +1218,9 @@ class GCENodeDriver(NodeDriver):
 
         request = '/zones/%s/instances/%s/attachDisk' % (
             node.extra['zone'].name, node.name)
-        response = self.connection.request(request, method='POST',
-                                           data=volume_data).object
-        if error in response:
+        response = self.connection.async_request(request, method='POST',
+                                                 data=volume_data).object
+        if 'error' in response:
             self._cateforize_error(response['error'])
         else:
             return True
@@ -1351,7 +1358,6 @@ class GCENodeDriver(NodeDriver):
             if (time.time() - start_time >= timeout):
                 raise Exception("Timeout (%s sec) while waiting to delete "
                                 "multiple instances")
-            time.sleep(3)
             complete = True
             for i, operation in enumerate(responses):
                 if operation is None:
@@ -1369,6 +1375,7 @@ class GCENodeDriver(NodeDriver):
                         success[i] = True
                 else:
                     complete = False
+                    time.sleep(2)
         return success
 
     def destroy_volume(self, volume):
@@ -1709,7 +1716,7 @@ class GCENodeDriver(NodeDriver):
         """
         extra = {}
         extra['selfLink'] = machine_type['selfLink']
-        extra['zone'] = machine_type['zone']
+        extra['zone'] = self.ex_get_zone(machine_type['zone'])
         extra['description'] = machine_type['description']
         extra['guestCpus'] = machine_type['guestCpus']
         extra['creationTimestamp'] = machine_type['creationTimestamp']
-- 
1.8.3


From e93fe75e6c1f84c922741e4257b7058a6b28d864 Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 11:10:40 -0700
Subject: [PATCH 06/14] Update demo script with a couple extra features and
 make it importable. Also, update secrets.py-dist to remove the ssh keywords.

---
 demos/gce_demo.py     | 297 ++++++++++++++++++++++++++++----------------------
 demos/secrets.py-dist |   3 +-
 2 files changed, 170 insertions(+), 130 deletions(-)

diff --git a/demos/gce_demo.py b/demos/gce_demo.py
index c4fdb8e..afaa120 100755
--- a/demos/gce_demo.py
+++ b/demos/gce_demo.py
@@ -15,9 +15,23 @@
 # limitations under the License.
 
 
-# This example performs several tasks on Google Compute Engine.  It should be
-# run directly. This can also serve as an integration test for the GCE
-# Node Driver.
+# This example performs several tasks on Google Compute Engine.  It can be run
+# directly or can be imported into an interactive python session.  This can
+# also serve as an integration test for the GCE Node Driver.
+#
+# To run interactively:
+#    - Make sure you have valid values in secrets.py
+#    - Run 'python' in this directory, then:
+#        import gce_demo
+#        gce = gce_demo.get_gce_driver()
+#        gce.list_nodes()
+#        etc.
+#    - Or, to run the full demo from the interactive python shell:
+#        import gce_demo
+#        gce_demo.CLEANUP = False               # optional
+#        gce_demo.MAX_NODES = 4                 # optional
+#        gce_demo.DATACENTER = 'us-central1-a'  # optional
+#        gce_demo.main()
 
 import os.path
 import sys
@@ -52,12 +66,11 @@ CLEANUP = True
 args = getattr(secrets, 'GCE_PARAMS', ())
 kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
 
-gce = get_driver(Provider.GCE)(*args,
-                               datacenter=DATACENTER,
-                               **kwargs)
-
-
 # ==== HELPER FUNCTIONS ====
+def get_gce_driver():
+    driver = get_driver(Provider.GCE)(*args, datacenter=DATACENTER, **kwargs)
+    return driver
+
 def display(title, resource_list):
     """
     Display a list of resources.
@@ -114,123 +127,151 @@ def clean_up(base_name, node_list=None, resource_list=None):
 
 # ==== DEMO CODE STARTS HERE ====
 
-# Get project info and print name
-project = gce.ex_get_project()
-print('Project: %s' % project.name)
-
-# == Get Lists of Everything and Display the lists (up to 10) ==
-# These can either just return values for the current datacenter (zone)
-# or for everything.
-all_nodes = gce.list_nodes(ex_zone='all')
-display('Nodes', all_nodes)
-
-all_addresses = gce.ex_list_addresses(region='all')
-display('Addresses', all_addresses)
-
-all_volumes = gce.ex_list_volumes(ex_zone='all')
-display('Volumes', all_volumes)
-
-# This can return everything, but there is a large amount of overlap,
-# so we'll just get the sizes from the current zone.
-sizes = gce.list_sizes()
-display('Sizes', sizes)
-
-# These are global
-firewalls = gce.ex_list_firewalls()
-display('Firewalls', firewalls)
-
-networks = gce.ex_list_networks()
-display('Networks', networks)
-
-images = gce.list_images()
-display('Images', images)
-
-locations = gce.list_locations()
-display('Locations', locations)
-
-zones = gce.ex_list_zones()
-display('Zones', zones)
-
-# == Clean up any old demo resources ==
-print('Cleaning up any "%s" resources:' % DEMO_BASE_NAME)
-clean_up(DEMO_BASE_NAME, all_nodes,
-         all_addresses + all_volumes + firewalls + networks)
-
-# == Create Node with non-persistent disk ==
-if MAX_NODES > 1:
-    print('Creating Node with non-persistent disk:')
-    name = '%s-np-node' % DEMO_BASE_NAME
-    node_1 = gce.create_node(name, 'n1-standard-1', 'debian-7',
-                             ex_tags=['libcloud'])
-    print('   Node %s created' % name)
-
-# == Create Node with persistent disk ==
-print('Creating Node with Persistent disk:')
-name = '%s-persist-node' % DEMO_BASE_NAME
-# Use objects this time instead of names
-# Get latest Debian 7 image
-image = gce.ex_get_image('debian-7')
-# Get Machine Size
-size = gce.ex_get_size('n1-standard-1')
-# Create Disk.  Size is None to just take default of image
-volume_name = '%s-boot-disk' % DEMO_BASE_NAME
-volume = gce.create_volume(None, volume_name, image=image)
-# Create Node with Disk
-node_2 = gce.create_node(name, size, image, ex_tags=['libcloud'],
-                         ex_boot_disk=volume)
-print('   Node %s created with attached disk %s' % (node_2.name, volume.name))
-
-# == Create Multiple nodes at once ==
-base_name = '%s-muliple-nodes' % DEMO_BASE_NAME
-number = MAX_NODES - 2
-if number > 0:
-    print('Creating Multiple Nodes (%s):' % number)
-    multi_nodes = gce.ex_create_multiple_nodes(base_name, size, image, number,
-                                               ex_tags=['libcloud'])
-    for node in multi_nodes:
-        print('   Node %s created.' % node.name)
-
-# == Create a Network ==
-print('Creating Network:')
-name = '%s-network' % DEMO_BASE_NAME
-cidr = '10.10.0.0/16'
-network_1 = gce.ex_create_network(name, cidr)
-print('   Network %s created' % network_1.name)
-
-# == Create a Firewall ==
-print('Creating a Firewall:')
-name = '%s-firewall' % DEMO_BASE_NAME
-allowed = [{'IPProtocol': 'tcp',
-            'ports': ['3141']}]
-firewall_1 = gce.ex_create_firewall(name, allowed, network=network_1,
-                                    source_tags=['libcloud'])
-print('   Firewall %s created' % firewall_1.name)
-
-# == Create a Static Address ==
-print('Creating an Address:')
-name = '%s-address' % DEMO_BASE_NAME
-address_1 = gce.ex_create_address(name)
-print('   Address %s created with IP %s' % (address_1.name, address_1.address))
-
-# == List Updated Resources in current zone/region ==
-print('Updated Resources in current zone/region:')
-nodes = gce.list_nodes()
-display('Nodes', nodes)
-
-addresses = gce.ex_list_addresses()
-display('Addresses', addresses)
-
-volumes = gce.ex_list_volumes()
-display('Volumes', volumes)
-
-firewalls = gce.ex_list_firewalls()
-display('Firewalls', firewalls)
-
-networks = gce.ex_list_networks()
-display('Networks', networks)
-
-
-if CLEANUP:
-    print('Cleaning up %s resources created.' % DEMO_BASE_NAME)
-    clean_up(DEMO_BASE_NAME, nodes,
-             addresses + volumes + firewalls + networks)
+def main():
+    global gce
+    gce = get_gce_driver()
+    # Get project info and print name
+    project = gce.ex_get_project()
+    print('Project: %s' % project.name)
+
+    # == Get Lists of Everything and Display the lists (up to 10) ==
+    # These can either just return values for the current datacenter (zone)
+    # or for everything.
+    all_nodes = gce.list_nodes(ex_zone='all')
+    display('Nodes', all_nodes)
+
+    all_addresses = gce.ex_list_addresses(region='all')
+    display('Addresses', all_addresses)
+
+    all_volumes = gce.ex_list_volumes(ex_zone='all')
+    display('Volumes', all_volumes)
+
+    # This can return everything, but there is a large amount of overlap,
+    # so we'll just get the sizes from the current zone.
+    sizes = gce.list_sizes()
+    display('Sizes', sizes)
+
+    # These are global
+    firewalls = gce.ex_list_firewalls()
+    display('Firewalls', firewalls)
+
+    networks = gce.ex_list_networks()
+    display('Networks', networks)
+
+    images = gce.list_images()
+    display('Images', images)
+
+    locations = gce.list_locations()
+    display('Locations', locations)
+
+    zones = gce.ex_list_zones()
+    display('Zones', zones)
+
+    # == Clean up any old demo resources ==
+    print('Cleaning up any "%s" resources:' % DEMO_BASE_NAME)
+    clean_up(DEMO_BASE_NAME, all_nodes,
+             all_addresses + all_volumes + firewalls + networks)
+
+    # == Create Node with non-persistent disk ==
+    if MAX_NODES > 1:
+        print('Creating Node with non-persistent disk:')
+        name = '%s-np-node' % DEMO_BASE_NAME
+        node_1 = gce.create_node(name, 'n1-standard-1', 'debian-7',
+                                 ex_tags=['libcloud'])
+        print('   Node %s created' % name)
+
+        # == Create, and attach a disk ==
+        print('Creating a new disk:')
+        disk_name = '%s-attach-disk' % DEMO_BASE_NAME
+        volume = gce.create_volume(1, disk_name)
+        if volume.attach(node_1):
+          print ('   Attached %s to %s' % (volume.name, node_1.name))
+
+        if CLEANUP:
+            # == Detach the disk ==
+            if gce.detach_volume(volume, ex_node=node_1):
+                print('   Detached %s from %s' % (volume.name, node_1.name))
+
+
+    # == Create Node with persistent disk ==
+    print('Creating Node with Persistent disk:')
+    name = '%s-persist-node' % DEMO_BASE_NAME
+    # Use objects this time instead of names
+    # Get latest Debian 7 image
+    image = gce.ex_get_image('debian-7')
+    # Get Machine Size
+    size = gce.ex_get_size('n1-standard-1')
+    # Create Disk.  Size is None to just take default of image
+    volume_name = '%s-boot-disk' % DEMO_BASE_NAME
+    volume = gce.create_volume(None, volume_name, image=image)
+    # Create Node with Disk
+    node_2 = gce.create_node(name, size, image, ex_tags=['libcloud'],
+                             ex_boot_disk=volume)
+    print('   Node %s created with attached disk %s' % (node_2.name, volume.name))
+
+    # == Update Tags for Node ==
+    print('Updating Tags for %s' % node_2.name)
+    tags = node_2.extra['tags']
+    tags.append('newtag')
+    if gce.ex_set_node_tags(node_2, tags):
+        print('   Tags updated for %s' % node_2.name)
+    check_node = gce.ex_get_node(node_2.name)
+    print('   New tags: %s' % check_node.extra['tags'])
+
+    # == Create Multiple nodes at once ==
+    base_name = '%s-multiple-nodes' % DEMO_BASE_NAME
+    number = MAX_NODES - 2
+    if number > 0:
+        print('Creating Multiple Nodes (%s):' % number)
+        multi_nodes = gce.ex_create_multiple_nodes(base_name, size, image, number,
+                                                   ex_tags=['libcloud'])
+        for node in multi_nodes:
+            print('   Node %s created.' % node.name)
+
+    # == Create a Network ==
+    print('Creating Network:')
+    name = '%s-network' % DEMO_BASE_NAME
+    cidr = '10.10.0.0/16'
+    network_1 = gce.ex_create_network(name, cidr)
+    print('   Network %s created' % network_1.name)
+
+    # == Create a Firewall ==
+    print('Creating a Firewall:')
+    name = '%s-firewall' % DEMO_BASE_NAME
+    allowed = [{'IPProtocol': 'tcp',
+                'ports': ['3141']}]
+    firewall_1 = gce.ex_create_firewall(name, allowed, network=network_1,
+                                        source_tags=['libcloud'])
+    print('   Firewall %s created' % firewall_1.name)
+
+    # == Create a Static Address ==
+    print('Creating an Address:')
+    name = '%s-address' % DEMO_BASE_NAME
+    address_1 = gce.ex_create_address(name)
+    print('   Address %s created with IP %s' % (address_1.name, address_1.address))
+
+    # == List Updated Resources in current zone/region ==
+    print('Updated Resources in current zone/region:')
+    nodes = gce.list_nodes()
+    display('Nodes', nodes)
+
+    addresses = gce.ex_list_addresses()
+    display('Addresses', addresses)
+
+    volumes = gce.ex_list_volumes()
+    display('Volumes', volumes)
+
+    firewalls = gce.ex_list_firewalls()
+    display('Firewalls', firewalls)
+
+    networks = gce.ex_list_networks()
+    display('Networks', networks)
+
+
+    if CLEANUP:
+        print('Cleaning up %s resources created.' % DEMO_BASE_NAME)
+        clean_up(DEMO_BASE_NAME, nodes,
+                 addresses + volumes + firewalls + networks)
+
+if __name__ == '__main__':
+    main()
diff --git a/demos/secrets.py-dist b/demos/secrets.py-dist
index 636d123..fd48c76 100644
--- a/demos/secrets.py-dist
+++ b/demos/secrets.py-dist
@@ -24,8 +24,7 @@ ECP_PARAMS = ('user_name', 'password')
 GANDI_PARAMS = ('user',)
 GCE_PARAMS = ('email_address', 'key') # Service Account Authentication
 #GCE_PARAMS = ('client_id', 'client_secret') # Installed App Authentication
-GCE_KEYWORD_PARAMS = {'project': 'project_name', 'ssh_username': None,
-                      'ssh_private_key_file': None}
+GCE_KEYWORD_PARAMS = {'project': 'project_name'}
 HOSTINGCOM_PARAMS = ('user', 'secret')
 IBM_PARAMS = ('user', 'secret')
 # OPENSTACK_PARAMS = ('user_name', 'api_key', secure_bool, 'host', port_int)
-- 
1.8.3


From aed9684bdcdbe3584fbff5ddde7483e4f17b8d82 Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 15:43:48 -0700
Subject: [PATCH 07/14] Fixtures for Google Compute Engine Tests

---
 .../compute/fixtures/gce/aggregated_addresses.json |   71 +
 .../compute/fixtures/gce/aggregated_disks.json     |   81 +
 .../compute/fixtures/gce/aggregated_instances.json |  414 +++++
 .../fixtures/gce/aggregated_machineTypes.json      | 1683 ++++++++++++++++++++
 .../compute/fixtures/gce/global_firewalls.json     |   88 +
 .../fixtures/gce/global_firewalls_lcfirewall.json  |   19 +
 .../gce/global_firewalls_lcfirewall_delete.json    |   14 +
 .../gce/global_firewalls_lcfirewall_put.json       |   14 +
 .../fixtures/gce/global_firewalls_post.json        |   13 +
 .../test/compute/fixtures/gce/global_images.json   |   22 +
 .../compute/fixtures/gce/global_images.json.save   |   22 +
 .../test/compute/fixtures/gce/global_networks.json |   34 +
 .../fixtures/gce/global_networks_default.json      |    9 +
 .../fixtures/gce/global_networks_lcnetwork.json    |    9 +
 .../gce/global_networks_lcnetwork_delete.json      |   14 +
 ...obal_networks_libcloud-demo-europe-network.json |    9 +
 .../gce/global_networks_libcloud-demo-network.json |    9 +
 .../compute/fixtures/gce/global_networks_post.json |   13 +
 ...eration_global_firewalls_lcfirewall_delete.json |   15 +
 ..._operation_global_firewalls_lcfirewall_put.json |   15 +
 ...operations_operation_global_firewalls_post.json |   15 +
 ...operation_global_networks_lcnetwork_delete.json |   15 +
 .../operations_operation_global_networks_post.json |   15 +
 ...ons_us-central1_addresses_lcaddress_delete.json |   15 +
 ...eration_regions_us-central1_addresses_post.json |   15 +
 ...ration_zones_europe-west1-a_instances_post.json |   25 +
 ...on_zones_us-central1-a_disks_lcdisk_delete.json |   15 +
 ...s_operation_zones_us-central1-a_disks_post.json |   16 +
 ..._us-central1-a_instances_lcnode-000_delete.json |   16 +
 ..._us-central1-a_instances_lcnode-001_delete.json |   16 +
 ...ral1-a_instances_node-name_attachDisk_post.json |   16 +
 ...s_us-central1-a_instances_node-name_delete.json |   16 +
 ...ral1-a_instances_node-name_detachDisk_post.json |   16 +
 ...-central1-a_instances_node-name_reset_post.json |   15 +
 ...entral1-a_instances_node-name_setTags_post.json |   16 +
 ...eration_zones_us-central1-a_instances_post.json |   16 +
 libcloud/test/compute/fixtures/gce/project.json    |   74 +
 .../gce/projects_debian-cloud_global_images.json   |  157 ++
 .../gce/regions_us-central1_addresses.json         |   29 +
 .../regions_us-central1_addresses_lcaddress.json   |   11 +
 ...ons_us-central1_addresses_lcaddress_delete.json |   15 +
 .../gce/regions_us-central1_addresses_post.json    |   14 +
 libcloud/test/compute/fixtures/gce/zones.json      |  207 +++
 .../gce/zones_europe-west1-a_instances.json        |  145 ++
 .../gce/zones_europe-west1-a_instances_post.json   |   15 +
 ..._europe-west1-a_machineTypes_n1-standard-1.json |   14 +
 .../compute/fixtures/gce/zones_us-central1-a.json  |   40 +
 .../fixtures/gce/zones_us-central1-a_disks.json    |   37 +
 .../gce/zones_us-central1-a_disks_lcdisk.json      |   10 +
 .../zones_us-central1-a_disks_lcdisk_delete.json   |   15 +
 .../gce/zones_us-central1-a_disks_post.json        |   14 +
 .../gce/zones_us-central1-a_instances.json         |  232 +++
 .../zones_us-central1-a_instances_lcnode-000.json  |   42 +
 ..._us-central1-a_instances_lcnode-000_delete.json |   15 +
 .../zones_us-central1-a_instances_lcnode-001.json  |   42 +
 ..._us-central1-a_instances_lcnode-001_delete.json |   15 +
 .../zones_us-central1-a_instances_node-name.json   |   42 +
 ...ral1-a_instances_node-name_attachDisk_post.json |   15 +
 ...s_us-central1-a_instances_node-name_delete.json |   15 +
 ...ral1-a_instances_node-name_detachDisk_post.json |   15 +
 ...-central1-a_instances_node-name_reset_post.json |   15 +
 ...entral1-a_instances_node-name_setTags_post.json |   15 +
 .../gce/zones_us-central1-a_instances_post.json    |   14 +
 .../gce/zones_us-central1-a_machineTypes.json      |  374 +++++
 ...s_us-central1-a_machineTypes_n1-standard-1.json |   14 +
 65 files changed, 4488 insertions(+)
 create mode 100644 libcloud/test/compute/fixtures/gce/aggregated_addresses.json
 create mode 100644 libcloud/test/compute/fixtures/gce/aggregated_disks.json
 create mode 100644 libcloud/test/compute/fixtures/gce/aggregated_instances.json
 create mode 100644 libcloud/test/compute/fixtures/gce/aggregated_machineTypes.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_firewalls.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall_put.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_firewalls_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_images.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_images.json.save
 create mode 100644 libcloud/test/compute/fixtures/gce/global_networks.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_networks_default.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_networks_lcnetwork.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_networks_lcnetwork_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_networks_libcloud-demo-europe-network.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_networks_libcloud-demo-network.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_networks_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_lcfirewall_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_lcfirewall_put.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_global_networks_lcnetwork_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_global_networks_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_addresses_lcaddress_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_addresses_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_europe-west1-a_instances_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_disks_lcdisk_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_disks_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_lcnode-000_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_lcnode-001_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_attachDisk_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_detachDisk_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_reset_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_setTags_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/project.json
 create mode 100644 libcloud/test/compute/fixtures/gce/projects_debian-cloud_global_images.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_addresses.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_lcaddress.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_lcaddress_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_europe-west1-a_instances.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_europe-west1-a_instances_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_europe-west1-a_machineTypes_n1-standard-1.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_lcdisk.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_lcdisk_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-000.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-000_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-001.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-001_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_attachDisk_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_detachDisk_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_reset_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_setTags_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_machineTypes.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-a_machineTypes_n1-standard-1.json

diff --git a/libcloud/test/compute/fixtures/gce/aggregated_addresses.json b/libcloud/test/compute/fixtures/gce/aggregated_addresses.json
new file mode 100644
index 0000000..6ac222e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/aggregated_addresses.json
@@ -0,0 +1,71 @@
+{
+  "id": "projects/project_name/aggregated/addresses",
+  "items": {
+    "regions/europe-west1": {
+      "addresses": [
+        {
+          "address": "192.158.29.247",
+          "creationTimestamp": "2013-06-26T09:51:47.506-07:00",
+          "description": "",
+          "id": "10955781597205896134",
+          "kind": "compute#address",
+          "name": "libcloud-demo-europe-address",
+          "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/europe-west1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/europe-west1/addresses/libcloud-demo-europe-address",
+          "status": "RESERVED"
+        }
+      ]
+    },
+    "regions/us-central1": {
+      "addresses": [
+        {
+          "address": "173.255.113.20",
+          "creationTimestamp": "2013-06-26T12:21:40.625-07:00",
+          "description": "",
+          "id": "01531551729918243104",
+          "kind": "compute#address",
+          "name": "lcaddress",
+          "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/lcaddress",
+          "status": "RESERVED"
+        },
+        {
+          "address": "108.59.82.4",
+          "creationTimestamp": "2013-06-26T09:48:31.184-07:00",
+          "description": "",
+          "id": "17634862894218443422",
+          "kind": "compute#address",
+          "name": "libcloud-demo-address",
+          "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/libcloud-demo-address",
+          "status": "RESERVED"
+        },
+        {
+          "address": "173.255.114.104",
+          "creationTimestamp": "2013-06-04T16:28:43.764-07:00",
+          "description": "",
+          "id": "11879548153827627972",
+          "kind": "compute#address",
+          "name": "testaddress",
+          "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/testaddress",
+          "status": "RESERVED"
+        }
+      ]
+    },
+    "regions/us-central2": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "regions/us-central2"
+          }
+        ],
+        "message": "There are no results for scope 'regions/us-central2' on this page."
+      }
+    }
+  },
+  "kind": "compute#addressAggregatedList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/aggregated/addresses"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/aggregated_disks.json b/libcloud/test/compute/fixtures/gce/aggregated_disks.json
new file mode 100644
index 0000000..1190ab4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/aggregated_disks.json
@@ -0,0 +1,81 @@
+{
+  "id": "projects/project_name/aggregated/disks",
+  "items": {
+    "zones/europe-west1-a": {
+      "disks": [
+        {
+          "creationTimestamp": "2013-06-26T09:50:22.508-07:00",
+          "id": "0811494794539478718",
+          "kind": "compute#disk",
+          "name": "libcloud-demo-europe-boot-disk",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/disks/libcloud-demo-europe-boot-disk",
+          "sizeGb": "10",
+          "status": "READY",
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+        }
+      ]
+    },
+    "zones/europe-west1-b": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "zones/europe-west1-b"
+          }
+        ],
+        "message": "There are no results for scope 'zones/europe-west1-b' on this page."
+      }
+    },
+    "zones/us-central1-a": {
+      "disks": [
+        {
+          "creationTimestamp": "2013-06-25T10:57:34.305-07:00",
+          "id": "14383387450728762434",
+          "kind": "compute#disk",
+          "name": "test-disk",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/test-disk",
+          "sizeGb": "10",
+          "status": "READY",
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+        },
+        {
+          "creationTimestamp": "2013-06-26T09:47:09.178-07:00",
+          "id": "10880026303683859871",
+          "kind": "compute#disk",
+          "name": "libcloud-demo-boot-disk",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/libcloud-demo-boot-disk",
+          "sizeGb": "10",
+          "status": "READY",
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+        }
+      ]
+    },
+    "zones/us-central1-b": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "zones/us-central1-b"
+          }
+        ],
+        "message": "There are no results for scope 'zones/us-central1-b' on this page."
+      }
+    },
+    "zones/us-central2-a": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "zones/us-central2-a"
+          }
+        ],
+        "message": "There are no results for scope 'zones/us-central2-a' on this page."
+      }
+    }
+  },
+  "kind": "compute#diskAggregatedList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/aggregated/disks"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/aggregated_instances.json b/libcloud/test/compute/fixtures/gce/aggregated_instances.json
new file mode 100644
index 0000000..3e72aff
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/aggregated_instances.json
@@ -0,0 +1,414 @@
+{
+  "id": "projects/project_name/aggregated/instances",
+  "items": {
+    "zones/europe-west1-a": {
+      "instances": [
+        {
+          "canIpForward": false,
+          "creationTimestamp": "2013-06-26T15:13:38.295-07:00",
+          "disks": [
+            {
+              "index": 0,
+              "kind": "compute#attachedDisk",
+              "mode": "READ_WRITE",
+              "type": "SCRATCH"
+            }
+          ],
+          "id": "4658881585544531189",
+          "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+          "kind": "compute#instance",
+          "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1",
+          "metadata": {
+            "fingerprint": "42WmSpB8rSM=",
+            "kind": "compute#metadata"
+          },
+          "name": "libcloud-demo-europe-multiple-nodes-000",
+          "networkInterfaces": [
+            {
+              "accessConfigs": [
+                {
+                  "kind": "compute#accessConfig",
+                  "name": "External NAT",
+                  "natIP": "192.158.29.167",
+                  "type": "ONE_TO_ONE_NAT"
+                }
+              ],
+              "name": "nic0",
+              "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+              "networkIP": "10.240.144.78"
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-multiple-nodes-000",
+          "status": "RUNNING",
+          "tags": {
+            "fingerprint": "W7t6ZyTyIrc=",
+            "items": [
+              "libcloud"
+            ]
+          },
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+        },
+        {
+          "canIpForward": false,
+          "creationTimestamp": "2013-06-26T15:13:21.549-07:00",
+          "disks": [
+            {
+              "boot": true,
+              "deviceName": "libcloud-demo-europe-boot-disk",
+              "index": 0,
+              "kind": "compute#attachedDisk",
+              "mode": "READ_WRITE",
+              "source": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/disks/libcloud-demo-europe-boot-disk",
+              "type": "PERSISTENT"
+            }
+          ],
+          "id": "0681789716029574243",
+          "kernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130603",
+          "kind": "compute#instance",
+          "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1",
+          "metadata": {
+            "fingerprint": "42WmSpB8rSM=",
+            "kind": "compute#metadata"
+          },
+          "name": "libcloud-demo-europe-persist-node",
+          "networkInterfaces": [
+            {
+              "accessConfigs": [
+                {
+                  "kind": "compute#accessConfig",
+                  "name": "External NAT",
+                  "natIP": "192.158.29.121",
+                  "type": "ONE_TO_ONE_NAT"
+                }
+              ],
+              "name": "nic0",
+              "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+              "networkIP": "10.240.206.91"
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-persist-node",
+          "status": "RUNNING",
+          "tags": {
+            "fingerprint": "W7t6ZyTyIrc=",
+            "items": [
+              "libcloud"
+            ]
+          },
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+        },
+        {
+          "canIpForward": false,
+          "creationTimestamp": "2013-06-26T15:12:29.726-07:00",
+          "disks": [
+            {
+              "index": 0,
+              "kind": "compute#attachedDisk",
+              "mode": "READ_WRITE",
+              "type": "SCRATCH"
+            }
+          ],
+          "id": "14308265828754333159",
+          "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+          "kind": "compute#instance",
+          "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1",
+          "metadata": {
+            "fingerprint": "42WmSpB8rSM=",
+            "kind": "compute#metadata"
+          },
+          "name": "libcloud-demo-europe-np-node",
+          "networkInterfaces": [
+            {
+              "accessConfigs": [
+                {
+                  "kind": "compute#accessConfig",
+                  "name": "External NAT",
+                  "natIP": "192.158.29.88",
+                  "type": "ONE_TO_ONE_NAT"
+                }
+              ],
+              "name": "nic0",
+              "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+              "networkIP": "10.240.66.77"
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-np-node",
+          "status": "RUNNING",
+          "tags": {
+            "fingerprint": "W7t6ZyTyIrc=",
+            "items": [
+              "libcloud"
+            ]
+          },
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+        }
+      ]
+    },
+    "zones/europe-west1-b": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "zones/europe-west1-b"
+          }
+        ],
+        "message": "There are no results for scope 'zones/europe-west1-b' on this page."
+      }
+    },
+    "zones/us-central1-a": {
+      "instances": [
+        {
+          "canIpForward": false,
+          "creationTimestamp": "2013-06-26T15:11:02.386-07:00",
+          "disks": [
+            {
+              "boot": true,
+              "deviceName": "libcloud-demo-boot-disk",
+              "index": 0,
+              "kind": "compute#attachedDisk",
+              "mode": "READ_WRITE",
+              "source": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/libcloud-demo-boot-disk",
+              "type": "PERSISTENT"
+            }
+          ],
+          "id": "2378270030714524465",
+          "kernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130603",
+          "kind": "compute#instance",
+          "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+          "metadata": {
+            "fingerprint": "42WmSpB8rSM=",
+            "kind": "compute#metadata"
+          },
+          "name": "libcloud-demo-persist-node",
+          "networkInterfaces": [
+            {
+              "accessConfigs": [
+                {
+                  "kind": "compute#accessConfig",
+                  "name": "External NAT",
+                  "natIP": "108.59.81.66",
+                  "type": "ONE_TO_ONE_NAT"
+                }
+              ],
+              "name": "nic0",
+              "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+              "networkIP": "10.240.192.190"
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/libcloud-demo-persist-node",
+          "status": "RUNNING",
+          "tags": {
+            "fingerprint": "W7t6ZyTyIrc=",
+            "items": [
+              "libcloud"
+            ]
+          },
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+        },
+        {
+          "canIpForward": false,
+          "creationTimestamp": "2013-06-26T15:11:19.247-07:00",
+          "disks": [
+            {
+              "index": 0,
+              "kind": "compute#attachedDisk",
+              "mode": "READ_WRITE",
+              "type": "SCRATCH"
+            }
+          ],
+          "id": "8573880455005118258",
+          "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+          "kind": "compute#instance",
+          "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+          "metadata": {
+            "fingerprint": "42WmSpB8rSM=",
+            "kind": "compute#metadata"
+          },
+          "name": "libcloud-demo-multiple-nodes-000",
+          "networkInterfaces": [
+            {
+              "accessConfigs": [
+                {
+                  "kind": "compute#accessConfig",
+                  "name": "External NAT",
+                  "natIP": "108.59.81.107",
+                  "type": "ONE_TO_ONE_NAT"
+                }
+              ],
+              "name": "nic0",
+              "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+              "networkIP": "10.240.224.165"
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/libcloud-demo-multiple-nodes-000",
+          "status": "RUNNING",
+          "tags": {
+            "fingerprint": "W7t6ZyTyIrc=",
+            "items": [
+              "libcloud"
+            ]
+          },
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+        },
+        {
+          "canIpForward": false,
+          "creationTimestamp": "2013-06-26T15:00:12.021-07:00",
+          "disks": [
+            {
+              "index": 0,
+              "kind": "compute#attachedDisk",
+              "mode": "READ_WRITE",
+              "type": "SCRATCH"
+            }
+          ],
+          "id": "1845312225624811608",
+          "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+          "kind": "compute#instance",
+          "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+          "metadata": {
+            "fingerprint": "42WmSpB8rSM=",
+            "kind": "compute#metadata"
+          },
+          "name": "node-name",
+          "networkInterfaces": [
+            {
+              "accessConfigs": [
+                {
+                  "kind": "compute#accessConfig",
+                  "name": "External NAT",
+                  "natIP": "173.255.115.146",
+                  "type": "ONE_TO_ONE_NAT"
+                }
+              ],
+              "name": "nic0",
+              "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+              "networkIP": "10.240.113.94"
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+          "status": "RUNNING",
+          "tags": {
+            "fingerprint": "42WmSpB8rSM="
+          },
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+        },
+        {
+          "canIpForward": false,
+          "creationTimestamp": "2013-06-26T15:10:09.700-07:00",
+          "disks": [
+            {
+              "index": 0,
+              "kind": "compute#attachedDisk",
+              "mode": "READ_WRITE",
+              "type": "SCRATCH"
+            }
+          ],
+          "id": "03138438763739542377",
+          "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+          "kind": "compute#instance",
+          "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+          "metadata": {
+            "fingerprint": "42WmSpB8rSM=",
+            "kind": "compute#metadata"
+          },
+          "name": "libcloud-demo-np-node",
+          "networkInterfaces": [
+            {
+              "accessConfigs": [
+                {
+                  "kind": "compute#accessConfig",
+                  "name": "External NAT",
+                  "natIP": "108.59.80.244",
+                  "type": "ONE_TO_ONE_NAT"
+                }
+              ],
+              "name": "nic0",
+              "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+              "networkIP": "10.240.147.18"
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/libcloud-demo-np-node",
+          "status": "RUNNING",
+          "tags": {
+            "fingerprint": "W7t6ZyTyIrc=",
+            "items": [
+              "libcloud"
+            ]
+          },
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+        },
+        {
+          "canIpForward": false,
+          "creationTimestamp": "2013-06-26T15:11:19.662-07:00",
+          "disks": [
+            {
+              "index": 0,
+              "kind": "compute#attachedDisk",
+              "mode": "READ_WRITE",
+              "type": "SCRATCH"
+            }
+          ],
+          "id": "17221721898919682654",
+          "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+          "kind": "compute#instance",
+          "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+          "metadata": {
+            "fingerprint": "42WmSpB8rSM=",
+            "kind": "compute#metadata"
+          },
+          "name": "libcloud-demo-multiple-nodes-001",
+          "networkInterfaces": [
+            {
+              "accessConfigs": [
+                {
+                  "kind": "compute#accessConfig",
+                  "name": "External NAT",
+                  "natIP": "108.59.81.166",
+                  "type": "ONE_TO_ONE_NAT"
+                }
+              ],
+              "name": "nic0",
+              "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+              "networkIP": "10.240.223.109"
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/libcloud-demo-multiple-nodes-001",
+          "status": "RUNNING",
+          "tags": {
+            "fingerprint": "W7t6ZyTyIrc=",
+            "items": [
+              "libcloud"
+            ]
+          },
+          "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+        }
+      ]
+    },
+    "zones/us-central1-b": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "zones/us-central1-b"
+          }
+        ],
+        "message": "There are no results for scope 'zones/us-central1-b' on this page."
+      }
+    },
+    "zones/us-central2-a": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "zones/us-central2-a"
+          }
+        ],
+        "message": "There are no results for scope 'zones/us-central2-a' on this page."
+      }
+    }
+  },
+  "kind": "compute#instanceAggregatedList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/aggregated/instances"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/aggregated_machineTypes.json b/libcloud/test/compute/fixtures/gce/aggregated_machineTypes.json
new file mode 100644
index 0000000..54f8e23
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/aggregated_machineTypes.json
@@ -0,0 +1,1683 @@
+{
+  "id": "projects/project_name/aggregated/machineTypes",
+  "items": {
+    "zones/europe-west1-a": {
+      "machineTypes": [
+        {
+          "creationTimestamp": "2012-11-16T11:40:59.630-08:00",
+          "description": "2 vCPUs, 13 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "00770157291441082211",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highmem-2-d",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:19.448-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "06313284160910191442",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-2-d",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:46:10.572-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM",
+          "guestCpus": 2,
+          "id": "16898271314080235997",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highcpu-2",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:51:04.549-08:00",
+          "description": "8 vCPUS, 7.2 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "02507333096579477005",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highcpu-8-d",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:40.050-07:00",
+          "description": "4 vCPUs, 15 GB RAM",
+          "guestCpus": 4,
+          "id": "09494636486174545828",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-4",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:56.867-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM",
+          "guestCpus": 2,
+          "id": "17936898073622676356",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-2",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:50:15.128-08:00",
+          "description": "8 vCPUs, 7.2 GB RAM",
+          "guestCpus": 8,
+          "id": "01206886442411821831",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highcpu-8",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:44:25.985-08:00",
+          "description": "8 vCPUs, 52 GB RAM",
+          "guestCpus": 8,
+          "id": "01717932668777642040",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highmem-8",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:42:08.983-08:00",
+          "description": "4 vCPUs, 26 GB RAM",
+          "guestCpus": 4,
+          "id": "11556032176405786676",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highmem-4",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:14.670-07:00",
+          "description": "1 vCPU, 3.75 GB RAM",
+          "guestCpus": 1,
+          "id": "11077240422128681563",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:06.129-08:00",
+          "description": "2 vCPUs, 13 GB RAM",
+          "guestCpus": 2,
+          "id": "05438694236916301519",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highmem-2",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:43:17.400-08:00",
+          "description": "4 vCPUs, 26 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "05095504563332567951",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highmem-4-d",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:48:06.087-08:00",
+          "description": "4 vCPUs, 3.6 GB RAM",
+          "guestCpus": 4,
+          "id": "04759000181765218034",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highcpu-4",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:34.258-07:00",
+          "description": "1 vCPU, 3.75 GB RAM, 1 scratch disk (420 GB)",
+          "guestCpus": 1,
+          "id": "10583029372018866711",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1-d",
+          "scratchDisks": [
+            {
+              "diskGb": 420
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1-d",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:45:08.195-08:00",
+          "description": "8 vCPUs, 52 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "07181827135536388552",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highmem-8-d",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:47:07.825-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "15178384466070744001",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highcpu-2-d",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:50:05.677-07:00",
+          "description": "4 vCPUs, 15 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "00523085164784013586",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-4-d",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:45.550-07:00",
+          "description": "1 vCPU (shared physical core) and 1.7 GB RAM",
+          "guestCpus": 1,
+          "id": "1500265464823777597",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 1740,
+          "name": "g1-small",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/g1-small",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:49.088-07:00",
+          "description": "1 vCPU (shared physical core) and 0.6 GB RAM",
+          "guestCpus": 1,
+          "id": "1133568312750571513",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 614,
+          "name": "f1-micro",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/f1-micro",
+          "zone": "europe-west1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:49:07.563-08:00",
+          "description": "4 vCPUS, 3.6 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "01151097524490134507",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-highcpu-4-d",
+          "zone": "europe-west1-a"
+        }
+      ]
+    },
+    "zones/europe-west1-b": {
+      "machineTypes": [
+        {
+          "creationTimestamp": "2012-11-16T11:44:25.985-08:00",
+          "description": "8 vCPUs, 52 GB RAM",
+          "guestCpus": 8,
+          "id": "01717932668777642040",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highmem-8",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:45:08.195-08:00",
+          "description": "8 vCPUs, 52 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "07181827135536388552",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highmem-8-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:50:15.128-08:00",
+          "description": "8 vCPUs, 7.2 GB RAM",
+          "guestCpus": 8,
+          "id": "01206886442411821831",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highcpu-8",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:43:17.400-08:00",
+          "description": "4 vCPUs, 26 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "05095504563332567951",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highmem-4-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:59.630-08:00",
+          "description": "2 vCPUs, 13 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "00770157291441082211",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highmem-2-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:48:06.087-08:00",
+          "description": "4 vCPUs, 3.6 GB RAM",
+          "guestCpus": 4,
+          "id": "04759000181765218034",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highcpu-4",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:49.088-07:00",
+          "description": "1 vCPU (shared physical core) and 0.6 GB RAM",
+          "guestCpus": 1,
+          "id": "1133568312750571513",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 614,
+          "name": "f1-micro",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/f1-micro",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:34.258-07:00",
+          "description": "1 vCPU, 3.75 GB RAM, 1 scratch disk (420 GB)",
+          "guestCpus": 1,
+          "id": "10583029372018866711",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1-d",
+          "scratchDisks": [
+            {
+              "diskGb": 420
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-standard-1-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:19.448-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "06313284160910191442",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-standard-2-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:06.129-08:00",
+          "description": "2 vCPUs, 13 GB RAM",
+          "guestCpus": 2,
+          "id": "05438694236916301519",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highmem-2",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:47:07.825-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "15178384466070744001",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highcpu-2-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:42:08.983-08:00",
+          "description": "4 vCPUs, 26 GB RAM",
+          "guestCpus": 4,
+          "id": "11556032176405786676",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highmem-4",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:50:05.677-07:00",
+          "description": "4 vCPUs, 15 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "00523085164784013586",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-standard-4-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:45.550-07:00",
+          "description": "1 vCPU (shared physical core) and 1.7 GB RAM",
+          "guestCpus": 1,
+          "id": "1500265464823777597",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 1740,
+          "name": "g1-small",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/g1-small",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:46:10.572-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM",
+          "guestCpus": 2,
+          "id": "16898271314080235997",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highcpu-2",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:40.050-07:00",
+          "description": "4 vCPUs, 15 GB RAM",
+          "guestCpus": 4,
+          "id": "09494636486174545828",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-standard-4",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:51:04.549-08:00",
+          "description": "8 vCPUS, 7.2 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "02507333096579477005",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highcpu-8-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:14.670-07:00",
+          "description": "1 vCPU, 3.75 GB RAM",
+          "guestCpus": 1,
+          "id": "11077240422128681563",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-standard-1",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:49:07.563-08:00",
+          "description": "4 vCPUS, 3.6 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "01151097524490134507",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-highcpu-4-d",
+          "zone": "europe-west1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:56.867-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM",
+          "guestCpus": 2,
+          "id": "17936898073622676356",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b/machineTypes/n1-standard-2",
+          "zone": "europe-west1-b"
+        }
+      ]
+    },
+    "zones/us-central1-a": {
+      "machineTypes": [
+        {
+          "creationTimestamp": "2012-11-16T11:44:25.985-08:00",
+          "description": "8 vCPUs, 52 GB RAM",
+          "guestCpus": 8,
+          "id": "01717932668777642040",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-8",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:43:17.400-08:00",
+          "description": "4 vCPUs, 26 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "05095504563332567951",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-4-d",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:47:07.825-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "15178384466070744001",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-2-d",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:40.050-07:00",
+          "description": "4 vCPUs, 15 GB RAM",
+          "guestCpus": 4,
+          "id": "09494636486174545828",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-4",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:59.630-08:00",
+          "description": "2 vCPUs, 13 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "00770157291441082211",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-2-d",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:50:05.677-07:00",
+          "description": "4 vCPUs, 15 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "00523085164784013586",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-4-d",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:50:15.128-08:00",
+          "description": "8 vCPUs, 7.2 GB RAM",
+          "guestCpus": 8,
+          "id": "01206886442411821831",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-8",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:46:10.572-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM",
+          "guestCpus": 2,
+          "id": "16898271314080235997",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-2",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:19.448-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "06313284160910191442",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-2-d",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:56.867-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM",
+          "guestCpus": 2,
+          "id": "17936898073622676356",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-2",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:42:08.983-08:00",
+          "description": "4 vCPUs, 26 GB RAM",
+          "guestCpus": 4,
+          "id": "11556032176405786676",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-4",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:14.670-07:00",
+          "description": "1 vCPU, 3.75 GB RAM",
+          "guestCpus": 1,
+          "id": "11077240422128681563",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:48:06.087-08:00",
+          "description": "4 vCPUs, 3.6 GB RAM",
+          "guestCpus": 4,
+          "id": "04759000181765218034",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-4",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:45.550-07:00",
+          "description": "1 vCPU (shared physical core) and 1.7 GB RAM",
+          "guestCpus": 1,
+          "id": "1500265464823777597",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 1740,
+          "name": "g1-small",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/g1-small",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:49.088-07:00",
+          "description": "1 vCPU (shared physical core) and 0.6 GB RAM",
+          "guestCpus": 1,
+          "id": "1133568312750571513",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 614,
+          "name": "f1-micro",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/f1-micro",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:06.129-08:00",
+          "description": "2 vCPUs, 13 GB RAM",
+          "guestCpus": 2,
+          "id": "05438694236916301519",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-2",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:34.258-07:00",
+          "description": "1 vCPU, 3.75 GB RAM, 1 scratch disk (420 GB)",
+          "guestCpus": 1,
+          "id": "10583029372018866711",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1-d",
+          "scratchDisks": [
+            {
+              "diskGb": 420
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1-d",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:49:07.563-08:00",
+          "description": "4 vCPUS, 3.6 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "01151097524490134507",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-4-d",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:45:08.195-08:00",
+          "description": "8 vCPUs, 52 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "07181827135536388552",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-8-d",
+          "zone": "us-central1-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:51:04.549-08:00",
+          "description": "8 vCPUS, 7.2 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "02507333096579477005",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-8-d",
+          "zone": "us-central1-a"
+        }
+      ]
+    },
+    "zones/us-central1-b": {
+      "machineTypes": [
+        {
+          "creationTimestamp": "2012-06-07T13:50:05.677-07:00",
+          "description": "4 vCPUs, 15 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "00523085164784013586",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-standard-4-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:49:07.563-08:00",
+          "description": "4 vCPUS, 3.6 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "01151097524490134507",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highcpu-4-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:44:25.985-08:00",
+          "description": "8 vCPUs, 52 GB RAM",
+          "guestCpus": 8,
+          "id": "01717932668777642040",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highmem-8",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:47:07.825-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "15178384466070744001",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highcpu-2-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:51:04.549-08:00",
+          "description": "8 vCPUS, 7.2 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "02507333096579477005",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highcpu-8-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:50:15.128-08:00",
+          "description": "8 vCPUs, 7.2 GB RAM",
+          "guestCpus": 8,
+          "id": "01206886442411821831",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highcpu-8",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:45.550-07:00",
+          "description": "1 vCPU (shared physical core) and 1.7 GB RAM",
+          "guestCpus": 1,
+          "id": "1500265464823777597",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 1740,
+          "name": "g1-small",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/g1-small",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:48:06.087-08:00",
+          "description": "4 vCPUs, 3.6 GB RAM",
+          "guestCpus": 4,
+          "id": "04759000181765218034",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highcpu-4",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:34.258-07:00",
+          "description": "1 vCPU, 3.75 GB RAM, 1 scratch disk (420 GB)",
+          "guestCpus": 1,
+          "id": "10583029372018866711",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1-d",
+          "scratchDisks": [
+            {
+              "diskGb": 420
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-standard-1-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:42:08.983-08:00",
+          "description": "4 vCPUs, 26 GB RAM",
+          "guestCpus": 4,
+          "id": "11556032176405786676",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highmem-4",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:14.670-07:00",
+          "description": "1 vCPU, 3.75 GB RAM",
+          "guestCpus": 1,
+          "id": "11077240422128681563",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-standard-1",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:49.088-07:00",
+          "description": "1 vCPU (shared physical core) and 0.6 GB RAM",
+          "guestCpus": 1,
+          "id": "1133568312750571513",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 614,
+          "name": "f1-micro",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/f1-micro",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:59.630-08:00",
+          "description": "2 vCPUs, 13 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "00770157291441082211",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highmem-2-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:06.129-08:00",
+          "description": "2 vCPUs, 13 GB RAM",
+          "guestCpus": 2,
+          "id": "05438694236916301519",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highmem-2",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:19.448-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "06313284160910191442",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-standard-2-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:56.867-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM",
+          "guestCpus": 2,
+          "id": "17936898073622676356",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-standard-2",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:46:10.572-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM",
+          "guestCpus": 2,
+          "id": "16898271314080235997",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highcpu-2",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:43:17.400-08:00",
+          "description": "4 vCPUs, 26 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "05095504563332567951",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highmem-4-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:45:08.195-08:00",
+          "description": "8 vCPUs, 52 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "07181827135536388552",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-highmem-8-d",
+          "zone": "us-central1-b"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:40.050-07:00",
+          "description": "4 vCPUs, 15 GB RAM",
+          "guestCpus": 4,
+          "id": "09494636486174545828",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/machineTypes/n1-standard-4",
+          "zone": "us-central1-b"
+        }
+      ]
+    },
+    "zones/us-central2-a": {
+      "machineTypes": [
+        {
+          "creationTimestamp": "2012-11-16T11:49:07.563-08:00",
+          "description": "4 vCPUS, 3.6 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "01151097524490134507",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highcpu-4-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:43:17.400-08:00",
+          "description": "4 vCPUs, 26 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "05095504563332567951",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highmem-4-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:40.050-07:00",
+          "description": "4 vCPUs, 15 GB RAM",
+          "guestCpus": 4,
+          "id": "09494636486174545828",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-standard-4",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:06.129-08:00",
+          "description": "2 vCPUs, 13 GB RAM",
+          "guestCpus": 2,
+          "id": "05438694236916301519",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highmem-2",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:56.867-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM",
+          "guestCpus": 2,
+          "id": "17936898073622676356",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-standard-2",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:49.088-07:00",
+          "description": "1 vCPU (shared physical core) and 0.6 GB RAM",
+          "guestCpus": 1,
+          "id": "1133568312750571513",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 614,
+          "name": "f1-micro",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/f1-micro",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:50:15.128-08:00",
+          "description": "8 vCPUs, 7.2 GB RAM",
+          "guestCpus": 8,
+          "id": "01206886442411821831",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highcpu-8",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:42:08.983-08:00",
+          "description": "4 vCPUs, 26 GB RAM",
+          "guestCpus": 4,
+          "id": "11556032176405786676",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 26624,
+          "name": "n1-highmem-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highmem-4",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:45:08.195-08:00",
+          "description": "8 vCPUs, 52 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "07181827135536388552",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highmem-8-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:44:25.985-08:00",
+          "description": "8 vCPUs, 52 GB RAM",
+          "guestCpus": 8,
+          "id": "01717932668777642040",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 53248,
+          "name": "n1-highmem-8",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highmem-8",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:34.258-07:00",
+          "description": "1 vCPU, 3.75 GB RAM, 1 scratch disk (420 GB)",
+          "guestCpus": 1,
+          "id": "10583029372018866711",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1-d",
+          "scratchDisks": [
+            {
+              "diskGb": 420
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-standard-1-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:47:07.825-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "15178384466070744001",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highcpu-2-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:40:59.630-08:00",
+          "description": "2 vCPUs, 13 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "00770157291441082211",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 13312,
+          "name": "n1-highmem-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highmem-2-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:48:06.087-08:00",
+          "description": "4 vCPUs, 3.6 GB RAM",
+          "guestCpus": 4,
+          "id": "04759000181765218034",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3686,
+          "name": "n1-highcpu-4",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highcpu-4",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:51:04.549-08:00",
+          "description": "8 vCPUS, 7.2 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+          "guestCpus": 8,
+          "id": "02507333096579477005",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7373,
+          "name": "n1-highcpu-8-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            },
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highcpu-8-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:50:05.677-07:00",
+          "description": "4 vCPUs, 15 GB RAM, 1 scratch disk (1770 GB)",
+          "guestCpus": 4,
+          "id": "00523085164784013586",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 15360,
+          "name": "n1-standard-4-d",
+          "scratchDisks": [
+            {
+              "diskGb": 1770
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-standard-4-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:48:14.670-07:00",
+          "description": "1 vCPU, 3.75 GB RAM",
+          "guestCpus": 1,
+          "id": "11077240422128681563",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 3840,
+          "name": "n1-standard-1",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-standard-1",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-11-16T11:46:10.572-08:00",
+          "description": "2 vCPUs, 1.8 GB RAM",
+          "guestCpus": 2,
+          "id": "16898271314080235997",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 1843,
+          "name": "n1-highcpu-2",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-highcpu-2",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2012-06-07T13:49:19.448-07:00",
+          "description": "2 vCPUs, 7.5 GB RAM, 1 scratch disk (870 GB)",
+          "guestCpus": 2,
+          "id": "06313284160910191442",
+          "imageSpaceGb": 10,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 16,
+          "maximumPersistentDisksSizeGb": "10240",
+          "memoryMb": 7680,
+          "name": "n1-standard-2-d",
+          "scratchDisks": [
+            {
+              "diskGb": 870
+            }
+          ],
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/n1-standard-2-d",
+          "zone": "us-central2-a"
+        },
+        {
+          "creationTimestamp": "2013-04-25T13:32:45.550-07:00",
+          "description": "1 vCPU (shared physical core) and 1.7 GB RAM",
+          "guestCpus": 1,
+          "id": "1500265464823777597",
+          "imageSpaceGb": 0,
+          "kind": "compute#machineType",
+          "maximumPersistentDisks": 4,
+          "maximumPersistentDisksSizeGb": "3072",
+          "memoryMb": 1740,
+          "name": "g1-small",
+          "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a/machineTypes/g1-small",
+          "zone": "us-central2-a"
+        }
+      ]
+    }
+  },
+  "kind": "compute#machineTypeAggregatedList",
+  "nextPageToken": "ChhQRVJfUFJPSkVDVF9NQUNISU5FX1RZUEUSGjYwMDUzMTk1NTY3NS5uMS1zdGFuZGFyZC04",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/aggregated/machineTypes"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_firewalls.json b/libcloud/test/compute/fixtures/gce/global_firewalls.json
new file mode 100644
index 0000000..c25af7a
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_firewalls.json
@@ -0,0 +1,88 @@
+{
+  "id": "projects/project_name/global/firewalls",
+  "items": [
+    {
+      "allowed": [
+        {
+          "IPProtocol": "udp"
+        },
+        {
+          "IPProtocol": "tcp"
+        },
+        {
+          "IPProtocol": "icmp"
+        }
+      ],
+      "creationTimestamp": "2013-06-25T19:50:41.630-07:00",
+      "description": "",
+      "id": "5399576268464751692",
+      "kind": "compute#firewall",
+      "name": "default-allow-internal",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/default-allow-internal",
+      "sourceRanges": [
+        "10.240.0.0/16"
+      ]
+    },
+    {
+      "allowed": [
+        {
+          "IPProtocol": "tcp",
+          "ports": [
+            "22"
+          ]
+        }
+      ],
+      "creationTimestamp": "2013-06-25T19:48:25.111-07:00",
+      "description": "",
+      "id": "8063006729705804986",
+      "kind": "compute#firewall",
+      "name": "default-ssh",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/default-ssh",
+      "sourceRanges": [
+        "0.0.0.0/0"
+      ]
+    },
+    {
+      "allowed": [
+        {
+          "IPProtocol": "tcp",
+          "ports": [
+            "3141"
+          ]
+        }
+      ],
+      "creationTimestamp": "2013-06-26T09:51:41.593-07:00",
+      "id": "14041102034246553251",
+      "kind": "compute#firewall",
+      "name": "libcloud-demo-europe-firewall",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/libcloud-demo-europe-network",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/libcloud-demo-europe-firewall",
+      "sourceTags": [
+        "libcloud"
+      ]
+    },
+    {
+      "allowed": [
+        {
+          "IPProtocol": "tcp",
+          "ports": [
+            "3141"
+          ]
+        }
+      ],
+      "creationTimestamp": "2013-06-26T09:48:23.268-07:00",
+      "id": "0716768890200439066",
+      "kind": "compute#firewall",
+      "name": "libcloud-demo-firewall",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/libcloud-demo-network",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/libcloud-demo-firewall",
+      "sourceTags": [
+        "libcloud"
+      ]
+    }
+  ],
+  "kind": "compute#firewallList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall.json b/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall.json
new file mode 100644
index 0000000..fde8474
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall.json
@@ -0,0 +1,19 @@
+{
+  "allowed": [
+    {
+      "IPProtocol": "tcp",
+      "ports": [
+        "4567"
+      ]
+    }
+  ],
+  "creationTimestamp": "2013-06-26T10:04:43.773-07:00",
+  "id": "0565629596395414121",
+  "kind": "compute#firewall",
+  "name": "lcfirewall",
+  "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/lcfirewall",
+  "sourceTags": [
+    "libcloud"
+  ]
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall_delete.json b/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall_delete.json
new file mode 100644
index 0000000..75c0ce7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall_delete.json
@@ -0,0 +1,14 @@
+{
+  "id": "8983098895755095934",
+  "insertTime": "2013-06-26T10:04:53.453-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_firewalls_lcfirewall_delete",
+  "operationType": "delete",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_firewalls_lcfirewall_delete",
+  "startTime": "2013-06-26T10:04:53.508-07:00",
+  "status": "PENDING",
+  "targetId": "0565629596395414121",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/lcfirewall",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall_put.json b/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall_put.json
new file mode 100644
index 0000000..c075282
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_firewalls_lcfirewall_put.json
@@ -0,0 +1,14 @@
+{
+  "id": "6526551968265354277",
+  "insertTime": "2013-06-26T20:52:00.355-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_firewalls_lcfirewall_put",
+  "operationType": "update",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_firewalls_lcfirewall_put",
+  "startTime": "2013-06-26T20:52:00.410-07:00",
+  "status": "PENDING",
+  "targetId": "10942695305090163011",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/lcfirewall",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_firewalls_post.json b/libcloud/test/compute/fixtures/gce/global_firewalls_post.json
new file mode 100644
index 0000000..84124f3
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_firewalls_post.json
@@ -0,0 +1,13 @@
+{
+  "id": "16789512465352307784",
+  "insertTime": "2013-06-26T20:51:06.068-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_firewalls_post",
+  "operationType": "insert",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_firewalls_post",
+  "startTime": "2013-06-26T20:51:06.128-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/lcfirewall",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_images.json b/libcloud/test/compute/fixtures/gce/global_images.json
new file mode 100644
index 0000000..c472dfa
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_images.json
@@ -0,0 +1,22 @@
+{
+  "id": "projects/project_name/global/images",
+  "items": [
+    {
+      "creationTimestamp": "2013-06-19T13:47:20.563-07:00",
+      "description": "Local Debian GNU/Linux 7.1 (wheezy) built on 2013-06-17",
+      "id": "1549141992333368759",
+      "kind": "compute#image",
+      "name": "debian-7-wheezy-v20130617",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130603",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "sourceType": "RAW",
+      "status": "READY"
+    }
+  ],
+  "kind": "compute#imageList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/images"
+}
diff --git a/libcloud/test/compute/fixtures/gce/global_images.json.save b/libcloud/test/compute/fixtures/gce/global_images.json.save
new file mode 100644
index 0000000..c472dfa
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_images.json.save
@@ -0,0 +1,22 @@
+{
+  "id": "projects/project_name/global/images",
+  "items": [
+    {
+      "creationTimestamp": "2013-06-19T13:47:20.563-07:00",
+      "description": "Local Debian GNU/Linux 7.1 (wheezy) built on 2013-06-17",
+      "id": "1549141992333368759",
+      "kind": "compute#image",
+      "name": "debian-7-wheezy-v20130617",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130603",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "sourceType": "RAW",
+      "status": "READY"
+    }
+  ],
+  "kind": "compute#imageList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/images"
+}
diff --git a/libcloud/test/compute/fixtures/gce/global_networks.json b/libcloud/test/compute/fixtures/gce/global_networks.json
new file mode 100644
index 0000000..071d1dc
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_networks.json
@@ -0,0 +1,34 @@
+{
+  "id": "projects/project_name/global/networks",
+  "items": [
+    {
+      "IPv4Range": "10.240.0.0/16",
+      "creationTimestamp": "2013-06-19T12:37:13.233-07:00",
+      "gatewayIPv4": "10.240.0.1",
+      "id": "08257021638942464470",
+      "kind": "compute#network",
+      "name": "default",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default"
+    },
+    {
+      "IPv4Range": "10.10.0.0/16",
+      "creationTimestamp": "2013-06-26T09:51:34.018-07:00",
+      "gatewayIPv4": "10.10.0.1",
+      "id": "13254259054875092094",
+      "kind": "compute#network",
+      "name": "libcloud-demo-europe-network",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/libcloud-demo-europe-network"
+    },
+    {
+      "IPv4Range": "10.10.0.0/16",
+      "creationTimestamp": "2013-06-26T09:48:15.703-07:00",
+      "gatewayIPv4": "10.10.0.1",
+      "id": "17172579178188075621",
+      "kind": "compute#network",
+      "name": "libcloud-demo-network",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/libcloud-demo-network"
+    }
+  ],
+  "kind": "compute#networkList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_networks_default.json b/libcloud/test/compute/fixtures/gce/global_networks_default.json
new file mode 100644
index 0000000..a6353e8
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_networks_default.json
@@ -0,0 +1,9 @@
+{
+  "IPv4Range": "10.240.0.0/16",
+  "creationTimestamp": "2013-06-19T12:37:13.233-07:00",
+  "gatewayIPv4": "10.240.0.1",
+  "id": "08257021638942464470",
+  "kind": "compute#network",
+  "name": "default",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_networks_lcnetwork.json b/libcloud/test/compute/fixtures/gce/global_networks_lcnetwork.json
new file mode 100644
index 0000000..b615cad
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_networks_lcnetwork.json
@@ -0,0 +1,9 @@
+{
+  "IPv4Range": "10.11.0.0/16",
+  "creationTimestamp": "2013-06-26T10:05:03.500-07:00",
+  "gatewayIPv4": "10.11.0.1",
+  "id": "16211908079305042870",
+  "kind": "compute#network",
+  "name": "lcnetwork",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/lcnetwork"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_networks_lcnetwork_delete.json b/libcloud/test/compute/fixtures/gce/global_networks_lcnetwork_delete.json
new file mode 100644
index 0000000..cc65f00
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_networks_lcnetwork_delete.json
@@ -0,0 +1,14 @@
+{
+  "id": "4914541423567262393",
+  "insertTime": "2013-06-26T10:05:11.102-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_networks_lcnetwork_delete",
+  "operationType": "delete",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_networks_lcnetwork_delete",
+  "startTime": "2013-06-26T10:05:11.273-07:00",
+  "status": "PENDING",
+  "targetId": "16211908079305042870",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/lcnetwork",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_networks_libcloud-demo-europe-network.json b/libcloud/test/compute/fixtures/gce/global_networks_libcloud-demo-europe-network.json
new file mode 100644
index 0000000..93915c0
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_networks_libcloud-demo-europe-network.json
@@ -0,0 +1,9 @@
+{
+  "IPv4Range": "10.10.0.0/16",
+  "creationTimestamp": "2013-06-26T09:51:34.018-07:00",
+  "gatewayIPv4": "10.10.0.1",
+  "id": "13254259054875092094",
+  "kind": "compute#network",
+  "name": "libcloud-demo-europe-network",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/libcloud-demo-europe-network"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_networks_libcloud-demo-network.json b/libcloud/test/compute/fixtures/gce/global_networks_libcloud-demo-network.json
new file mode 100644
index 0000000..55b9be1
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_networks_libcloud-demo-network.json
@@ -0,0 +1,9 @@
+{
+  "IPv4Range": "10.10.0.0/16",
+  "creationTimestamp": "2013-06-26T09:48:15.703-07:00",
+  "gatewayIPv4": "10.10.0.1",
+  "id": "17172579178188075621",
+  "kind": "compute#network",
+  "name": "libcloud-demo-network",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/libcloud-demo-network"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_networks_post.json b/libcloud/test/compute/fixtures/gce/global_networks_post.json
new file mode 100644
index 0000000..72fca77
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_networks_post.json
@@ -0,0 +1,13 @@
+{
+  "id": "3681664092089171723",
+  "insertTime": "2013-06-26T10:05:03.271-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_networks_post",
+  "operationType": "insert",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_networks_post",
+  "startTime": "2013-06-26T10:05:03.315-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/lcnetwork",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_lcfirewall_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_lcfirewall_delete.json
new file mode 100644
index 0000000..56c6db6
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_lcfirewall_delete.json
@@ -0,0 +1,15 @@
+{
+  "endTime": "2013-06-26T10:05:00.978-07:00",
+  "id": "8983098895755095934",
+  "insertTime": "2013-06-26T10:04:53.453-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_firewalls_lcfirewall_delete",
+  "operationType": "delete",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_firewalls_lcfirewall_delete",
+  "startTime": "2013-06-26T10:04:53.508-07:00",
+  "status": "DONE",
+  "targetId": "0565629596395414121",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/lcfirewall",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_lcfirewall_put.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_lcfirewall_put.json
new file mode 100644
index 0000000..e50185c
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_lcfirewall_put.json
@@ -0,0 +1,15 @@
+{
+  "endTime": "2013-06-26T20:52:10.075-07:00",
+  "id": "6526551968265354277",
+  "insertTime": "2013-06-26T20:52:00.355-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_firewalls_lcfirewall_put",
+  "operationType": "update",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_firewalls_lcfirewall_put",
+  "startTime": "2013-06-26T20:52:00.410-07:00",
+  "status": "DONE",
+  "targetId": "10942695305090163011",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/lcfirewall",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_post.json
new file mode 100644
index 0000000..57623d9
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_firewalls_post.json
@@ -0,0 +1,15 @@
+{
+  "endTime": "2013-06-26T20:51:12.108-07:00",
+  "id": "16789512465352307784",
+  "insertTime": "2013-06-26T20:51:06.068-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_firewalls_post",
+  "operationType": "insert",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_firewalls_post",
+  "startTime": "2013-06-26T20:51:06.128-07:00",
+  "status": "DONE",
+  "targetId": "10942695305090163011",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/firewalls/lcfirewall",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_networks_lcnetwork_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_networks_lcnetwork_delete.json
new file mode 100644
index 0000000..a5e75cf
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_networks_lcnetwork_delete.json
@@ -0,0 +1,15 @@
+{
+  "endTime": "2013-06-26T10:05:12.607-07:00",
+  "id": "4914541423567262393",
+  "insertTime": "2013-06-26T10:05:11.102-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_networks_lcnetwork_delete",
+  "operationType": "delete",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_networks_lcnetwork_delete",
+  "startTime": "2013-06-26T10:05:11.273-07:00",
+  "status": "DONE",
+  "targetId": "16211908079305042870",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/lcnetwork",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_networks_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_networks_post.json
new file mode 100644
index 0000000..d0a989e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_networks_post.json
@@ -0,0 +1,15 @@
+{
+  "endTime": "2013-06-26T10:05:07.630-07:00",
+  "id": "3681664092089171723",
+  "insertTime": "2013-06-26T10:05:03.271-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_networks_post",
+  "operationType": "insert",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_networks_post",
+  "startTime": "2013-06-26T10:05:03.315-07:00",
+  "status": "DONE",
+  "targetId": "16211908079305042870",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/lcnetwork",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_addresses_lcaddress_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_addresses_lcaddress_delete.json
new file mode 100644
index 0000000..a138e5c
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_addresses_lcaddress_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "7128783508312083402",
+  "insertTime": "2013-06-26T12:21:44.075-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_addresses_lcaddress_delete",
+  "operationType": "delete",
+  "progress": 100,
+  "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/operations/operation-regions_us-central1_addresses_lcaddress_delete",
+  "startTime": "2013-06-26T12:21:44.110-07:00",
+  "status": "DONE",
+  "targetId": "01531551729918243104",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/lcaddress",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_addresses_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_addresses_post.json
new file mode 100644
index 0000000..baf5d57
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_addresses_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "16064059851942653139",
+  "insertTime": "2013-06-26T12:21:40.299-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_addresses_post",
+  "operationType": "insert",
+  "progress": 100,
+  "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/operations/operation-regions_us-central1_addresses_post",
+  "startTime": "2013-06-26T12:21:40.358-07:00",
+  "status": "DONE",
+  "targetId": "01531551729918243104",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/lcaddress",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_europe-west1-a_instances_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_europe-west1-a_instances_post.json
new file mode 100644
index 0000000..96ffebc
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_europe-west1-a_instances_post.json
@@ -0,0 +1,25 @@
+{
+  "error": {
+    "errors": [
+      {
+        "code": "RESOURCE_ALREADY_EXISTS",
+        "message": "The resource 'projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-np-node' already exists"
+      }
+    ]
+  },
+  "httpErrorMessage": "CONFLICT",
+  "httpErrorStatusCode": 409,
+  "id": "1510575454210533141",
+  "insertTime": "2013-06-26T20:57:34.366-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_europe-west1-a_instances_post",
+  "operationType": "insert",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/operations/operation-zones_europe-west1-a_instances_post",
+  "startTime": "2013-06-26T20:57:34.453-07:00",
+  "status": "DONE",
+  "targetId": "14308265828754333159",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-np-node",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_disks_lcdisk_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_disks_lcdisk_delete.json
new file mode 100644
index 0000000..e71f2a3
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_disks_lcdisk_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "06887337364510109333",
+  "insertTime": "2013-06-26T10:06:11.835-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_disks_lcdisk_delete",
+  "operationType": "delete",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_disks_lcdisk_delete",
+  "startTime": "2013-06-26T10:06:12.006-07:00",
+  "status": "DONE",
+  "targetId": "16109451798967042451",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/lcdisk",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_disks_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_disks_post.json
new file mode 100644
index 0000000..122a7a8
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_disks_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-06-26T16:48:25.375-07:00",
+  "id": "0211151278250678078",
+  "insertTime": "2013-06-26T16:48:17.403-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_disks_post",
+  "operationType": "insert",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_disks_post",
+  "startTime": "2013-06-26T16:48:17.479-07:00",
+  "status": "DONE",
+  "targetId": "03196637868764498730",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/lcdisk",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_lcnode-000_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_lcnode-000_delete.json
new file mode 100644
index 0000000..524fe81
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_lcnode-000_delete.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-06-26T16:13:36.800-07:00",
+  "id": "3319596145594427549",
+  "insertTime": "2013-06-26T16:13:12.903-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_lcnode-000_delete",
+  "operationType": "delete",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_lcnode-000_delete",
+  "startTime": "2013-06-26T16:13:12.948-07:00",
+  "status": "DONE",
+  "targetId": "5390075309006132922",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/lcnode-000",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_lcnode-001_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_lcnode-001_delete.json
new file mode 100644
index 0000000..369e9d2
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_lcnode-001_delete.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-06-26T16:13:56.931-07:00",
+  "id": "17469711273432628502",
+  "insertTime": "2013-06-26T16:13:40.579-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_lcnode-001_delete",
+  "operationType": "delete",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_lcnode-001_delete",
+  "startTime": "2013-06-26T16:13:40.620-07:00",
+  "status": "DONE",
+  "targetId": "16630486471904253898",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/lcnode-001",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_attachDisk_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_attachDisk_post.json
new file mode 100644
index 0000000..1563c39
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_attachDisk_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-06-26T16:48:31.831-07:00",
+  "id": "7455886659787654716",
+  "insertTime": "2013-06-26T16:48:27.691-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_attachDisk_post",
+  "operationType": "attachDisk",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_attachDisk_post",
+  "startTime": "2013-06-26T16:48:27.762-07:00",
+  "status": "DONE",
+  "targetId": "1845312225624811608",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_delete.json
new file mode 100644
index 0000000..8e728e7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_delete.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-06-26T10:06:00.917-07:00",
+  "id": "6999931397447918763",
+  "insertTime": "2013-06-26T10:05:40.350-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_delete",
+  "operationType": "delete",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_delete",
+  "startTime": "2013-06-26T10:05:40.405-07:00",
+  "status": "DONE",
+  "targetId": "07410051435384876224",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_detachDisk_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_detachDisk_post.json
new file mode 100644
index 0000000..fdf2a75
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_detachDisk_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-06-26T16:48:41.278-07:00",
+  "id": "3921383727105838816",
+  "insertTime": "2013-06-26T16:48:35.357-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_detachDisk_post",
+  "operationType": "detachDisk",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_detachDisk_post",
+  "startTime": "2013-06-26T16:48:35.398-07:00",
+  "status": "DONE",
+  "targetId": "1845312225624811608",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_reset_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_reset_post.json
new file mode 100644
index 0000000..28d87ef
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_reset_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "10507122129283663728",
+  "insertTime": "2013-06-26T15:03:02.766-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_reset_post",
+  "operationType": "reset",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_reset_post",
+  "startTime": "2013-06-26T15:03:02.813-07:00",
+  "status": "DONE",
+  "targetId": "1845312225624811608",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_setTags_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_setTags_post.json
new file mode 100644
index 0000000..14c26f1
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_node-name_setTags_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-06-26T21:20:10.487-07:00",
+  "id": "8115150846190320932",
+  "insertTime": "2013-06-26T21:20:03.962-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_setTags_post",
+  "operationType": "setTags",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_setTags_post",
+  "startTime": "2013-06-26T21:20:04.103-07:00",
+  "status": "DONE",
+  "targetId": "1845312225624811608",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_post.json
new file mode 100644
index 0000000..ab62f88
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_instances_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-06-26T16:13:08.382-07:00",
+  "id": "1858155812259649243",
+  "insertTime": "2013-06-26T16:12:51.492-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_post",
+  "operationType": "insert",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_post",
+  "startTime": "2013-06-26T16:12:51.537-07:00",
+  "status": "DONE",
+  "targetId": "16630486471904253898",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/lcnode-001",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/project.json b/libcloud/test/compute/fixtures/gce/project.json
new file mode 100644
index 0000000..b8b746c
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/project.json
@@ -0,0 +1,74 @@
+{
+  "commonInstanceMetadata": {
+    "items": [
+      {
+        "key": "sshKeys",
+        "value": "ASDFASDF"
+      }
+    ],
+    "kind": "compute#metadata"
+  },
+  "creationTimestamp": "2013-02-05T16:19:20.516-08:00",
+  "description": "",
+  "id": "2193465259114366848",
+  "kind": "compute#project",
+  "name": "project_name",
+  "quotas": [
+    {
+      "limit": 8.0,
+      "metric": "INSTANCES",
+      "usage": 7.0
+    },
+    {
+      "limit": 8.0,
+      "metric": "CPUS",
+      "usage": 7.0
+    },
+    {
+      "limit": 8.0,
+      "metric": "EPHEMERAL_ADDRESSES",
+      "usage": 7.0
+    },
+    {
+      "limit": 8.0,
+      "metric": "DISKS",
+      "usage": 3.0
+    },
+    {
+      "limit": 1024.0,
+      "metric": "DISKS_TOTAL_GB",
+      "usage": 30.0
+    },
+    {
+      "limit": 1000.0,
+      "metric": "SNAPSHOTS",
+      "usage": 0.0
+    },
+    {
+      "limit": 5.0,
+      "metric": "NETWORKS",
+      "usage": 3.0
+    },
+    {
+      "limit": 100.0,
+      "metric": "FIREWALLS",
+      "usage": 4.0
+    },
+    {
+      "limit": 100.0,
+      "metric": "IMAGES",
+      "usage": 0.0
+    },
+    {
+      "limit": 7.0,
+      "metric": "STATIC_ADDRESSES",
+      "usage": 3.0
+    },
+    {
+      "limit": 100.0,
+      "metric": "ROUTES",
+      "usage": 6.0
+    }
+  ],
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/projects_debian-cloud_global_images.json b/libcloud/test/compute/fixtures/gce/projects_debian-cloud_global_images.json
new file mode 100644
index 0000000..461b0c9
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/projects_debian-cloud_global_images.json
@@ -0,0 +1,157 @@
+{
+  "id": "projects/debian-cloud/global/images",
+  "items": [
+    {
+      "creationTimestamp": "2013-05-07T17:09:22.111-07:00",
+      "description": "Debian GNU/Linux 6.0.7 (squeeze) built on 2013-05-07",
+      "id": "647943287916432906",
+      "kind": "compute#image",
+      "name": "debian-6-squeeze-v20130507",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130225",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-6-squeeze-v20130507",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-05-09T12:56:21.720-07:00",
+      "description": "Debian GNU/Linux 6.0.7 (squeeze) built on 2013-05-09",
+      "id": "15745758816845911589",
+      "kind": "compute#image",
+      "name": "debian-6-squeeze-v20130509",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130225",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-6-squeeze-v20130509",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-05-14T21:01:12.124-07:00",
+      "description": "Debian GNU/Linux 6.0.7 (squeeze) built on 2013-05-15",
+      "id": "006866479348046290",
+      "kind": "compute#image",
+      "name": "debian-6-squeeze-v20130515",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130515",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-6-squeeze-v20130515",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-05-30T09:48:37.837-07:00",
+      "description": "Debian GNU/Linux 6.0.7 (squeeze) built on 2013-05-22",
+      "id": "1266148899538866390",
+      "kind": "compute#image",
+      "name": "debian-6-squeeze-v20130522",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130522",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-6-squeeze-v20130522",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-06-19T13:45:44.111-07:00",
+      "description": "Debian GNU/Linux 6.0.7 (squeeze) built on 2013-06-17",
+      "id": "04009358257173422091",
+      "kind": "compute#image",
+      "name": "debian-6-squeeze-v20130617",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130603",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-6-squeeze-v20130617",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-05-07T17:01:30.071-07:00",
+      "description": "Debian GNU/Linux 7.0 (wheezy) built on 2013-05-07",
+      "id": "15638477823580670459",
+      "kind": "compute#image",
+      "name": "debian-7-wheezy-v20130507",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130225",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130507",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-05-09T12:56:47.910-07:00",
+      "description": "Debian GNU/Linux 7.0 (wheezy) built on 2013-05-09",
+      "id": "020034532765408091",
+      "kind": "compute#image",
+      "name": "debian-7-wheezy-v20130509",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130225",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130509",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-05-14T21:02:55.044-07:00",
+      "description": "Debian GNU/Linux 7.0 (wheezy) built on 2013-05-15",
+      "id": "0587071888358410836",
+      "kind": "compute#image",
+      "name": "debian-7-wheezy-v20130515",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130515",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130515",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-05-30T09:47:30.980-07:00",
+      "description": "Debian GNU/Linux 7.0 (wheezy) built on 2013-05-22",
+      "id": "622079684385221180",
+      "kind": "compute#image",
+      "name": "debian-7-wheezy-v20130522",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130522",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130522",
+      "sourceType": "RAW",
+      "status": "READY"
+    },
+    {
+      "creationTimestamp": "2013-06-19T13:47:20.563-07:00",
+      "description": "Debian GNU/Linux 7.1 (wheezy) built on 2013-06-17",
+      "id": "1549141992333368759",
+      "kind": "compute#image",
+      "name": "debian-7-wheezy-v20130617",
+      "preferredKernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130603",
+      "rawDisk": {
+        "containerType": "TAR",
+        "source": ""
+      },
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "sourceType": "RAW",
+      "status": "READY"
+    }
+  ],
+  "kind": "compute#imageList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses.json
new file mode 100644
index 0000000..a75d633
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses.json
@@ -0,0 +1,29 @@
+{
+  "id": "projects/project_name/regions/us-central1/addresses",
+  "items": [
+    {
+      "address": "108.59.82.4",
+      "creationTimestamp": "2013-06-26T09:48:31.184-07:00",
+      "description": "",
+      "id": "17634862894218443422",
+      "kind": "compute#address",
+      "name": "libcloud-demo-address",
+      "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/libcloud-demo-address",
+      "status": "RESERVED"
+    },
+    {
+      "address": "173.255.114.104",
+      "creationTimestamp": "2013-06-04T16:28:43.764-07:00",
+      "description": "",
+      "id": "11879548153827627972",
+      "kind": "compute#address",
+      "name": "testaddress",
+      "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/testaddress",
+      "status": "RESERVED"
+    }
+  ],
+  "kind": "compute#addressList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_lcaddress.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_lcaddress.json
new file mode 100644
index 0000000..5d2838e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_lcaddress.json
@@ -0,0 +1,11 @@
+{
+  "address": "173.255.113.20",
+  "creationTimestamp": "2013-06-26T12:21:40.625-07:00",
+  "description": "",
+  "id": "01531551729918243104",
+  "kind": "compute#address",
+  "name": "lcaddress",
+  "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/lcaddress",
+  "status": "RESERVED"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_lcaddress_delete.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_lcaddress_delete.json
new file mode 100644
index 0000000..afa4508
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_lcaddress_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "7128783508312083402",
+  "insertTime": "2013-06-26T12:21:44.075-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_addresses_lcaddress_delete",
+  "operationType": "delete",
+  "progress": 0,
+  "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/operations/operation-regions_us-central1_addresses_lcaddress_delete",
+  "startTime": "2013-06-26T12:21:44.110-07:00",
+  "status": "PENDING",
+  "targetId": "01531551729918243104",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/lcaddress",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_post.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_post.json
new file mode 100644
index 0000000..1242c94
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_addresses_post.json
@@ -0,0 +1,14 @@
+{
+  "id": "16064059851942653139",
+  "insertTime": "2013-06-26T12:21:40.299-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_addresses_post",
+  "operationType": "insert",
+  "progress": 0,
+  "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/operations/operation-regions_us-central1_addresses_post",
+  "startTime": "2013-06-26T12:21:40.358-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/addresses/lcaddress",
+  "user": "897001307951@developer.gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones.json b/libcloud/test/compute/fixtures/gce/zones.json
new file mode 100644
index 0000000..2c45d56
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones.json
@@ -0,0 +1,207 @@
+{
+  "id": "projects/project_name/zones",
+  "items": [
+    {
+      "creationTimestamp": "2013-02-05T16:19:23.254-08:00",
+      "description": "europe-west1-a",
+      "id": "13416642339679437530",
+      "kind": "compute#zone",
+      "maintenanceWindows": [
+        {
+          "beginTime": "2013-08-03T12:00:00.000-07:00",
+          "description": "maintenance zone",
+          "endTime": "2013-08-18T12:00:00.000-07:00",
+          "name": "2013-08-03-planned-outage"
+        }
+      ],
+      "name": "europe-west1-a",
+      "quotas": [
+        {
+          "limit": 8.0,
+          "metric": "INSTANCES",
+          "usage": 3.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "CPUS",
+          "usage": 3.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "DISKS",
+          "usage": 1.0
+        },
+        {
+          "limit": 1024.0,
+          "metric": "DISKS_TOTAL_GB",
+          "usage": 10.0
+        }
+      ],
+      "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/europe-west1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a",
+      "status": "UP"
+    },
+    {
+      "creationTimestamp": "2013-02-05T16:19:23.254-08:00",
+      "description": "europe-west1-b",
+      "id": "20623650177407096",
+      "kind": "compute#zone",
+      "maintenanceWindows": [
+        {
+          "beginTime": "2013-09-28T12:00:00.000-07:00",
+          "description": "maintenance zone",
+          "endTime": "2013-10-13T12:00:00.000-07:00",
+          "name": "2013-09-28-planned-outage"
+        }
+      ],
+      "name": "europe-west1-b",
+      "quotas": [
+        {
+          "limit": 8.0,
+          "metric": "INSTANCES",
+          "usage": 0.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "CPUS",
+          "usage": 0.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "DISKS",
+          "usage": 0.0
+        },
+        {
+          "limit": 1024.0,
+          "metric": "DISKS_TOTAL_GB",
+          "usage": 0.0
+        }
+      ],
+      "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/europe-west1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-b",
+      "status": "UP"
+    },
+    {
+      "creationTimestamp": "2013-02-05T16:19:23.269-08:00",
+      "description": "us-central1-a",
+      "id": "13462829244527433283",
+      "kind": "compute#zone",
+      "maintenanceWindows": [
+        {
+          "beginTime": "2013-08-17T12:00:00.000-07:00",
+          "description": "maintenance zone",
+          "endTime": "2013-09-01T12:00:00.000-07:00",
+          "name": "2013-08-17-planned-outage"
+        }
+      ],
+      "name": "us-central1-a",
+      "quotas": [
+        {
+          "limit": 8.0,
+          "metric": "INSTANCES",
+          "usage": 4.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "CPUS",
+          "usage": 4.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "DISKS",
+          "usage": 2.0
+        },
+        {
+          "limit": 1024.0,
+          "metric": "DISKS_TOTAL_GB",
+          "usage": 20.0
+        }
+      ],
+      "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a",
+      "status": "UP"
+    },
+    {
+      "creationTimestamp": "2013-02-05T16:19:23.269-08:00",
+      "description": "us-central1-b",
+      "id": "1045862591201432620",
+      "kind": "compute#zone",
+      "maintenanceWindows": [
+        {
+          "beginTime": "2013-10-26T12:00:00.000-07:00",
+          "description": "maintenance zone",
+          "endTime": "2013-11-10T12:00:00.000-08:00",
+          "name": "2013-10-26-planned-outage"
+        }
+      ],
+      "name": "us-central1-b",
+      "quotas": [
+        {
+          "limit": 8.0,
+          "metric": "INSTANCES",
+          "usage": 0.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "CPUS",
+          "usage": 0.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "DISKS",
+          "usage": 0.0
+        },
+        {
+          "limit": 1024.0,
+          "metric": "DISKS_TOTAL_GB",
+          "usage": 0.0
+        }
+      ],
+      "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b",
+      "status": "UP"
+    },
+    {
+      "creationTimestamp": "2013-02-05T16:19:23.257-08:00",
+      "description": "us-central2-a",
+      "id": "1001467574647549152",
+      "kind": "compute#zone",
+      "maintenanceWindows": [
+        {
+          "beginTime": "2013-10-12T12:00:00.000-07:00",
+          "description": "maintenance zone",
+          "endTime": "2013-10-27T12:00:00.000-07:00",
+          "name": "2013-10-12-planned-outage"
+        }
+      ],
+      "name": "us-central2-a",
+      "quotas": [
+        {
+          "limit": 8.0,
+          "metric": "INSTANCES",
+          "usage": 0.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "CPUS",
+          "usage": 0.0
+        },
+        {
+          "limit": 8.0,
+          "metric": "DISKS",
+          "usage": 0.0
+        },
+        {
+          "limit": 1024.0,
+          "metric": "DISKS_TOTAL_GB",
+          "usage": 0.0
+        }
+      ],
+      "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central2",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central2-a",
+      "status": "UP"
+    }
+  ],
+  "kind": "compute#zoneList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_instances.json b/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_instances.json
new file mode 100644
index 0000000..5965c46
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_instances.json
@@ -0,0 +1,145 @@
+{
+  "id": "projects/project_name/zones/europe-west1-a/instances",
+  "items": [
+    {
+      "canIpForward": false,
+      "creationTimestamp": "2013-06-26T15:13:38.295-07:00",
+      "disks": [
+        {
+          "index": 0,
+          "kind": "compute#attachedDisk",
+          "mode": "READ_WRITE",
+          "type": "SCRATCH"
+        }
+      ],
+      "id": "4658881585544531189",
+      "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "kind": "compute#instance",
+      "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1",
+      "metadata": {
+        "fingerprint": "42WmSpB8rSM=",
+        "kind": "compute#metadata"
+      },
+      "name": "libcloud-demo-europe-multiple-nodes-000",
+      "networkInterfaces": [
+        {
+          "accessConfigs": [
+            {
+              "kind": "compute#accessConfig",
+              "name": "External NAT",
+              "natIP": "192.158.29.167",
+              "type": "ONE_TO_ONE_NAT"
+            }
+          ],
+          "name": "nic0",
+          "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+          "networkIP": "10.240.144.78"
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-multiple-nodes-000",
+      "status": "RUNNING",
+      "tags": {
+        "fingerprint": "W7t6ZyTyIrc=",
+        "items": [
+          "libcloud"
+        ]
+      },
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+    },
+    {
+      "canIpForward": false,
+      "creationTimestamp": "2013-06-26T15:12:29.726-07:00",
+      "disks": [
+        {
+          "index": 0,
+          "kind": "compute#attachedDisk",
+          "mode": "READ_WRITE",
+          "type": "SCRATCH"
+        }
+      ],
+      "id": "14308265828754333159",
+      "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "kind": "compute#instance",
+      "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1",
+      "metadata": {
+        "fingerprint": "42WmSpB8rSM=",
+        "kind": "compute#metadata"
+      },
+      "name": "libcloud-demo-europe-np-node",
+      "networkInterfaces": [
+        {
+          "accessConfigs": [
+            {
+              "kind": "compute#accessConfig",
+              "name": "External NAT",
+              "natIP": "192.158.29.88",
+              "type": "ONE_TO_ONE_NAT"
+            }
+          ],
+          "name": "nic0",
+          "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+          "networkIP": "10.240.66.77"
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-np-node",
+      "status": "RUNNING",
+      "tags": {
+        "fingerprint": "W7t6ZyTyIrc=",
+        "items": [
+          "libcloud"
+        ]
+      },
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+    },
+    {
+      "canIpForward": false,
+      "creationTimestamp": "2013-06-26T15:13:21.549-07:00",
+      "disks": [
+        {
+          "boot": true,
+          "deviceName": "libcloud-demo-europe-boot-disk",
+          "index": 0,
+          "kind": "compute#attachedDisk",
+          "mode": "READ_WRITE",
+          "source": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/disks/libcloud-demo-europe-boot-disk",
+          "type": "PERSISTENT"
+        }
+      ],
+      "id": "0681789716029574243",
+      "kernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130603",
+      "kind": "compute#instance",
+      "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1",
+      "metadata": {
+        "fingerprint": "42WmSpB8rSM=",
+        "kind": "compute#metadata"
+      },
+      "name": "libcloud-demo-europe-persist-node",
+      "networkInterfaces": [
+        {
+          "accessConfigs": [
+            {
+              "kind": "compute#accessConfig",
+              "name": "External NAT",
+              "natIP": "192.158.29.121",
+              "type": "ONE_TO_ONE_NAT"
+            }
+          ],
+          "name": "nic0",
+          "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+          "networkIP": "10.240.206.91"
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-persist-node",
+      "status": "RUNNING",
+      "tags": {
+        "fingerprint": "W7t6ZyTyIrc=",
+        "items": [
+          "libcloud"
+        ]
+      },
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+    }
+  ],
+  "kind": "compute#instanceList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_instances_post.json b/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_instances_post.json
new file mode 100644
index 0000000..8a5813d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_instances_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "1510575454210533141",
+  "insertTime": "2013-06-26T20:57:34.366-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_europe-west1-a_instances_post",
+  "operationType": "insert",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/operations/operation-zones_europe-west1-a_instances_post",
+  "startTime": "2013-06-26T20:57:34.453-07:00",
+  "status": "PENDING",
+  "targetId": "14308265828754333159",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/instances/libcloud-demo-europe-np-node",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_machineTypes_n1-standard-1.json b/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_machineTypes_n1-standard-1.json
new file mode 100644
index 0000000..f66e26c
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_europe-west1-a_machineTypes_n1-standard-1.json
@@ -0,0 +1,14 @@
+{
+  "creationTimestamp": "2012-06-07T13:48:14.670-07:00",
+  "description": "1 vCPU, 3.75 GB RAM",
+  "guestCpus": 1,
+  "id": "11077240422128681563",
+  "imageSpaceGb": 10,
+  "kind": "compute#machineType",
+  "maximumPersistentDisks": 16,
+  "maximumPersistentDisksSizeGb": "10240",
+  "memoryMb": 3840,
+  "name": "n1-standard-1",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/europe-west1-a/machineTypes/n1-standard-1",
+  "zone": "europe-west1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a.json
new file mode 100644
index 0000000..5fae313
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a.json
@@ -0,0 +1,40 @@
+{
+  "creationTimestamp": "2013-02-05T16:19:23.269-08:00",
+  "description": "us-central1-a",
+  "id": "13462829244527433283",
+  "kind": "compute#zone",
+  "maintenanceWindows": [
+    {
+      "beginTime": "2013-08-17T12:00:00.000-07:00",
+      "description": "maintenance zone",
+      "endTime": "2013-09-01T12:00:00.000-07:00",
+      "name": "2013-08-17-planned-outage"
+    }
+  ],
+  "name": "us-central1-a",
+  "quotas": [
+    {
+      "limit": 8.0,
+      "metric": "INSTANCES",
+      "usage": 4.0
+    },
+    {
+      "limit": 8.0,
+      "metric": "CPUS",
+      "usage": 4.0
+    },
+    {
+      "limit": 8.0,
+      "metric": "DISKS",
+      "usage": 2.0
+    },
+    {
+      "limit": 1024.0,
+      "metric": "DISKS_TOTAL_GB",
+      "usage": 20.0
+    }
+  ],
+  "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a",
+  "status": "UP"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks.json
new file mode 100644
index 0000000..fad39f7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks.json
@@ -0,0 +1,37 @@
+{
+  "id": "projects/project_name/zones/us-central1-a/disks",
+  "items": [
+    {
+      "creationTimestamp": "2013-06-26T10:06:04.007-07:00",
+      "id": "16109451798967042451",
+      "kind": "compute#disk",
+      "name": "lcdisk",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/lcdisk",
+      "sizeGb": "1",
+      "status": "READY",
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+    },
+    {
+      "creationTimestamp": "2013-06-26T09:47:09.178-07:00",
+      "id": "10880026303683859871",
+      "kind": "compute#disk",
+      "name": "libcloud-demo-boot-disk",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/libcloud-demo-boot-disk",
+      "sizeGb": "10",
+      "status": "READY",
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+    },
+    {
+      "creationTimestamp": "2013-06-25T10:57:34.305-07:00",
+      "id": "14383387450728762434",
+      "kind": "compute#disk",
+      "name": "test-disk",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/test-disk",
+      "sizeGb": "10",
+      "status": "READY",
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+    }
+  ],
+  "kind": "compute#diskList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_lcdisk.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_lcdisk.json
new file mode 100644
index 0000000..85d7e31
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_lcdisk.json
@@ -0,0 +1,10 @@
+{
+  "creationTimestamp": "2013-06-26T10:06:04.007-07:00",
+  "id": "16109451798967042451",
+  "kind": "compute#disk",
+  "name": "lcdisk",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/lcdisk",
+  "sizeGb": "1",
+  "status": "READY",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_lcdisk_delete.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_lcdisk_delete.json
new file mode 100644
index 0000000..024da2a
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_lcdisk_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "06887337364510109333",
+  "insertTime": "2013-06-26T10:06:11.835-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_disks_lcdisk_delete",
+  "operationType": "delete",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_disks_lcdisk_delete",
+  "startTime": "2013-06-26T10:06:12.006-07:00",
+  "status": "PENDING",
+  "targetId": "16109451798967042451",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/lcdisk",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_post.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_post.json
new file mode 100644
index 0000000..51cf6c0
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_disks_post.json
@@ -0,0 +1,14 @@
+{
+  "id": "0211151278250678078",
+  "insertTime": "2013-06-26T16:48:17.403-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_disks_post",
+  "operationType": "insert",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_disks_post",
+  "startTime": "2013-06-26T16:48:17.479-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/lcdisk",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances.json
new file mode 100644
index 0000000..1cbb5c6
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances.json
@@ -0,0 +1,232 @@
+{
+  "id": "projects/project_name/zones/us-central1-a/instances",
+  "items": [
+    {
+      "canIpForward": false,
+      "creationTimestamp": "2013-06-26T15:00:12.021-07:00",
+      "disks": [
+        {
+          "index": 0,
+          "kind": "compute#attachedDisk",
+          "mode": "READ_WRITE",
+          "type": "SCRATCH"
+        }
+      ],
+      "id": "1845312225624811608",
+      "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "kind": "compute#instance",
+      "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+      "metadata": {
+        "fingerprint": "42WmSpB8rSM=",
+        "kind": "compute#metadata"
+      },
+      "name": "node-name",
+      "networkInterfaces": [
+        {
+          "accessConfigs": [
+            {
+              "kind": "compute#accessConfig",
+              "name": "External NAT",
+              "natIP": "173.255.115.146",
+              "type": "ONE_TO_ONE_NAT"
+            }
+          ],
+          "name": "nic0",
+          "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+          "networkIP": "10.240.113.94"
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+      "status": "RUNNING",
+      "tags": {
+        "fingerprint": "42WmSpB8rSM="
+      },
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+    },
+    {
+      "canIpForward": false,
+      "creationTimestamp": "2013-06-26T15:11:19.247-07:00",
+      "disks": [
+        {
+          "index": 0,
+          "kind": "compute#attachedDisk",
+          "mode": "READ_WRITE",
+          "type": "SCRATCH"
+        }
+      ],
+      "id": "8573880455005118258",
+      "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "kind": "compute#instance",
+      "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+      "metadata": {
+        "fingerprint": "42WmSpB8rSM=",
+        "kind": "compute#metadata"
+      },
+      "name": "libcloud-demo-multiple-nodes-000",
+      "networkInterfaces": [
+        {
+          "accessConfigs": [
+            {
+              "kind": "compute#accessConfig",
+              "name": "External NAT",
+              "natIP": "108.59.81.107",
+              "type": "ONE_TO_ONE_NAT"
+            }
+          ],
+          "name": "nic0",
+          "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+          "networkIP": "10.240.224.165"
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/libcloud-demo-multiple-nodes-000",
+      "status": "RUNNING",
+      "tags": {
+        "fingerprint": "W7t6ZyTyIrc=",
+        "items": [
+          "libcloud"
+        ]
+      },
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+    },
+    {
+      "canIpForward": false,
+      "creationTimestamp": "2013-06-26T15:11:19.662-07:00",
+      "disks": [
+        {
+          "index": 0,
+          "kind": "compute#attachedDisk",
+          "mode": "READ_WRITE",
+          "type": "SCRATCH"
+        }
+      ],
+      "id": "17221721898919682654",
+      "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "kind": "compute#instance",
+      "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+      "metadata": {
+        "fingerprint": "42WmSpB8rSM=",
+        "kind": "compute#metadata"
+      },
+      "name": "libcloud-demo-multiple-nodes-001",
+      "networkInterfaces": [
+        {
+          "accessConfigs": [
+            {
+              "kind": "compute#accessConfig",
+              "name": "External NAT",
+              "natIP": "108.59.81.166",
+              "type": "ONE_TO_ONE_NAT"
+            }
+          ],
+          "name": "nic0",
+          "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+          "networkIP": "10.240.223.109"
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/libcloud-demo-multiple-nodes-001",
+      "status": "RUNNING",
+      "tags": {
+        "fingerprint": "W7t6ZyTyIrc=",
+        "items": [
+          "libcloud"
+        ]
+      },
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+    },
+    {
+      "canIpForward": false,
+      "creationTimestamp": "2013-06-26T15:10:09.700-07:00",
+      "disks": [
+        {
+          "index": 0,
+          "kind": "compute#attachedDisk",
+          "mode": "READ_WRITE",
+          "type": "SCRATCH"
+        }
+      ],
+      "id": "03138438763739542377",
+      "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+      "kind": "compute#instance",
+      "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+      "metadata": {
+        "fingerprint": "42WmSpB8rSM=",
+        "kind": "compute#metadata"
+      },
+      "name": "libcloud-demo-np-node",
+      "networkInterfaces": [
+        {
+          "accessConfigs": [
+            {
+              "kind": "compute#accessConfig",
+              "name": "External NAT",
+              "natIP": "108.59.80.244",
+              "type": "ONE_TO_ONE_NAT"
+            }
+          ],
+          "name": "nic0",
+          "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+          "networkIP": "10.240.147.18"
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/libcloud-demo-np-node",
+      "status": "RUNNING",
+      "tags": {
+        "fingerprint": "W7t6ZyTyIrc=",
+        "items": [
+          "libcloud"
+        ]
+      },
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+    },
+    {
+      "canIpForward": false,
+      "creationTimestamp": "2013-06-26T15:11:02.386-07:00",
+      "disks": [
+        {
+          "boot": true,
+          "deviceName": "libcloud-demo-boot-disk",
+          "index": 0,
+          "kind": "compute#attachedDisk",
+          "mode": "READ_WRITE",
+          "source": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/disks/libcloud-demo-boot-disk",
+          "type": "PERSISTENT"
+        }
+      ],
+      "id": "2378270030714524465",
+      "kernel": "https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130603",
+      "kind": "compute#instance",
+      "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+      "metadata": {
+        "fingerprint": "42WmSpB8rSM=",
+        "kind": "compute#metadata"
+      },
+      "name": "libcloud-demo-persist-node",
+      "networkInterfaces": [
+        {
+          "accessConfigs": [
+            {
+              "kind": "compute#accessConfig",
+              "name": "External NAT",
+              "natIP": "108.59.81.66",
+              "type": "ONE_TO_ONE_NAT"
+            }
+          ],
+          "name": "nic0",
+          "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+          "networkIP": "10.240.192.190"
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/libcloud-demo-persist-node",
+      "status": "RUNNING",
+      "tags": {
+        "fingerprint": "W7t6ZyTyIrc=",
+        "items": [
+          "libcloud"
+        ]
+      },
+      "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+    }
+  ],
+  "kind": "compute#instanceList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances"
+}
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-000.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-000.json
new file mode 100644
index 0000000..0e5ef0d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-000.json
@@ -0,0 +1,42 @@
+{
+  "canIpForward": false,
+  "creationTimestamp": "2013-06-26T16:12:30.443-07:00",
+  "disks": [
+    {
+      "index": 0,
+      "kind": "compute#attachedDisk",
+      "mode": "READ_WRITE",
+      "type": "SCRATCH"
+    }
+  ],
+  "id": "5390075309006132922",
+  "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+  "kind": "compute#instance",
+  "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+  "metadata": {
+    "fingerprint": "42WmSpB8rSM=",
+    "kind": "compute#metadata"
+  },
+  "name": "lcnode-000",
+  "networkInterfaces": [
+    {
+      "accessConfigs": [
+        {
+          "kind": "compute#accessConfig",
+          "name": "External NAT",
+          "natIP": "108.59.81.107",
+          "type": "ONE_TO_ONE_NAT"
+        }
+      ],
+      "name": "nic0",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+      "networkIP": "10.240.106.153"
+    }
+  ],
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/lcnode-000",
+  "status": "RUNNING",
+  "tags": {
+    "fingerprint": "42WmSpB8rSM="
+  },
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-000_delete.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-000_delete.json
new file mode 100644
index 0000000..b588ffe
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-000_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "3319596145594427549",
+  "insertTime": "2013-06-26T16:13:12.903-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_lcnode-000_delete",
+  "operationType": "delete",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_lcnode-000_delete",
+  "startTime": "2013-06-26T16:13:12.948-07:00",
+  "status": "PENDING",
+  "targetId": "5390075309006132922",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/lcnode-000",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-001.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-001.json
new file mode 100644
index 0000000..203e261
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-001.json
@@ -0,0 +1,42 @@
+{
+  "canIpForward": false,
+  "creationTimestamp": "2013-06-26T16:12:51.782-07:00",
+  "disks": [
+    {
+      "index": 0,
+      "kind": "compute#attachedDisk",
+      "mode": "READ_WRITE",
+      "type": "SCRATCH"
+    }
+  ],
+  "id": "16630486471904253898",
+  "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+  "kind": "compute#instance",
+  "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+  "metadata": {
+    "fingerprint": "42WmSpB8rSM=",
+    "kind": "compute#metadata"
+  },
+  "name": "lcnode-001",
+  "networkInterfaces": [
+    {
+      "accessConfigs": [
+        {
+          "kind": "compute#accessConfig",
+          "name": "External NAT",
+          "natIP": "108.59.81.166",
+          "type": "ONE_TO_ONE_NAT"
+        }
+      ],
+      "name": "nic0",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+      "networkIP": "10.240.96.232"
+    }
+  ],
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/lcnode-001",
+  "status": "RUNNING",
+  "tags": {
+    "fingerprint": "42WmSpB8rSM="
+  },
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-001_delete.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-001_delete.json
new file mode 100644
index 0000000..dcbed4c
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_lcnode-001_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "17469711273432628502",
+  "insertTime": "2013-06-26T16:13:40.579-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_lcnode-001_delete",
+  "operationType": "delete",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_lcnode-001_delete",
+  "startTime": "2013-06-26T16:13:40.620-07:00",
+  "status": "PENDING",
+  "targetId": "16630486471904253898",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/lcnode-001",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name.json
new file mode 100644
index 0000000..396cff1
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name.json
@@ -0,0 +1,42 @@
+{
+  "canIpForward": false,
+  "creationTimestamp": "2013-06-26T15:00:12.021-07:00",
+  "disks": [
+    {
+      "index": 0,
+      "kind": "compute#attachedDisk",
+      "mode": "READ_WRITE",
+      "type": "SCRATCH"
+    }
+  ],
+  "id": "1845312225624811608",
+  "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130617",
+  "kind": "compute#instance",
+  "machineType": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+  "metadata": {
+    "fingerprint": "42WmSpB8rSM=",
+    "kind": "compute#metadata"
+  },
+  "name": "node-name",
+  "networkInterfaces": [
+    {
+      "accessConfigs": [
+        {
+          "kind": "compute#accessConfig",
+          "name": "External NAT",
+          "natIP": "173.255.115.146",
+          "type": "ONE_TO_ONE_NAT"
+        }
+      ],
+      "name": "nic0",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+      "networkIP": "10.240.113.94"
+    }
+  ],
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "status": "RUNNING",
+  "tags": {
+    "fingerprint": "42WmSpB8rSM="
+  },
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_attachDisk_post.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_attachDisk_post.json
new file mode 100644
index 0000000..01b756b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_attachDisk_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "7455886659787654716",
+  "insertTime": "2013-06-26T16:48:27.691-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_attachDisk_post",
+  "operationType": "attachDisk",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_attachDisk_post",
+  "startTime": "2013-06-26T16:48:27.762-07:00",
+  "status": "PENDING",
+  "targetId": "1845312225624811608",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_delete.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_delete.json
new file mode 100644
index 0000000..a6542aa
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "6999931397447918763",
+  "insertTime": "2013-06-26T10:05:40.350-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_delete",
+  "operationType": "delete",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_delete",
+  "startTime": "2013-06-26T10:05:40.405-07:00",
+  "status": "PENDING",
+  "targetId": "07410051435384876224",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_detachDisk_post.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_detachDisk_post.json
new file mode 100644
index 0000000..2595e52
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_detachDisk_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "3921383727105838816",
+  "insertTime": "2013-06-26T16:48:35.357-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_detachDisk_post",
+  "operationType": "detachDisk",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_detachDisk_post",
+  "startTime": "2013-06-26T16:48:35.398-07:00",
+  "status": "PENDING",
+  "targetId": "1845312225624811608",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_reset_post.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_reset_post.json
new file mode 100644
index 0000000..94bc8dd
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_reset_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "10507122129283663728",
+  "insertTime": "2013-06-26T15:03:02.766-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_reset_post",
+  "operationType": "reset",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_reset_post",
+  "startTime": "2013-06-26T15:03:02.813-07:00",
+  "status": "PENDING",
+  "targetId": "1845312225624811608",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_setTags_post.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_setTags_post.json
new file mode 100644
index 0000000..23ee9cc
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_node-name_setTags_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "8115150846190320932",
+  "insertTime": "2013-06-26T21:20:03.962-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_node-name_setTags_post",
+  "operationType": "setTags",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_node-name_setTags_post",
+  "startTime": "2013-06-26T21:20:04.103-07:00",
+  "status": "PENDING",
+  "targetId": "1845312225624811608",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/node-name",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_post.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_post.json
new file mode 100644
index 0000000..d03c4b0
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instances_post.json
@@ -0,0 +1,14 @@
+{
+  "id": "1858155812259649243",
+  "insertTime": "2013-06-26T16:12:51.492-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_instances_post",
+  "operationType": "insert",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_instances_post",
+  "startTime": "2013-06-26T16:12:51.537-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/instances/lcnode-001",
+  "user": "897001307951@developer.gserviceaccount.com",
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_machineTypes.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_machineTypes.json
new file mode 100644
index 0000000..3b0b2a5
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_machineTypes.json
@@ -0,0 +1,374 @@
+{
+  "id": "projects/project_name/zones/us-central1-a/machineTypes",
+  "items": [
+    {
+      "creationTimestamp": "2013-04-25T13:32:49.088-07:00",
+      "description": "1 vCPU (shared physical core) and 0.6 GB RAM",
+      "guestCpus": 1,
+      "id": "1133568312750571513",
+      "imageSpaceGb": 0,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 4,
+      "maximumPersistentDisksSizeGb": "3072",
+      "memoryMb": 614,
+      "name": "f1-micro",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/f1-micro",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2013-04-25T13:32:45.550-07:00",
+      "description": "1 vCPU (shared physical core) and 1.7 GB RAM",
+      "guestCpus": 1,
+      "id": "1500265464823777597",
+      "imageSpaceGb": 0,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 4,
+      "maximumPersistentDisksSizeGb": "3072",
+      "memoryMb": 1740,
+      "name": "g1-small",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/g1-small",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:46:10.572-08:00",
+      "description": "2 vCPUs, 1.8 GB RAM",
+      "guestCpus": 2,
+      "id": "16898271314080235997",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 1843,
+      "name": "n1-highcpu-2",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-2",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:47:07.825-08:00",
+      "description": "2 vCPUs, 1.8 GB RAM, 1 scratch disk (870 GB)",
+      "guestCpus": 2,
+      "id": "15178384466070744001",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 1843,
+      "name": "n1-highcpu-2-d",
+      "scratchDisks": [
+        {
+          "diskGb": 870
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-2-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:48:06.087-08:00",
+      "description": "4 vCPUs, 3.6 GB RAM",
+      "guestCpus": 4,
+      "id": "04759000181765218034",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 3686,
+      "name": "n1-highcpu-4",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-4",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:49:07.563-08:00",
+      "description": "4 vCPUS, 3.6 GB RAM, 1 scratch disk (1770 GB)",
+      "guestCpus": 4,
+      "id": "01151097524490134507",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 3686,
+      "name": "n1-highcpu-4-d",
+      "scratchDisks": [
+        {
+          "diskGb": 1770
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-4-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:50:15.128-08:00",
+      "description": "8 vCPUs, 7.2 GB RAM",
+      "guestCpus": 8,
+      "id": "01206886442411821831",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 7373,
+      "name": "n1-highcpu-8",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-8",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:51:04.549-08:00",
+      "description": "8 vCPUS, 7.2 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+      "guestCpus": 8,
+      "id": "02507333096579477005",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 7373,
+      "name": "n1-highcpu-8-d",
+      "scratchDisks": [
+        {
+          "diskGb": 1770
+        },
+        {
+          "diskGb": 1770
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highcpu-8-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:40:06.129-08:00",
+      "description": "2 vCPUs, 13 GB RAM",
+      "guestCpus": 2,
+      "id": "05438694236916301519",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 13312,
+      "name": "n1-highmem-2",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-2",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:40:59.630-08:00",
+      "description": "2 vCPUs, 13 GB RAM, 1 scratch disk (870 GB)",
+      "guestCpus": 2,
+      "id": "00770157291441082211",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 13312,
+      "name": "n1-highmem-2-d",
+      "scratchDisks": [
+        {
+          "diskGb": 870
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-2-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:42:08.983-08:00",
+      "description": "4 vCPUs, 26 GB RAM",
+      "guestCpus": 4,
+      "id": "11556032176405786676",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 26624,
+      "name": "n1-highmem-4",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-4",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:43:17.400-08:00",
+      "description": "4 vCPUs, 26 GB RAM, 1 scratch disk (1770 GB)",
+      "guestCpus": 4,
+      "id": "05095504563332567951",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 26624,
+      "name": "n1-highmem-4-d",
+      "scratchDisks": [
+        {
+          "diskGb": 1770
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-4-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:44:25.985-08:00",
+      "description": "8 vCPUs, 52 GB RAM",
+      "guestCpus": 8,
+      "id": "01717932668777642040",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 53248,
+      "name": "n1-highmem-8",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-8",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-11-16T11:45:08.195-08:00",
+      "description": "8 vCPUs, 52 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+      "guestCpus": 8,
+      "id": "07181827135536388552",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 53248,
+      "name": "n1-highmem-8-d",
+      "scratchDisks": [
+        {
+          "diskGb": 1770
+        },
+        {
+          "diskGb": 1770
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-highmem-8-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-06-07T13:48:14.670-07:00",
+      "description": "1 vCPU, 3.75 GB RAM",
+      "guestCpus": 1,
+      "id": "11077240422128681563",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 3840,
+      "name": "n1-standard-1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-06-07T13:48:34.258-07:00",
+      "description": "1 vCPU, 3.75 GB RAM, 1 scratch disk (420 GB)",
+      "guestCpus": 1,
+      "id": "10583029372018866711",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 3840,
+      "name": "n1-standard-1-d",
+      "scratchDisks": [
+        {
+          "diskGb": 420
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-06-07T13:48:56.867-07:00",
+      "description": "2 vCPUs, 7.5 GB RAM",
+      "guestCpus": 2,
+      "id": "17936898073622676356",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 7680,
+      "name": "n1-standard-2",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-2",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-06-07T13:49:19.448-07:00",
+      "description": "2 vCPUs, 7.5 GB RAM, 1 scratch disk (870 GB)",
+      "guestCpus": 2,
+      "id": "06313284160910191442",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 7680,
+      "name": "n1-standard-2-d",
+      "scratchDisks": [
+        {
+          "diskGb": 870
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-2-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-06-07T13:49:40.050-07:00",
+      "description": "4 vCPUs, 15 GB RAM",
+      "guestCpus": 4,
+      "id": "09494636486174545828",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 15360,
+      "name": "n1-standard-4",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-4",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-06-07T13:50:05.677-07:00",
+      "description": "4 vCPUs, 15 GB RAM, 1 scratch disk (1770 GB)",
+      "guestCpus": 4,
+      "id": "00523085164784013586",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 15360,
+      "name": "n1-standard-4-d",
+      "scratchDisks": [
+        {
+          "diskGb": 1770
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-4-d",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-06-07T13:50:42.334-07:00",
+      "description": "8 vCPUs, 30 GB RAM",
+      "guestCpus": 8,
+      "id": "04084282969223214132",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 30720,
+      "name": "n1-standard-8",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-8",
+      "zone": "us-central1-a"
+    },
+    {
+      "creationTimestamp": "2012-06-07T13:51:19.936-07:00",
+      "description": "8 vCPUs, 30 GB RAM, 2 scratch disks (1770 GB, 1770 GB)",
+      "guestCpus": 8,
+      "id": "00035824420671580077",
+      "imageSpaceGb": 10,
+      "kind": "compute#machineType",
+      "maximumPersistentDisks": 16,
+      "maximumPersistentDisksSizeGb": "10240",
+      "memoryMb": 30720,
+      "name": "n1-standard-8-d",
+      "scratchDisks": [
+        {
+          "diskGb": 1770
+        },
+        {
+          "diskGb": 1770
+        }
+      ],
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-8-d",
+      "zone": "us-central1-a"
+    }
+  ],
+  "kind": "compute#machineTypeList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_machineTypes_n1-standard-1.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_machineTypes_n1-standard-1.json
new file mode 100644
index 0000000..736f4bb
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_machineTypes_n1-standard-1.json
@@ -0,0 +1,14 @@
+{
+  "creationTimestamp": "2012-06-07T13:48:14.670-07:00",
+  "description": "1 vCPU, 3.75 GB RAM",
+  "guestCpus": 1,
+  "id": "11077240422128681563",
+  "imageSpaceGb": 10,
+  "kind": "compute#machineType",
+  "maximumPersistentDisks": 16,
+  "maximumPersistentDisksSizeGb": "10240",
+  "memoryMb": 3840,
+  "name": "n1-standard-1",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-a/machineTypes/n1-standard-1",
+  "zone": "us-central1-a"
+}
\ No newline at end of file
-- 
1.8.3


From a29a4cf6e959917dd3d5277782cf592e76a311bc Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 15:46:23 -0700
Subject: [PATCH 08/14] Misc PEP8 fixes.

---
 demos/gce_demo.py             | 17 ++++++++++-------
 demos/secrets.py-dist         |  4 ++--
 libcloud/test/secrets.py-dist |  4 ++++
 3 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/demos/gce_demo.py b/demos/gce_demo.py
index afaa120..a0e9690 100755
--- a/demos/gce_demo.py
+++ b/demos/gce_demo.py
@@ -66,11 +66,13 @@ CLEANUP = True
 args = getattr(secrets, 'GCE_PARAMS', ())
 kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
 
+
 # ==== HELPER FUNCTIONS ====
 def get_gce_driver():
     driver = get_driver(Provider.GCE)(*args, datacenter=DATACENTER, **kwargs)
     return driver
 
+
 def display(title, resource_list):
     """
     Display a list of resources.
@@ -125,8 +127,8 @@ def clean_up(base_name, node_list=None, resource_list=None):
             else:
                 print('   Failed to Delete %s' % resource.name)
 
-# ==== DEMO CODE STARTS HERE ====
 
+# ==== DEMO CODE STARTS HERE ====
 def main():
     global gce
     gce = get_gce_driver()
@@ -185,14 +187,13 @@ def main():
         disk_name = '%s-attach-disk' % DEMO_BASE_NAME
         volume = gce.create_volume(1, disk_name)
         if volume.attach(node_1):
-          print ('   Attached %s to %s' % (volume.name, node_1.name))
+            print ('   Attached %s to %s' % (volume.name, node_1.name))
 
         if CLEANUP:
             # == Detach the disk ==
             if gce.detach_volume(volume, ex_node=node_1):
                 print('   Detached %s from %s' % (volume.name, node_1.name))
 
-
     # == Create Node with persistent disk ==
     print('Creating Node with Persistent disk:')
     name = '%s-persist-node' % DEMO_BASE_NAME
@@ -207,7 +208,8 @@ def main():
     # Create Node with Disk
     node_2 = gce.create_node(name, size, image, ex_tags=['libcloud'],
                              ex_boot_disk=volume)
-    print('   Node %s created with attached disk %s' % (node_2.name, volume.name))
+    print('   Node %s created with attached disk %s' % (node_2.name,
+                                                        volume.name))
 
     # == Update Tags for Node ==
     print('Updating Tags for %s' % node_2.name)
@@ -223,7 +225,8 @@ def main():
     number = MAX_NODES - 2
     if number > 0:
         print('Creating Multiple Nodes (%s):' % number)
-        multi_nodes = gce.ex_create_multiple_nodes(base_name, size, image, number,
+        multi_nodes = gce.ex_create_multiple_nodes(base_name, size, image,
+                                                   number,
                                                    ex_tags=['libcloud'])
         for node in multi_nodes:
             print('   Node %s created.' % node.name)
@@ -248,7 +251,8 @@ def main():
     print('Creating an Address:')
     name = '%s-address' % DEMO_BASE_NAME
     address_1 = gce.ex_create_address(name)
-    print('   Address %s created with IP %s' % (address_1.name, address_1.address))
+    print('   Address %s created with IP %s' % (address_1.name,
+                                                address_1.address))
 
     # == List Updated Resources in current zone/region ==
     print('Updated Resources in current zone/region:')
@@ -267,7 +271,6 @@ def main():
     networks = gce.ex_list_networks()
     display('Networks', networks)
 
-
     if CLEANUP:
         print('Cleaning up %s resources created.' % DEMO_BASE_NAME)
         clean_up(DEMO_BASE_NAME, nodes,
diff --git a/demos/secrets.py-dist b/demos/secrets.py-dist
index fd48c76..82c3de1 100644
--- a/demos/secrets.py-dist
+++ b/demos/secrets.py-dist
@@ -22,8 +22,8 @@ DREAMHOST_PARAMS = ('key',)
 EC2_PARAMS = ('access_id', 'secret')
 ECP_PARAMS = ('user_name', 'password')
 GANDI_PARAMS = ('user',)
-GCE_PARAMS = ('email_address', 'key') # Service Account Authentication
-#GCE_PARAMS = ('client_id', 'client_secret') # Installed App Authentication
+GCE_PARAMS = ('email_address', 'key')  # Service Account Authentication
+#GCE_PARAMS = ('client_id', 'client_secret')  # Installed App Authentication
 GCE_KEYWORD_PARAMS = {'project': 'project_name'}
 HOSTINGCOM_PARAMS = ('user', 'secret')
 IBM_PARAMS = ('user', 'secret')
diff --git a/libcloud/test/secrets.py-dist b/libcloud/test/secrets.py-dist
index cd6c83b..c620951 100644
--- a/libcloud/test/secrets.py-dist
+++ b/libcloud/test/secrets.py-dist
@@ -22,6 +22,10 @@ DREAMHOST_PARAMS = ('key',)
 EC2_PARAMS = ('access_id', 'secret')
 ECP_PARAMS = ('user_name', 'password')
 GANDI_PARAMS = ('user',)
+GCE_PARAMS = ('email_address', 'key')  # Service Account Authentication
+#GCE_PARAMS = ('client_id', 'client_secret')  # Installed App Authentication
+GCE_KEYWORD_PARAMS = {'project': 'project_name', 'ssh_username': None,
+                      'ssh_private_key_file': None}
 HOSTINGCOM_PARAMS = ('user', 'secret')
 IBM_PARAMS = ('user', 'secret')
 # OPENSTACK_PARAMS = ('user_name', 'api_key', secure_bool, 'host', port_int)
-- 
1.8.3


From cbb684fdf571be67ebda8067c88be850ccc4df8c Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 22:28:22 -0700
Subject: [PATCH 09/14] Additional Documentation, PEP8 fixes, and PY3 fixes.

---
 demos/gce_demo.py               |   2 +
 libcloud/common/google.py       | 120 ++++++++++++++++++++++++++++------------
 libcloud/compute/drivers/gce.py |   6 +-
 3 files changed, 91 insertions(+), 37 deletions(-)

diff --git a/demos/gce_demo.py b/demos/gce_demo.py
index a0e9690..54ae553 100755
--- a/demos/gce_demo.py
+++ b/demos/gce_demo.py
@@ -21,6 +21,8 @@
 #
 # To run interactively:
 #    - Make sure you have valid values in secrets.py
+#      (For more information about setting up your credentials, see the
+#      libcloud/common/google.py docstring)
 #    - Run 'python' in this directory, then:
 #        import gce_demo
 #        gce = gce_demo.get_gce_driver()
diff --git a/libcloud/common/google.py b/libcloud/common/google.py
index 2939a23..5c6e524 100644
--- a/libcloud/common/google.py
+++ b/libcloud/common/google.py
@@ -15,6 +15,47 @@
 
 """
 Module for Google Connection and Authentication classes.
+
+Information about setting up your Google OAUTH2 credentials:
+
+For libcloud, there are two basic methods for authenticating to Google using
+OAUTH2: Service Accounts and Client IDs for Installed Applications.
+
+Both are initially set up from the
+U{API Console<https://code.google.com/apis/console#access>}
+
+Setting up Service Account authentication (note that you need the PyCrypto
+package installed to use this):
+    - Go to the API Console
+    - Click on "Create another client ID..."
+    - Select "Service account" and click on "Create client ID"
+    - Download the Private Key
+    - The key that you download is a PKCS12 key.  It needs to be converted to
+      the PEM format.
+    - Convert the key using OpenSSL (the default password is 'notasecret'):
+      C{openssl pkcs12 -in YOURPRIVKEY.p12 -nodes -nocerts
+      | openssl rsa -out PRIV.pem}
+    - Move the .pem file to a safe location.
+    - To Authenticate, you will need to pass the Service Account's "Email
+      address" in as the user_id and the path to the .pem file as the key.
+
+Setting up Installed Application authentication:
+    - Go to the API Connsole
+    - Click on "Create another client ID..."
+    - Select "Installed application" and click on "Create client ID"
+    - To Authenticate, pass in the "Client ID" as the user_id and the "Client
+      secret" as the key
+    - The first time that you do this, the libcloud will give you a URL to
+      visit.  Copy and paste the URL into a browser.
+    - When you go to the URL it will ask you to log in (if you aren't already)
+      and ask you if you want to allow the project access to your account.
+    - Click on Accept and you will be given a code.
+    - Paste that code at the prompt given to you by the Google libcloud
+      connection.
+    - At that point, a token & refresh token will be stored in your home
+      directory and will be used for authentication.
+
+Please remember to secure your keys and access tokens.
 """
 from __future__ import with_statement
 
@@ -31,8 +72,7 @@ import datetime
 import os
 import socket
 
-from libcloud.utils.py3 import urlencode
-from libcloud.utils.py3 import urlparse
+from libcloud.utils.py3 import urlencode, urlparse, PY3
 from libcloud.common.base import (ConnectionUserAndKey, JsonResponse,
                                   PollingConnection)
 from libcloud.compute.types import (InvalidCredsError,
@@ -80,32 +120,6 @@ class GoogleBaseAuthConnection(ConnectionUserAndKey):
     host = 'accounts.google.com'
     auth_path = '/o/oauth2/auth'
 
-    def add_default_headers(self, headers):
-        headers['Content-Type'] = "application/x-www-form-urlencoded"
-        headers['Host'] = self.host
-        return headers
-
-    def _token_request(self, request_body):
-        """
-        Return an updated token from a token request body.
-
-        @param  request_body: A dictionary of values to send in the body of the
-                              token request.
-        @type   request_body: C{dict}
-
-        @return:  A dictionary with updated token information
-        @rtype:   C{dict}
-        """
-        data = urlencode(request_body)
-        now = datetime.datetime.utcnow()
-        response = self.request('/o/oauth2/token', method='POST', data=data)
-        token_info = response.object
-        if 'expires_in' in token_info:
-            expire_time = now + datetime.timedelta(
-                seconds=token_info['expires_in'])
-            token_info['expire_time'] = expire_time.strftime(TIMESTAMP_FORMAT)
-        return token_info
-
     def __init__(self, user_id, key, scope,
                  redirect_uri='urn:ietf:wg:oauth:2.0:oob',
                  login_hint=None, **kwargs):
@@ -139,6 +153,35 @@ class GoogleBaseAuthConnection(ConnectionUserAndKey):
 
         super(GoogleBaseAuthConnection, self).__init__(user_id, key, **kwargs)
 
+    def _now(self):
+        return datetime.datetime.utcnow()
+
+    def add_default_headers(self, headers):
+        headers['Content-Type'] = "application/x-www-form-urlencoded"
+        headers['Host'] = self.host
+        return headers
+
+    def _token_request(self, request_body):
+        """
+        Return an updated token from a token request body.
+
+        @param  request_body: A dictionary of values to send in the body of the
+                              token request.
+        @type   request_body: C{dict}
+
+        @return:  A dictionary with updated token information
+        @rtype:   C{dict}
+        """
+        data = urlencode(request_body)
+        now = self._now()
+        response = self.request('/o/oauth2/token', method='POST', data=data)
+        token_info = response.object
+        if 'expires_in' in token_info:
+            expire_time = now + datetime.timedelta(
+                seconds=token_info['expires_in'])
+            token_info['expire_time'] = expire_time.strftime(TIMESTAMP_FORMAT)
+        return token_info
+
 
 class GoogleInstalledAppAuthConnection(GoogleBaseAuthConnection):
     """Authentication connection for "Installed Application" authentication."""
@@ -163,7 +206,10 @@ class GoogleInstalledAppAuthConnection(GoogleBaseAuthConnection):
         url = 'https://%s%s?%s' % (self.host, self.auth_path, data)
         print('Please Go to the following URL and sign in:')
         print(url)
-        code = raw_input('Enter Code:')
+        if PY3:
+            code = input('Enter Code:')
+        else:
+            code = raw_input('Enter Code:')
         return code
 
     def get_new_token(self):
@@ -289,8 +335,9 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
     """Base connection class for interacting with Google APIs."""
     driver = GoogleBaseDriver
     responseCls = GoogleResponse
+    host = 'www.googleapis.com'
     poll_interval = 2.0
-    timeout = 60
+    timeout = 120
 
     def __init__(self, user_id, key, auth_type=None,
                  credential_file=None, **kwargs):
@@ -326,7 +373,9 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
                 auth_type = 'SA'
             else:
                 auth_type = 'IA'
-
+        if 'scope' in kwargs:
+            self.scope = kwargs['scope']
+            kwargs.pop('scope', None)
         self.token_info = self._get_token_info_from_file()
         if auth_type == 'SA':
             self.auth_conn = GoogleServiceAcctAuthConnection(
@@ -346,6 +395,9 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
 
         super(GoogleBaseConnection, self).__init__(user_id, key, **kwargs)
 
+    def _now(self):
+        return datetime.datetime.utcnow()
+
     def add_default_headers(self, headers):
         """
         @inherits: L{Connection.add_default_headers}
@@ -361,7 +413,7 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
 
         @inherits: L{Connection.pre_connect_hook}
         """
-        now = datetime.datetime.utcnow()
+        now = self._now()
         if self.token_expire_time < now:
             self.token_info = self.auth_conn.refresh_token(self.token_info)
             self.token_expire_time = datetime.datetime.strptime(
@@ -422,7 +474,7 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
         filename = os.path.realpath(os.path.expanduser(self.credential_file))
         data = json.dumps(self.token_info)
         with open(filename, 'w') as f:
-          f.write(data)
+            f.write(data)
 
     def has_completed(self, response):
         """
@@ -462,7 +514,7 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
         """
         if action.startswith('https://'):
             u = urlparse.urlsplit(action)
-            request = urlparse.urlunsplit((None, None, u[2], u[3], u[4]))
+            request = urlparse.urlunsplit(('', '', u[2], u[3], u[4]))
         else:
             request = self.request_path + action
         return request
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 4f86ef7..974555b 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -967,7 +967,7 @@ class GCENodeDriver(NodeDriver):
 
         node_list = [None] * number
         responses = []
-        for i in xrange(number):
+        for i in range(number):
             name = '%s-%03d' % (base_name, i)
             request, node_data = self._create_node_req(name, size, image,
                                                        location, ex_network,
@@ -1099,9 +1099,9 @@ class GCENodeDriver(NodeDriver):
         response = self.connection.async_request(request, method='POST',
                                                  data='ignored').object
         if 'error' in response:
-          self._categorize_error(response['error'])
+            self._categorize_error(response['error'])
         else:
-          return True
+            return True
 
     def ex_set_node_tags(self, node, tags):
         """
-- 
1.8.3


From fe2210331f395ace458449a42eb11cf5417250e6 Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 22:29:17 -0700
Subject: [PATCH 10/14] Add unittests for Google Compute Engine

---
 libcloud/test/common/test_google.py | 233 ++++++++++++
 libcloud/test/compute/test_gce.py   | 702 ++++++++++++++++++++++++++++++++++++
 2 files changed, 935 insertions(+)
 create mode 100644 libcloud/test/common/test_google.py
 create mode 100644 libcloud/test/compute/test_gce.py

diff --git a/libcloud/test/common/test_google.py b/libcloud/test/common/test_google.py
new file mode 100644
index 0000000..904dfff
--- /dev/null
+++ b/libcloud/test/common/test_google.py
@@ -0,0 +1,233 @@
+# 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.
+"""
+Tests for Google Connection classes.
+"""
+import datetime
+import sys
+import unittest
+import json
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.test import MockHttp, LibcloudTestCase, MockResponse
+from libcloud.common.google import (GoogleAuthError,
+                                    GoogleBaseAuthConnection,
+                                    GoogleInstalledAppAuthConnection,
+                                    GoogleServiceAcctAuthConnection,
+                                    GoogleBaseConnection)
+from libcloud.test.secrets import GCE_PARAMS, GCE_KEYWORD_PARAMS
+
+# Skip some tests if PyCrypto is unavailable
+try:
+    from Crypto.Hash import SHA256
+except ImportError:
+    SHA256 = None
+
+
+class MockJsonResponse(object):
+    def __init__(self, body):
+        self.object = body
+
+
+class GoogleBaseAuthConnectionTest(LibcloudTestCase):
+    """
+    Tests for GoogleBaseAuthConnection
+    """
+    GoogleBaseAuthConnection._now = lambda x: datetime.datetime(2013, 6, 26,
+                                                                19, 0, 0)
+
+    def setUp(self):
+        GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp,
+                                                 GoogleAuthMockHttp)
+        self.mock_scope = ['https://www.googleapis.com/auth/foo']
+        self.conn = GoogleInstalledAppAuthConnection(*GCE_PARAMS,
+                                                     scope=self.mock_scope)
+
+    def test_add_default_headers(self):
+        old_headers = {}
+        expected_headers = {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'Host': 'accounts.google.com'}
+        new_headers = self.conn.add_default_headers(old_headers)
+        self.assertEqual(new_headers, expected_headers)
+
+    def test_token_request(self):
+        request_body = {'code': 'asdf', 'client_id': self.conn.user_id,
+                        'client_secret': self.conn.key,
+                        'redirect_uri': self.conn.redirect_uri,
+                        'grant_type': 'authorization_code'}
+        new_token = self.conn._token_request(request_body)
+        self.assertEqual(new_token['access_token'], 'installedapp')
+        self.assertEqual(new_token['expire_time'], '2013-06-26T20:00:00Z')
+
+
+class GoogleInstalledAppAuthConnectionTest(LibcloudTestCase):
+    """
+    Tests for GoogleInstalledAppAuthConnection
+    """
+    GoogleInstalledAppAuthConnection.get_code = lambda x: '1234'
+
+    def setUp(self):
+        GoogleInstalledAppAuthConnection.conn_classes = (GoogleAuthMockHttp,
+                                                         GoogleAuthMockHttp)
+        self.mock_scope = ['https://www.googleapis.com/auth/foo']
+        self.conn = GoogleInstalledAppAuthConnection(*GCE_PARAMS,
+                                                     scope=self.mock_scope)
+
+    def test_refresh_token(self):
+        # This token info doesn't have a refresh token, so a new token will be
+        # requested
+        token_info1 = {'access_token': 'tokentoken', 'token_type': 'Bearer',
+                       'expires_in': 3600}
+        new_token1 = self.conn.refresh_token(token_info1)
+        self.assertEqual(new_token1['access_token'], 'installedapp')
+
+        # This token info has a refresh token, so it will be able to be
+        # refreshed.
+        token_info2 = {'access_token': 'tokentoken', 'token_type': 'Bearer',
+                       'expires_in': 3600, 'refresh_token': 'refreshrefresh'}
+        new_token2 = self.conn.refresh_token(token_info2)
+        self.assertEqual(new_token2['access_token'], 'refreshrefresh')
+
+        # Both sets should have refresh info
+        self.assertTrue('refresh_token' in new_token1)
+        self.assertTrue('refresh_token' in new_token2)
+
+
+class GoogleBaseConnectionTest(LibcloudTestCase):
+    """
+    Tests for GoogleBaseConnection
+    """
+    GoogleBaseConnection._get_token_info_from_file = lambda x: None
+    GoogleBaseConnection._write_token_info_to_file = lambda x: None
+    GoogleInstalledAppAuthConnection.get_code = lambda x: '1234'
+    GoogleServiceAcctAuthConnection.get_new_token = \
+        lambda x: x._token_request({})
+    GoogleBaseConnection._now = lambda x: datetime.datetime(2013, 6, 26,
+                                                            19, 0, 0)
+
+    def setUp(self):
+        GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp,
+                                                 GoogleAuthMockHttp)
+        self.mock_scope = ['https://www.googleapis.com/auth/foo']
+        self.conn = GoogleBaseConnection(*GCE_PARAMS, auth_type='IA',
+                                         scope=self.mock_scope)
+
+    def test_auth_type(self):
+
+        self.assertRaises(GoogleAuthError, GoogleBaseConnection, *GCE_PARAMS,
+                          auth_type='XX')
+        if SHA256:
+            conn1 = GoogleBaseConnection(*GCE_PARAMS, auth_type='SA',
+                                         scope=self.mock_scope)
+            self.assertTrue(isinstance(conn1.auth_conn,
+                                       GoogleServiceAcctAuthConnection))
+
+        conn2 = GoogleBaseConnection(*GCE_PARAMS, auth_type='IA',
+                                     scope=self.mock_scope)
+        self.assertTrue(isinstance(conn2.auth_conn,
+                                   GoogleInstalledAppAuthConnection))
+
+    def test_add_default_headers(self):
+        old_headers = {}
+        new_expected_headers = {'Content-Type': 'application/json',
+                                'Host': 'www.googleapis.com'}
+        new_headers = self.conn.add_default_headers(old_headers)
+        self.assertEqual(new_headers, new_expected_headers)
+
+    def test_pre_connect_hook(self):
+        old_params = {}
+        old_headers = {}
+        new_expected_params = {}
+        new_expected_headers = {'Authorization': 'Bearer installedapp'}
+        new_params, new_headers = self.conn.pre_connect_hook(old_params,
+                                                             old_headers)
+        self.assertEqual(new_params, new_expected_params)
+        self.assertEqual(new_headers, new_expected_headers)
+
+    def test_encode_data(self):
+        data = {'key': 'value'}
+        json_data = '{"key": "value"}'
+        encoded_data = self.conn.encode_data(data)
+        self.assertEqual(encoded_data, json_data)
+
+    def test_has_completed(self):
+        body1 = {"endTime": "2013-06-26T10:05:07.630-07:00",
+                 "id": "3681664092089171723",
+                 "kind": "compute#operation",
+                 "status": "DONE",
+                 "targetId": "16211908079305042870"}
+        body2 = {"endTime": "2013-06-26T10:05:07.630-07:00",
+                 "id": "3681664092089171723",
+                 "kind": "compute#operation",
+                 "status": "RUNNING",
+                 "targetId": "16211908079305042870"}
+        response1 = MockJsonResponse(body1)
+        response2 = MockJsonResponse(body2)
+        self.assertTrue(self.conn.has_completed(response1))
+        self.assertFalse(self.conn.has_completed(response2))
+
+    def test_get_poll_request_kwargs(self):
+        body = {"endTime": "2013-06-26T10:05:07.630-07:00",
+                "id": "3681664092089171723",
+                "kind": "compute#operation",
+                "selfLink": "https://www.googleapis.com/operations-test"}
+        response = MockJsonResponse(body)
+        expected_kwargs = {'action':
+                           'https://www.googleapis.com/operations-test'}
+        kwargs = self.conn.get_poll_request_kwargs(response, None, {})
+        self.assertEqual(kwargs, expected_kwargs)
+
+    def test_morph_action_hook(self):
+        self.conn.request_path = '/compute/apiver/project/project-name'
+        action1 = ('https://www.googleapis.com/compute/apiver/project'
+                   '/project-name/instances')
+        action2 = '/instances'
+        expected_request = '/compute/apiver/project/project-name/instances'
+        request1 = self.conn.morph_action_hook(action1)
+        request2 = self.conn.morph_action_hook(action2)
+        self.assertEqual(request1, expected_request)
+        self.assertEqual(request2, expected_request)
+
+
+class GoogleAuthMockHttp(MockHttp):
+    """
+    Mock HTTP Class for Google Auth Connections.
+    """
+    json_hdr = {'content-type': 'application/json; charset=UTF-8'}
+
+    def _o_oauth2_token(self, method, url, body, headers):
+        token_info = {'access_token': 'tokentoken',
+                      'token_type': 'Bearer',
+                      'expires_in': 3600}
+        refresh_token = {'access_token': 'refreshrefresh',
+                         'token_type': 'Bearer',
+                         'expires_in': 3600}
+        ia_token = {'access_token': 'installedapp',
+                    'token_type': 'Bearer',
+                    'expires_in': 3600,
+                    'refresh_token': 'refreshrefresh'}
+        if 'code' in body:
+            body = json.dumps(ia_token)
+        elif 'refresh_token' in body:
+            body = json.dumps(refresh_token)
+        else:
+            body = json.dumps(token_info)
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())
diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py
new file mode 100644
index 0000000..d904559
--- /dev/null
+++ b/libcloud/test/compute/test_gce.py
@@ -0,0 +1,702 @@
+# 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.
+"""
+Tests for Google Compute Engine Driver
+"""
+import sys
+import unittest
+import datetime
+
+from libcloud.utils.py3 import httplib
+from libcloud.compute.drivers.gce import (GCENodeDriver, API_VERSION,
+                                          timestamp_to_datetime,
+                                          GCEAddress, GCEFirewall, GCENetwork,
+                                          GCENodeSize, GCEProject, GCEZone,
+                                          GCEError, ResourceExistsError,
+                                          QuotaExceededError)
+from libcloud.common.google import (GoogleBaseAuthConnection,
+                                    GoogleInstalledAppAuthConnection,
+                                    GoogleBaseConnection)
+from libcloud.test.common.test_google import GoogleAuthMockHttp
+from libcloud.compute.base import (Node, NodeImage, NodeSize, NodeLocation,
+                                   StorageVolume)
+
+from libcloud.test import MockHttpTestCase, LibcloudTestCase
+from libcloud.test.compute import TestCaseMixin
+from libcloud.test.file_fixtures import ComputeFileFixtures
+
+from libcloud.test.secrets import GCE_PARAMS, GCE_KEYWORD_PARAMS
+
+
+class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
+    """
+    Google Compute Engine Test Class.
+    """
+    # Mock out a few specific calls that interact with the user, system or
+    # environment.
+    GoogleBaseConnection._get_token_info_from_file = lambda x: None
+    GoogleBaseConnection._write_token_info_to_file = lambda x: None
+    GoogleInstalledAppAuthConnection.get_code = lambda x: '1234'
+    GCEZone._now = lambda x: datetime.datetime(2013, 6, 26, 19, 0, 0)
+    datacenter = 'us-central1-a'
+
+    def setUp(self):
+        GCEMockHttp.test = self
+        GCENodeDriver.connectionCls.conn_classes = (GCEMockHttp, GCEMockHttp)
+        GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp,
+                                                 GoogleAuthMockHttp)
+        GCEMockHttp.type = None
+        self.driver = GCENodeDriver(*GCE_PARAMS, auth_type='IA',
+                                    datacenter=self.datacenter,
+                                    **GCE_KEYWORD_PARAMS)
+
+    def test_timestamp_to_datetime(self):
+        timestamp1 = '2013-06-26T10:05:19.340-07:00'
+        datetime1 = datetime.datetime(2013, 6, 26, 17, 5, 19, 340000)
+        self.assertEqual(timestamp_to_datetime(timestamp1), datetime1)
+        timestamp2 = '2013-06-26T17:43:15.000-00:00'
+        datetime2 = datetime.datetime(2013, 6, 26, 17, 43, 15)
+        self.assertEqual(timestamp_to_datetime(timestamp2), datetime2)
+
+    def test_find_zone(self):
+        zone1 = self.driver._find_zone('libcloud-demo-np-node', 'instances')
+        self.assertEqual(zone1, 'us-central1-a')
+        zone2 = self.driver._find_zone('libcloud-demo-europe-np-node',
+                                       'instances')
+        self.assertEqual(zone2, 'europe-west1-a')
+        region = self.driver._find_zone('libcloud-demo-address', 'addresses',
+                                        region=True)
+        self.assertEqual(region, 'us-central1')
+
+    def test_match_images(self):
+        project = 'debian-cloud'
+        image = self.driver._match_images(project, 'debian-7')
+        self.assertEqual(image.name, 'debian-7-wheezy-v20130617')
+        image = self.driver._match_images(project, 'debian-6')
+        self.assertEqual(image.name, 'debian-6-squeeze-v20130617')
+
+    def test_ex_list_addresses(self):
+        address_list = self.driver.ex_list_addresses()
+        address_list_all = self.driver.ex_list_addresses('all')
+        address_list_uc1 = self.driver.ex_list_addresses('us-central1')
+        self.assertEqual(len(address_list), 2)
+        self.assertEqual(len(address_list_all), 4)
+        self.assertEqual(address_list[0].name, 'libcloud-demo-address')
+        self.assertEqual(address_list_uc1[0].name, 'libcloud-demo-address')
+        self.assertEqual(address_list_all[0].name, 'lcaddress')
+
+    def test_ex_list_firewalls(self):
+        firewalls = self.driver.ex_list_firewalls()
+        self.assertEqual(len(firewalls), 4)
+        self.assertEqual(firewalls[0].name, 'default-allow-internal')
+
+    def test_list_images(self):
+        local_images = self.driver.list_images()
+        debian_images = self.driver.list_images(ex_project='debian-cloud')
+        self.assertEqual(len(local_images), 1)
+        self.assertEqual(len(debian_images), 10)
+        self.assertEqual(local_images[0].name, 'debian-7-wheezy-v20130617')
+
+    def test_list_locations(self):
+        locations = self.driver.list_locations()
+        self.assertEqual(len(locations), 5)
+        self.assertEqual(locations[0].name, 'europe-west1-a')
+
+    def test_ex_list_networks(self):
+        networks = self.driver.ex_list_networks()
+        self.assertEqual(len(networks), 3)
+        self.assertEqual(networks[0].name, 'default')
+
+    def test_list_nodes(self):
+        nodes = self.driver.list_nodes()
+        nodes_all = self.driver.list_nodes(ex_zone='all')
+        nodes_uc1a = self.driver.list_nodes(ex_zone='us-central1-a')
+        self.assertEqual(len(nodes), 5)
+        self.assertEqual(len(nodes_all), 8)
+        self.assertEqual(len(nodes_uc1a), 5)
+        self.assertEqual(nodes[0].name, 'node-name')
+        self.assertEqual(nodes_uc1a[0].name, 'node-name')
+        self.assertEqual(nodes_all[0].name, 'libcloud-demo-persist-node')
+
+    def test_list_sizes(self):
+        sizes = self.driver.list_sizes()
+        sizes_all = self.driver.list_sizes('all')
+        self.assertEqual(len(sizes), 22)
+        self.assertEqual(len(sizes_all), 100)
+        self.assertEqual(sizes[0].name, 'f1-micro')
+        self.assertEqual(sizes[0].extra['zone'].name, 'us-central1-a')
+        self.assertEqual(sizes_all[0].name, 'n1-highmem-8')
+        self.assertEqual(sizes_all[0].extra['zone'].name, 'us-central1-a')
+
+    def test_ex_list_volumes(self):
+        volumes = self.driver.ex_list_volumes()
+        volumes_all = self.driver.ex_list_volumes('all')
+        volumes_uc1a = self.driver.ex_list_volumes('us-central1-a')
+        self.assertEqual(len(volumes), 3)
+        self.assertEqual(len(volumes_all), 3)
+        self.assertEqual(len(volumes_uc1a), 3)
+        self.assertEqual(volumes[0].name, 'lcdisk')
+        self.assertEqual(volumes_all[0].name, 'test-disk')
+        self.assertEqual(volumes_uc1a[0].name, 'lcdisk')
+
+    def test_ex_list_zones(self):
+        zones = self.driver.ex_list_zones()
+        self.assertEqual(len(zones), 5)
+        self.assertEqual(zones[0].name, 'europe-west1-a')
+
+    def test_ex_create_address(self):
+        address_name = 'lcaddress'
+        address = self.driver.ex_create_address(address_name)
+        self.assertTrue(isinstance(address, GCEAddress))
+        self.assertEqual(address.name, address_name)
+
+    def test_ex_create_firewall(self):
+        firewall_name = 'lcfirewall'
+        allowed = [{'IPProtocol': 'tcp', 'ports': ['4567']}]
+        source_tags = ['libcloud']
+        firewall = self.driver.ex_create_firewall(firewall_name, allowed,
+                                                  source_tags=source_tags)
+        self.assertTrue(isinstance(firewall, GCEFirewall))
+        self.assertEqual(firewall.name, firewall_name)
+
+    def test_ex_create_network(self):
+        network_name = 'lcnetwork'
+        cidr = '10.11.0.0/16'
+        network = self.driver.ex_create_network(network_name, cidr)
+        self.assertTrue(isinstance(network, GCENetwork))
+        self.assertEqual(network.name, network_name)
+        self.assertEqual(network.cidr, cidr)
+
+    def test_create_node_req(self):
+        image = self.driver.ex_get_image('debian-7')
+        size = self.driver.ex_get_size('n1-standard-1')
+        location = self.driver.zone
+        network = self.driver.ex_get_network('default')
+        tags = ['libcloud']
+        metadata = [{'key': 'test_key', 'value': 'test_value'}]
+        boot_disk = self.driver.ex_get_volume('lcdisk')
+        node_request, node_data = self.driver._create_node_req('lcnode', size,
+                                                               image, location,
+                                                               network, tags,
+                                                               metadata,
+                                                               boot_disk)
+        self.assertEqual(node_request, '/zones/%s/instances' % location.name)
+        self.assertEqual(node_data['metadata'][0]['key'], 'test_key')
+        self.assertEqual(node_data['tags']['items'][0], 'libcloud')
+        self.assertEqual(node_data['name'], 'lcnode')
+        self.assertTrue(node_data['disks'][0]['boot'])
+
+    def test_create_node(self):
+        node_name = 'node-name'
+        image = self.driver.ex_get_image('debian-7')
+        size = self.driver.ex_get_size('n1-standard-1')
+        node = self.driver.create_node(node_name, size, image)
+        self.assertTrue(isinstance(node, Node))
+        self.assertEqual(node.name, node_name)
+
+    def test_create_node_existing(self):
+        node_name = 'libcloud-demo-europe-np-node'
+        image = self.driver.ex_get_image('debian-7')
+        size = self.driver.ex_get_size('n1-standard-1', zone='europe-west1-a')
+        self.assertRaises(ResourceExistsError, self.driver.create_node,
+                          node_name, size, image, location='europe-west1-a')
+
+    def test_ex_create_multiple_nodes(self):
+        base_name = 'lcnode'
+        image = self.driver.ex_get_image('debian-7')
+        size = self.driver.ex_get_size('n1-standard-1')
+        number = 2
+        nodes = self.driver.ex_create_multiple_nodes(base_name, size, image,
+                                                     number)
+        self.assertEqual(len(nodes), 2)
+        self.assertTrue(isinstance(nodes[0], Node))
+        self.assertTrue(isinstance(nodes[1], Node))
+        self.assertEqual(nodes[0].name, '%s-000' % base_name)
+        self.assertEqual(nodes[1].name, '%s-001' % base_name)
+
+    def test_create_volume(self):
+        volume_name = 'lcdisk'
+        size = 1
+        volume = self.driver.create_volume(size, volume_name)
+        self.assertTrue(isinstance(volume, StorageVolume))
+        self.assertEqual(volume.name, volume_name)
+
+    def test_ex_update_firewall(self):
+        firewall_name = 'lcfirewall'
+        firewall = self.driver.ex_get_firewall(firewall_name)
+        firewall.source_ranges = ['10.0.0.0/16']
+        firewall.source_tags = ['libcloud', 'test']
+        firewall2 = self.driver.ex_update_firewall(firewall)
+        self.assertTrue(isinstance(firewall2, GCEFirewall))
+
+    def test_reboot_node(self):
+        node = self.driver.ex_get_node('node-name')
+        reboot = self.driver.reboot_node(node)
+        self.assertTrue(reboot)
+
+    def test_ex_set_node_tags(self):
+        new_tags = ['libcloud']
+        node = self.driver.ex_get_node('node-name')
+        set_tags = self.driver.ex_set_node_tags(node, new_tags)
+        self.assertTrue(set_tags)
+
+    def test_attach_volume(self):
+        volume = self.driver.ex_get_volume('lcdisk')
+        node = self.driver.ex_get_node('node-name')
+        attach = volume.attach(node)
+        self.assertTrue(attach)
+
+    def test_detach_volume(self):
+        volume = self.driver.ex_get_volume('lcdisk')
+        node = self.driver.ex_get_node('node-name')
+        # This fails since the node is required
+        detach = volume.detach()
+        self.assertFalse(detach)
+        # This should pass
+        detach = self.driver.detach_volume(volume, node)
+        self.assertTrue(detach)
+
+    def test_ex_destroy_address(self):
+        address = self.driver.ex_get_address('lcaddress')
+        destroyed = address.destroy()
+        self.assertTrue(destroyed)
+
+    def test_ex_destroy_firewall(self):
+        firewall = self.driver.ex_get_firewall('lcfirewall')
+        destroyed = firewall.destroy()
+        self.assertTrue(destroyed)
+
+    def test_ex_destroy_network(self):
+        network = self.driver.ex_get_network('lcnetwork')
+        destroyed = network.destroy()
+        self.assertTrue(destroyed)
+
+    def test_destroy_node(self):
+        node = self.driver.ex_get_node('node-name')
+        destroyed = node.destroy()
+        self.assertTrue(destroyed)
+
+    def test_ex_destroy_multiple_nodes(self):
+        nodes = []
+        nodes.append(self.driver.ex_get_node('lcnode-000'))
+        nodes.append(self.driver.ex_get_node('lcnode-001'))
+        destroyed = self.driver.ex_destroy_multiple_nodes(nodes)
+        for d in destroyed:
+            self.assertTrue(d)
+
+    def test_destroy_volume(self):
+        address = self.driver.ex_get_address('lcaddress')
+        destroyed = address.destroy()
+        self.assertTrue(destroyed)
+
+    def test_ex_get_address(self):
+        address_name = 'lcaddress'
+        address = self.driver.ex_get_address(address_name)
+        self.assertEqual(address.name, address_name)
+        self.assertEqual(address.address, '173.255.113.20')
+        self.assertEqual(address.region, 'us-central1')
+        self.assertEqual(address.extra['status'], 'RESERVED')
+
+    def test_ex_get_firewall(self):
+        firewall_name = 'lcfirewall'
+        firewall = self.driver.ex_get_firewall(firewall_name)
+        self.assertEqual(firewall.name, firewall_name)
+        self.assertEqual(firewall.network.name, 'default')
+        self.assertEqual(firewall.source_tags, ['libcloud'])
+
+    def test_ex_get_image(self):
+        partial_name = 'debian-7'
+        image = self.driver.ex_get_image(partial_name)
+        self.assertEqual(image.name, 'debian-7-wheezy-v20130617')
+        # A 'debian-7' image exists in the local project
+        self.assertTrue(image.extra['description'].startswith('Local'))
+
+        partial_name = 'debian-6'
+        image = self.driver.ex_get_image(partial_name)
+        self.assertEqual(image.name, 'debian-6-squeeze-v20130617')
+        self.assertTrue(image.extra['description'].startswith('Debian'))
+
+    def test_ex_get_network(self):
+        network_name = 'lcnetwork'
+        network = self.driver.ex_get_network(network_name)
+        self.assertEqual(network.name, network_name)
+        self.assertEqual(network.cidr, '10.11.0.0/16')
+        self.assertEqual(network.extra['gatewayIPv4'], '10.11.0.1')
+
+    def test_ex_get_project(self):
+        project = self.driver.ex_get_project()
+        self.assertEqual(project.name, 'project_name')
+        instances_quota = project.quotas[0]
+        self.assertEqual(instances_quota['usage'], 7.0)
+        self.assertEqual(instances_quota['limit'], 8.0)
+
+    def test_ex_get_size(self):
+        size_name = 'n1-standard-1'
+        size = self.driver.ex_get_size(size_name)
+        self.assertEqual(size.name, size_name)
+        self.assertEqual(size.extra['zone'].name, 'us-central1-a')
+        self.assertEqual(size.disk, 10)
+        self.assertEqual(size.ram, 3840)
+        self.assertEqual(size.extra['guestCpus'], 1)
+
+    def test_ex_get_volume(self):
+        volume_name = 'lcdisk'
+        volume = self.driver.ex_get_volume(volume_name)
+        self.assertEqual(volume.name, volume_name)
+        self.assertEqual(volume.size, '1')
+        self.assertEqual(volume.extra['status'], 'READY')
+
+    def test_ex_get_zone(self):
+        zone_name = 'us-central1-a'
+        expected_time_until = datetime.timedelta(days=52)
+        expected_duration = datetime.timedelta(days=15)
+        zone = self.driver.ex_get_zone(zone_name)
+        self.assertEqual(zone.name, zone_name)
+        self.assertEqual(zone.time_until_mw, expected_time_until)
+        self.assertEqual(zone.next_mw_duration, expected_duration)
+
+
+class GCEMockHttp(MockHttpTestCase):
+    fixtures = ComputeFileFixtures('gce')
+    json_hdr = {'content-type': 'application/json; charset=UTF-8'}
+
+    def _get_method_name(self, type, use_param, qs, path):
+        api_path = '/compute/%s' % API_VERSION
+        project_path = '/projects/%s' % GCE_KEYWORD_PARAMS['project']
+        path = path.replace(api_path, '')
+        # This replace is separate, since there is a call with a different
+        # project name
+        path = path.replace(project_path, '')
+        # The path to get project information is the base path, so use a fake
+        # '/project' path instead
+        if not path:
+            path = '/project'
+        method_name = super(GCEMockHttp, self)._get_method_name(type,
+                                                                use_param,
+                                                                qs, path)
+        return method_name
+
+    def _aggregated_addresses(self, method, url, body, headers):
+        body = self.fixtures.load('aggregated_addresses.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _aggregated_disks(self, method, url, body, headers):
+        body = self.fixtures.load('aggregated_disks.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _aggregated_instances(self, method, url, body, headers):
+        body = self.fixtures.load('aggregated_instances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _aggregated_machineTypes(self, method, url, body, headers):
+        body = self.fixtures.load('aggregated_machineTypes.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_firewalls(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load('global_firewalls_post.json')
+        else:
+            body = self.fixtures.load('global_firewalls.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_firewalls_lcfirewall(self, method, url, body, headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'global_firewalls_lcfirewall_delete.json')
+        elif method == 'PUT':
+            body = self.fixtures.load('global_firewalls_lcfirewall_put.json')
+        else:
+            body = self.fixtures.load('global_firewalls_lcfirewall.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_images(self, method, url, body, headers):
+        body = self.fixtures.load('global_images.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_networks(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load('global_networks_post.json')
+        else:
+            body = self.fixtures.load('global_networks.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_networks_default(self, method, url, body, headers):
+        body = self.fixtures.load('global_networks_default.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_networks_libcloud_demo_network(self, method, url, body,
+                                               headers):
+        body = self.fixtures.load('global_networks_libcloud-demo-network.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_networks_libcloud_demo_europe_network(self, method, url, body,
+                                                      headers):
+        body = self.fixtures.load(
+            'global_networks_libcloud-demo-europe-network.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_networks_lcnetwork(self, method, url, body, headers):
+        if method == 'DELETE':
+            body = self.fixtures.load('global_networks_lcnetwork_delete.json')
+        else:
+            body = self.fixtures.load('global_networks_lcnetwork.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_firewalls_lcfirewall_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_firewalls_lcfirewall_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_firewalls_lcfirewall_put(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_firewalls_lcfirewall_put.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_firewalls_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_firewalls_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_networks_lcnetwork_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_networks_lcnetwork_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_networks_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_networks_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_addresses_lcaddress_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_addresses_lcaddress_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_addresses_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_addresses_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_disks_lcdisk_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_disks_lcdisk_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_disks_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_disks_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instances_lcnode_000_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_instances_lcnode-000_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instances_lcnode_001_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_instances_lcnode-001_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instances_node_name_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_instances_node-name_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instances_node_name_attachDisk_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_instances_node-name_attachDisk_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instances_node_name_detachDisk_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_instances_node-name_detachDisk_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instances_node_name_setTags_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_instances_node-name_setTags_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instances_node_name_reset_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_instances_node-name_reset_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_europe_west1_a_operations_operation_zones_europe_west1_a_instances_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_europe-west1-a_instances_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instances_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_instances_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _project(self, method, url, body, headers):
+        body = self.fixtures.load('project.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _projects_debian_cloud_global_images(self, method, url, body, headers):
+        body = self.fixtures.load('projects_debian-cloud_global_images.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_addresses(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                'regions_us-central1_addresses_post.json')
+        else:
+            body = self.fixtures.load('regions_us-central1_addresses.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_addresses_lcaddress(self, method, url, body,
+                                                 headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'regions_us-central1_addresses_lcaddress_delete.json')
+        else:
+            body = self.fixtures.load(
+                'regions_us-central1_addresses_lcaddress.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones(self, method, url, body, headers):
+        body = self.fixtures.load('zones.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_disks(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load('zones_us-central1-a_disks_post.json')
+        else:
+            body = self.fixtures.load('zones_us-central1-a_disks.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_disks_lcdisk(self, method, url, body, headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'zones_us-central1-a_disks_lcdisk_delete.json')
+        else:
+            body = self.fixtures.load('zones_us-central1-a_disks_lcdisk.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_europe_west1_a_instances(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                'zones_europe-west1-a_instances_post.json')
+        else:
+            body = self.fixtures.load('zones_europe-west1-a_instances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instances(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                'zones_us-central1-a_instances_post.json')
+        else:
+            body = self.fixtures.load('zones_us-central1-a_instances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instances_node_name(self, method, url, body,
+                                                 headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'zones_us-central1-a_instances_node-name_delete.json')
+        else:
+            body = self.fixtures.load(
+                'zones_us-central1-a_instances_node-name.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instances_node_name_attachDisk(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-a_instances_node-name_attachDisk_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instances_node_name_detachDisk(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-a_instances_node-name_detachDisk_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instances_node_name_setTags(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-a_instances_node-name_setTags_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instances_node_name_reset(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-a_instances_node-name_reset_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instances_lcnode_000(self, method, url, body,
+                                                  headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'zones_us-central1-a_instances_lcnode-000_delete.json')
+        else:
+            body = self.fixtures.load(
+                'zones_us-central1-a_instances_lcnode-000.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instances_lcnode_001(self, method, url, body,
+                                                  headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'zones_us-central1-a_instances_lcnode-001_delete.json')
+        else:
+            body = self.fixtures.load(
+                'zones_us-central1-a_instances_lcnode-001.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a(self, method, url, body, headers):
+        body = self.fixtures.load('zones_us-central1-a.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_machineTypes(self, method, url, body, headers):
+        body = self.fixtures.load('zones_us-central1-a_machineTypes.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_europe_west1_a_machineTypes_n1_standard_1(self, method, url,
+                                                         body, headers):
+        body = self.fixtures.load(
+            'zones_europe-west1-a_machineTypes_n1-standard-1.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_machineTypes_n1_standard_1(self, method, url,
+                                                        body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-a_machineTypes_n1-standard-1.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())
-- 
1.8.3


From 2253cd789eeb57d803ffa9c91d8d52875f3708e2 Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 27 Jun 2013 22:47:33 -0700
Subject: [PATCH 11/14] Remove keywords for SSH information, since it's no
 longer needed for reboot.

---
 libcloud/test/secrets.py-dist | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libcloud/test/secrets.py-dist b/libcloud/test/secrets.py-dist
index c620951..e5adce0 100644
--- a/libcloud/test/secrets.py-dist
+++ b/libcloud/test/secrets.py-dist
@@ -24,8 +24,7 @@ ECP_PARAMS = ('user_name', 'password')
 GANDI_PARAMS = ('user',)
 GCE_PARAMS = ('email_address', 'key')  # Service Account Authentication
 #GCE_PARAMS = ('client_id', 'client_secret')  # Installed App Authentication
-GCE_KEYWORD_PARAMS = {'project': 'project_name', 'ssh_username': None,
-                      'ssh_private_key_file': None}
+GCE_KEYWORD_PARAMS = {'project': 'project_name'}
 HOSTINGCOM_PARAMS = ('user', 'secret')
 IBM_PARAMS = ('user', 'secret')
 # OPENSTACK_PARAMS = ('user_name', 'api_key', secure_bool, 'host', port_int)
-- 
1.8.3


From 4248b48f8e969767a17c7621c843cd83d7c3a813 Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Mon, 8 Jul 2013 11:15:27 -0700
Subject: [PATCH 12/14] Rename ex_list_volumes to list_volumes to align with
 LIBCLOUD-352

---
 demos/gce_demo.py                 | 4 ++--
 libcloud/compute/drivers/gce.py   | 2 +-
 libcloud/test/compute/test_gce.py | 8 ++++----
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/demos/gce_demo.py b/demos/gce_demo.py
index 54ae553..92a31b2 100755
--- a/demos/gce_demo.py
+++ b/demos/gce_demo.py
@@ -147,7 +147,7 @@ def main():
     all_addresses = gce.ex_list_addresses(region='all')
     display('Addresses', all_addresses)
 
-    all_volumes = gce.ex_list_volumes(ex_zone='all')
+    all_volumes = gce.list_volumes(ex_zone='all')
     display('Volumes', all_volumes)
 
     # This can return everything, but there is a large amount of overlap,
@@ -264,7 +264,7 @@ def main():
     addresses = gce.ex_list_addresses()
     display('Addresses', addresses)
 
-    volumes = gce.ex_list_volumes()
+    volumes = gce.list_volumes()
     display('Volumes', volumes)
 
     firewalls = gce.ex_list_firewalls()
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 974555b..f206305 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -625,7 +625,7 @@ class GCENodeDriver(NodeDriver):
                 list_sizes = [self._to_node_size(s) for s in response['items']]
         return list_sizes
 
-    def ex_list_volumes(self, ex_zone=None):
+    def list_volumes(self, ex_zone=None):
         """
         Return a list of volumes for a zone or all.
 
diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py
index d904559..01ec59e 100644
--- a/libcloud/test/compute/test_gce.py
+++ b/libcloud/test/compute/test_gce.py
@@ -140,10 +140,10 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(sizes_all[0].name, 'n1-highmem-8')
         self.assertEqual(sizes_all[0].extra['zone'].name, 'us-central1-a')
 
-    def test_ex_list_volumes(self):
-        volumes = self.driver.ex_list_volumes()
-        volumes_all = self.driver.ex_list_volumes('all')
-        volumes_uc1a = self.driver.ex_list_volumes('us-central1-a')
+    def test_list_volumes(self):
+        volumes = self.driver.list_volumes()
+        volumes_all = self.driver.list_volumes('all')
+        volumes_uc1a = self.driver.list_volumes('us-central1-a')
         self.assertEqual(len(volumes), 3)
         self.assertEqual(len(volumes_all), 3)
         self.assertEqual(len(volumes_uc1a), 3)
-- 
1.8.3


From 8873931f1816f6de0503364b433ce7be54c25e5e Mon Sep 17 00:00:00 2001
From: Tomaz Muraus <tomaz@tomaz.me>
Date: Tue, 9 Jul 2013 13:31:19 +0200
Subject: [PATCH 13/14] Fix Python 2.5 compatibility issues.

---
 libcloud/compute/drivers/gce.py     |  4 +++-
 libcloud/test/common/test_google.py | 30 +++++++++++++++++++-----------
 libcloud/test/compute/test_gce.py   |  9 +++++----
 3 files changed, 27 insertions(+), 16 deletions(-)

diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index f206305..6792425 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -46,7 +46,9 @@ def timestamp_to_datetime(timestamp):
     @return:  Datetime object corresponding to timestamp
     @rtype:   C{datetime}
     """
-    ts = datetime.datetime.strptime(timestamp[:-6], '%Y-%m-%dT%H:%M:%S.%f')
+    # We remove timezone offset and milliseconds (Python 2.5 strptime doesn't
+    # support %f)
+    ts = datetime.datetime.strptime(timestamp[:-10], '%Y-%m-%dT%H:%M:%S')
     tz_hours = int(timestamp[-5:-3])
     tz_mins = int(timestamp[-2:]) * int(timestamp[-6:-5] + '1')
     tz_delta = datetime.timedelta(hours=tz_hours, minutes=tz_mins)
diff --git a/libcloud/test/common/test_google.py b/libcloud/test/common/test_google.py
index 904dfff..35da4b2 100644
--- a/libcloud/test/common/test_google.py
+++ b/libcloud/test/common/test_google.py
@@ -18,7 +18,11 @@ Tests for Google Connection classes.
 import datetime
 import sys
 import unittest
-import json
+
+try:
+    import simplejson as json
+except ImportError:
+    import json
 
 from libcloud.utils.py3 import httplib
 
@@ -53,8 +57,9 @@ class GoogleBaseAuthConnectionTest(LibcloudTestCase):
         GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp,
                                                  GoogleAuthMockHttp)
         self.mock_scope = ['https://www.googleapis.com/auth/foo']
+        kwargs = {'scope': self.mock_scope}
         self.conn = GoogleInstalledAppAuthConnection(*GCE_PARAMS,
-                                                     scope=self.mock_scope)
+                                                     **kwargs)
 
     def test_add_default_headers(self):
         old_headers = {}
@@ -84,8 +89,9 @@ class GoogleInstalledAppAuthConnectionTest(LibcloudTestCase):
         GoogleInstalledAppAuthConnection.conn_classes = (GoogleAuthMockHttp,
                                                          GoogleAuthMockHttp)
         self.mock_scope = ['https://www.googleapis.com/auth/foo']
+        kwargs = {'scope': self.mock_scope}
         self.conn = GoogleInstalledAppAuthConnection(*GCE_PARAMS,
-                                                     scope=self.mock_scope)
+                                                     **kwargs)
 
     def test_refresh_token(self):
         # This token info doesn't have a refresh token, so a new token will be
@@ -123,21 +129,23 @@ class GoogleBaseConnectionTest(LibcloudTestCase):
         GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp,
                                                  GoogleAuthMockHttp)
         self.mock_scope = ['https://www.googleapis.com/auth/foo']
-        self.conn = GoogleBaseConnection(*GCE_PARAMS, auth_type='IA',
-                                         scope=self.mock_scope)
+        kwargs = {'scope': self.mock_scope, 'auth_type': 'IA'}
+        self.conn = GoogleBaseConnection(*GCE_PARAMS, **kwargs)
 
     def test_auth_type(self):
-
         self.assertRaises(GoogleAuthError, GoogleBaseConnection, *GCE_PARAMS,
-                          auth_type='XX')
+                          **{'auth_type': 'XX'})
+
+        kwargs = {'scope': self.mock_scope}
+
         if SHA256:
-            conn1 = GoogleBaseConnection(*GCE_PARAMS, auth_type='SA',
-                                         scope=self.mock_scope)
+            kwargs['auth_type'] = 'SA'
+            conn1 = GoogleBaseConnection(*GCE_PARAMS, **kwargs)
             self.assertTrue(isinstance(conn1.auth_conn,
                                        GoogleServiceAcctAuthConnection))
 
-        conn2 = GoogleBaseConnection(*GCE_PARAMS, auth_type='IA',
-                                     scope=self.mock_scope)
+        kwargs['auth_type'] = 'IA'
+        conn2 = GoogleBaseConnection(*GCE_PARAMS, **kwargs)
         self.assertTrue(isinstance(conn2.auth_conn,
                                    GoogleInstalledAppAuthConnection))
 
diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py
index 01ec59e..d991f62 100644
--- a/libcloud/test/compute/test_gce.py
+++ b/libcloud/test/compute/test_gce.py
@@ -58,13 +58,14 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp,
                                                  GoogleAuthMockHttp)
         GCEMockHttp.type = None
-        self.driver = GCENodeDriver(*GCE_PARAMS, auth_type='IA',
-                                    datacenter=self.datacenter,
-                                    **GCE_KEYWORD_PARAMS)
+        kwargs = GCE_KEYWORD_PARAMS.copy()
+        kwargs['auth_type'] = 'IA'
+        kwargs['datacenter'] = self.datacenter
+        self.driver = GCENodeDriver(*GCE_PARAMS, **kwargs)
 
     def test_timestamp_to_datetime(self):
         timestamp1 = '2013-06-26T10:05:19.340-07:00'
-        datetime1 = datetime.datetime(2013, 6, 26, 17, 5, 19, 340000)
+        datetime1 = datetime.datetime(2013, 6, 26, 17, 5, 19)
         self.assertEqual(timestamp_to_datetime(timestamp1), datetime1)
         timestamp2 = '2013-06-26T17:43:15.000-00:00'
         datetime2 = datetime.datetime(2013, 6, 26, 17, 43, 15)
-- 
1.8.3


From b51a3a9da34a46bc02686c53c147be17f5248125 Mon Sep 17 00:00:00 2001
From: Tomaz Muraus <tomaz@tomaz.me>
Date: Tue, 9 Jul 2013 13:38:38 +0200
Subject: [PATCH 14/14] Fix a typo.

---
 libcloud/compute/drivers/gce.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 6792425..c46b66d 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -46,7 +46,7 @@ def timestamp_to_datetime(timestamp):
     @return:  Datetime object corresponding to timestamp
     @rtype:   C{datetime}
     """
-    # We remove timezone offset and milliseconds (Python 2.5 strptime doesn't
+    # We remove timezone offset and microseconds (Python 2.5 strptime doesn't
     # support %f)
     ts = datetime.datetime.strptime(timestamp[:-10], '%Y-%m-%dT%H:%M:%S')
     tz_hours = int(timestamp[-5:-3])
-- 
1.8.3

