From c147838c2ea3d4d39eb35ba9d39d51a8aeac0de8 Mon Sep 17 00:00:00 2001
From: Rick Wright <rickw@google.com>
Date: Thu, 3 Oct 2013 13:44:29 -0700
Subject: [PATCH] GCE Loadbalancer Support

---
 demos/gce_demo.py                                  |   16 +-
 demos/gce_lb_demo.py                               |  302 +++++
 libcloud/common/google.py                          |  128 +-
 libcloud/compute/drivers/gce.py                    | 1310 ++++++++++++++++----
 libcloud/loadbalancer/drivers/gce.py               |  363 ++++++
 libcloud/loadbalancer/providers.py                 |    5 +-
 libcloud/loadbalancer/types.py                     |    1 +
 .../fixtures/gce/aggregated_forwardingRules.json   |   57 +
 .../fixtures/gce/aggregated_targetPools.json       |   64 +
 .../fixtures/gce/global_httpHealthChecks.json      |   35 +
 .../gce/global_httpHealthChecks_basic-check.json   |   15 +
 .../gce/global_httpHealthChecks_lchealthcheck.json |   14 +
 ...obal_httpHealthChecks_lchealthcheck_delete.json |   14 +
 .../global_httpHealthChecks_lchealthcheck_put.json |   14 +
 ...pHealthChecks_libcloud-lb-demo-healthcheck.json |   13 +
 .../fixtures/gce/global_httpHealthChecks_post.json |   13 +
 ...obal_httpHealthChecks_lchealthcheck_delete.json |   14 +
 ..._global_httpHealthChecks_lchealthcheck_put.json |   15 +
 ...ons_operation_global_httpHealthChecks_post.json |   14 +
 ...l1_forwardingRules_lcforwardingrule_delete.json |   16 +
 ...n_regions_us-central1_forwardingRules_post.json |   16 +
 ...rgetPools_lctargetpool_addHealthCheck_post.json |   16 +
 ..._targetPools_lctargetpool_addInstance_post.json |   16 +
 ...s-central1_targetPools_lctargetpool_delete.json |   15 +
 ...tPools_lctargetpool_removeHealthCheck_post.json |   16 +
 ...rgetPools_lctargetpool_removeInstance_post.json |   16 +
 ...ation_regions_us-central1_targetPools_post.json |   15 +
 libcloud/test/compute/fixtures/gce/regions.json    |   45 +
 .../gce/regions_us-central1_forwardingRules.json   |   31 +
 ...-central1_forwardingRules_lcforwardingrule.json |   12 +
 ...l1_forwardingRules_lcforwardingrule_delete.json |   15 +
 ...ntral1_forwardingRules_libcloud-lb-demo-lb.json |   12 +
 .../regions_us-central1_forwardingRules_post.json  |   14 +
 .../gce/regions_us-central1_targetPools.json       |   38 +
 ...gions_us-central1_targetPools_lctargetpool.json |   15 +
 ...rgetPools_lctargetpool_addHealthCheck_post.json |   15 +
 ..._targetPools_lctargetpool_addInstance_post.json |   15 +
 ...s-central1_targetPools_lctargetpool_delete.json |   15 +
 ...tPools_lctargetpool_removeHealthCheck_post.json |   15 +
 ...rgetPools_lctargetpool_removeInstance_post.json |   15 +
 ...entral1_targetPools_libcloud-lb-demo-lb-tp.json |   16 +
 .../gce/regions_us-central1_targetPools_post.json  |   14 +
 .../regions_us-central1_targetPools_www-pool.json  |   17 +
 ...tral1-b_instances_libcloud-lb-demo-www-000.json |   52 +
 ...tral1-b_instances_libcloud-lb-demo-www-001.json |   52 +
 ...tral1-b_instances_libcloud-lb-demo-www-002.json |   13 +
 libcloud/test/compute/test_gce.py                  |  423 ++++++-
 libcloud/test/loadbalancer/test_gce.py             |  207 ++++
 48 files changed, 3341 insertions(+), 243 deletions(-)
 create mode 100755 demos/gce_lb_demo.py
 create mode 100644 libcloud/loadbalancer/drivers/gce.py
 create mode 100644 libcloud/test/compute/fixtures/gce/aggregated_forwardingRules.json
 create mode 100644 libcloud/test/compute/fixtures/gce/aggregated_targetPools.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_httpHealthChecks.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_httpHealthChecks_basic-check.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck_put.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_httpHealthChecks_libcloud-lb-demo-healthcheck.json
 create mode 100644 libcloud/test/compute/fixtures/gce/global_httpHealthChecks_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_lchealthcheck_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_lchealthcheck_put.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_forwardingRules_lcforwardingrule_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_forwardingRules_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_addInstance_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_removeInstance_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_lcforwardingrule.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_lcforwardingrule_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_libcloud-lb-demo-lb.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_addInstance_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_delete.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_removeInstance_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_libcloud-lb-demo-lb-tp.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_post.json
 create mode 100644 libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_www-pool.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-000.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-001.json
 create mode 100644 libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-002.json
 create mode 100644 libcloud/test/loadbalancer/test_gce.py

diff --git a/demos/gce_demo.py b/demos/gce_demo.py
index 92a31b2..8909b0a 100755
--- a/demos/gce_demo.py
+++ b/demos/gce_demo.py
@@ -41,7 +41,13 @@ import sys
 try:
     import secrets
 except ImportError:
-    secrets = None
+    print('"demos/secrets.py" not found.\n\n'
+          'Please copy secrets.py-dist to secrets.py and update the GCE* '
+          'values with appropriate authentication information.\n'
+          'Additional information about setting these values can be found '
+          'in the docstring for:\n'
+          'libcloud/common/google.py\n')
+    sys.exit(1)
 
 # 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__),
@@ -58,7 +64,7 @@ MAX_NODES = 5
 DEMO_BASE_NAME = 'libcloud-demo'
 
 # Datacenter to create resources in
-DATACENTER = 'us-central1-a'
+DATACENTER = 'us-central2-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
@@ -68,10 +74,14 @@ CLEANUP = True
 args = getattr(secrets, 'GCE_PARAMS', ())
 kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
 
+# Add datacenter to kwargs for Python 2.5 compatibility
+kwargs = kwargs.copy()
+kwargs['datacenter'] = DATACENTER
+
 
 # ==== HELPER FUNCTIONS ====
 def get_gce_driver():
-    driver = get_driver(Provider.GCE)(*args, datacenter=DATACENTER, **kwargs)
+    driver = get_driver(Provider.GCE)(*args, **kwargs)
     return driver
 
 
diff --git a/demos/gce_lb_demo.py b/demos/gce_lb_demo.py
new file mode 100755
index 0000000..9aaffeb
--- /dev/null
+++ b/demos/gce_lb_demo.py
@@ -0,0 +1,302 @@
+#!/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 and the GCE
+# Load Balancer.  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 Load Balancer Driver.
+#
+# 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_lb_demo
+#        gcelb = gce_lb_demo.get_gcelb_driver()
+#        gcelb.list_balancers()
+#        etc.
+#    - Or, to run the full demo from the interactive python shell:
+#        import gce_lb_demo
+#        gce_lb_demo.CLEANUP = False               # optional
+#        gce_lb_demo.MAX_NODES = 4                 # optional
+#        gce_lb_demo.DATACENTER = 'us-central1-a'  # optional
+#        gce_lb_demo.main()
+
+import os.path
+import sys
+import time
+
+try:
+    import secrets
+except ImportError:
+    print('"demos/secrets.py" not found.\n\n'
+          'Please copy secrets.py-dist to secrets.py and update the GCE* '
+          'values with appropriate authentication information.\n'
+          'Additional information about setting these values can be found '
+          'in the docstring for:\n'
+          'libcloud/common/google.py\n')
+    sys.exit(1)
+
+# 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.utils.py3 import PY3
+if PY3:
+    import urllib.request as url_req
+else:
+    import urllib2 as url_req
+
+# This demo uses both the Compute driver and the LoadBalancer driver
+from libcloud.compute.types import Provider
+from libcloud.compute.providers import get_driver
+from libcloud.loadbalancer.types import Provider as Provider_lb
+from libcloud.loadbalancer.providers import get_driver as get_driver_lb
+
+# 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-lb-demo'
+
+# Datacenter to create resources in
+DATACENTER = 'us-central1-b'
+
+# 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', {})
+
+# Add datacenter to kwargs for Python 2.5 compatibility
+kwargs = kwargs.copy()
+kwargs['datacenter'] = DATACENTER
+
+
+# ==== HELPER FUNCTIONS ====
+def get_gce_driver():
+    driver = get_driver(Provider.GCE)(*args, **kwargs)
+    return driver
+
+
+def get_gcelb_driver(gce_driver=None):
+    # The GCE Load Balancer driver uses the GCE Compute driver for all of its
+    # API calls.  You can either provide the driver directly, or provide the
+    # same authentication information so the the LB driver can get its own
+    # Compute driver.
+    if gce_driver:
+        driver = get_driver_lb(Provider_lb.GCE)(gce_driver=gce_driver)
+    else:
+        driver = get_driver_lb(Provider_lb.GCE)(*args, **kwargs)
+    return driver
+
+
+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 ====
+def main():
+    global gce  # Used by the clean_up function
+    gce = get_gce_driver()
+    gcelb = get_gcelb_driver(gce)
+
+    # Existing Balancers
+    balancers = gcelb.list_balancers()
+    display('Load Balancers', balancers)
+
+    # Protocols
+    protocols = gcelb.list_protocols()
+    print('Protocols:')
+    for p in protocols:
+        print('   %s' % p)
+
+    # Healthchecks
+    healthchecks = gcelb.ex_list_healthchecks()
+    display('Health Checks', healthchecks)
+
+    # This demo is based on the GCE Load Balancing Quickstart described here:
+    # https://developers.google.com/compute/docs/load-balancing/lb-quickstart
+
+    # == Clean-up and existing demo resources ==
+    all_nodes = gce.list_nodes(ex_zone='all')
+    firewalls = gce.ex_list_firewalls()
+    print('Cleaning up any "%s" resources:' % DEMO_BASE_NAME)
+    clean_up(DEMO_BASE_NAME, all_nodes, balancers + healthchecks + firewalls)
+
+    # == Create 3 nodes to balance between ==
+    startup_script = ('apt-get -y update && '
+                      'apt-get -y install apache2 && '
+                      'hostname > /var/www/index.html')
+    tag = '%s-www' % DEMO_BASE_NAME
+    base_name = '%s-www' % DEMO_BASE_NAME
+    image = gce.ex_get_image('debian-7')
+    size = gce.ex_get_size('n1-standard-1')
+    number = 3
+    metadata = {'items': [{'key': 'startup-script',
+                           'value': startup_script}]}
+    lb_nodes = gce.ex_create_multiple_nodes(base_name, size, image,
+                                            number, ex_tags=[tag],
+                                            ex_metadata=metadata)
+    display('Created Nodes', lb_nodes)
+
+    # == Create a Firewall for instances ==
+    print('Creating a Firewall:')
+    name = '%s-firewall' % DEMO_BASE_NAME
+    allowed = [{'IPProtocol': 'tcp',
+                'ports': ['80']}]
+    firewall = gce.ex_create_firewall(name, allowed, source_tags=[tag])
+    print('   Firewall %s created' % firewall.name)
+
+    # == Create a Health Check ==
+    print('Creating a HealthCheck:')
+    name = '%s-healthcheck' % DEMO_BASE_NAME
+
+    # These are all the default values, but listed here as an example.  To
+    # create a healthcheck with the defaults, only name is required.
+    hc = gcelb.ex_create_healthcheck(name, host=None, path='/', port='80',
+                                     interval=5, timeout=5,
+                                     unhealthy_threshold=2,
+                                     healthy_threshold=2)
+    print('   Healthcheck %s created' % hc.name)
+
+    # == Create Load Balancer ==
+    print('Creating Load Balancer')
+    name = '%s-lb' % DEMO_BASE_NAME
+    port = 80
+    protocol = 'tcp'
+    algorithm = None
+    members = lb_nodes[:2]  # Only attach the first two initially
+    healthchecks = [hc]
+    balancer = gcelb.create_balancer(name, port, protocol, algorithm, members,
+                                     ex_healthchecks=healthchecks)
+    print('   Load Balancer %s created' % balancer.name)
+
+    # == Attach third Node ==
+    print('Attaching additional node to Load Balancer:')
+    member = balancer.attach_compute_node(lb_nodes[2])
+    print('   Attached %s to %s' % (member.id, balancer.name))
+
+    # == Show Balancer Members ==
+    members = balancer.list_members()
+    print('Load Balancer Members:')
+    for member in members:
+        print('   ID: %s IP: %s' % (member.id, member.ip))
+
+    # == Remove a Member ==
+    print('Removing a Member:')
+    detached = members[0]
+    detach = balancer.detach_member(detached)
+    if detach:
+        print('   Member %s detached from %s' % (detached.id, balancer.name))
+
+    # == Show Updated Balancer Members ==
+    members = balancer.list_members()
+    print('Updated Load Balancer Members:')
+    for member in members:
+        print('   ID: %s IP: %s' % (member.id, member.ip))
+
+    # == Reattach Member ==
+    print('Reattaching Member:')
+    member = balancer.attach_member(detached)
+    print('   Member %s attached to %s' % (member.id, balancer.name))
+
+    # == Test Load Balancer by connecting to it multiple times ==
+    print('Sleeping for 10 seconds to stabilize the balancer...')
+    time.sleep(10)
+    rounds = 200
+    url = 'http://%s/' % balancer.ip
+    line_length = 75
+    print('Connecting to %s %s times:' % (url, rounds))
+    for x in range(rounds):
+        response = url_req.urlopen(url)
+        if PY3:
+            output = str(response.read(), encoding='utf-8').strip()
+        else:
+            output = response.read().strip()
+        if 'www-001' in output:
+            padded_output = output.center(line_length)
+        elif 'www-002' in output:
+            padded_output = output.rjust(line_length)
+        else:
+            padded_output = output.ljust(line_length)
+        sys.stdout.write('\r%s' % padded_output)
+        sys.stdout.flush()
+    print('')
+
+    if CLEANUP:
+        balancers = gcelb.list_balancers()
+        healthchecks = gcelb.ex_list_healthchecks()
+        nodes = gce.list_nodes(ex_zone='all')
+        firewalls = gce.ex_list_firewalls()
+
+        print('Cleaning up %s resources created.' % DEMO_BASE_NAME)
+        clean_up(DEMO_BASE_NAME, nodes, balancers + healthchecks + firewalls)
+
+if __name__ == '__main__':
+    main()
diff --git a/libcloud/common/google.py b/libcloud/common/google.py
index 5c6e524..b92515a 100644
--- a/libcloud/common/google.py
+++ b/libcloud/common/google.py
@@ -71,13 +71,15 @@ import time
 import datetime
 import os
 import socket
+import sys
 
-from libcloud.utils.py3 import urlencode, urlparse, PY3
+from libcloud.utils.py3 import httplib, urlencode, urlparse, PY3
 from libcloud.common.base import (ConnectionUserAndKey, JsonResponse,
                                   PollingConnection)
-from libcloud.compute.types import (InvalidCredsError,
-                                    MalformedResponseError,
-                                    LibcloudError)
+from libcloud.common.types import (InvalidCredsError,
+                                   MalformedResponseError,
+                                   ProviderError,
+                                   LibcloudError)
 
 try:
     from Crypto.Hash import SHA256
@@ -101,10 +103,121 @@ class GoogleAuthError(LibcloudError):
         return repr(self.value)
 
 
-class GoogleResponse(JsonResponse):
+class GoogleBaseError(ProviderError):
+    def __init__(self, value, http_code, code, driver=None):
+        self.code = code
+        super(GoogleBaseError, self).__init__(value, http_code, driver)
+
+
+class JsonParseError(GoogleBaseError):
+    pass
+
+
+class ResourceNotFoundError(GoogleBaseError):
+    pass
+
+
+class QuotaExceededError(GoogleBaseError):
+    pass
+
+
+class ResourceExistsError(GoogleBaseError):
     pass
 
 
+class ResourceInUseError(GoogleBaseError):
+    pass
+
+
+class GoogleResponse(JsonResponse):
+    """
+    Google Base Response class.
+    """
+    def success(self):
+        """
+        Determine if the request was successful.
+
+        For the Google response class, tag all responses as successful and
+        raise appropriate Exceptions from parse_body.
+
+        @return: C{True}
+        """
+        return True
+
+    def _get_error(self, body):
+        """
+        Get the error code and message from a JSON response.
+
+        Return just the first error if there are multiple errors.
+
+        @param  body: The body of the JSON response dictionary
+        @type   body: C{dict}
+
+        @return:  Tuple containing error code and message
+        @rtype:   C{tuple} of C{str} or C{int}
+        """
+        if 'errors' in body['error']:
+            err = body['error']['errors'][0]
+        else:
+            err = body['error']
+
+        code = err.get('code')
+        message = err.get('message')
+        return (code, message)
+
+    def parse_body(self):
+        """
+        Parse the JSON response body, or raise exceptions as appropriate.
+
+        @return:  JSON dictionary
+        @rtype:   C{dict}
+        """
+        if len(self.body) == 0 and not self.parse_zero_length_body:
+            return self.body
+
+        json_error = False
+        try:
+            body = json.loads(self.body)
+        except:
+            # If there is both a JSON parsing error and an unsuccessful http
+            # response (like a 404), we want to raise the http error and not
+            # the JSON one, so don't raise JsonParseError here.
+            body = self.body
+            json_error = True
+
+        if self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]:
+            if json_error:
+                raise JsonParseError(body, self.status, None)
+            elif 'error' in body:
+                (code, message) = self._get_error(body)
+                if code == 'QUOTA_EXCEEDED':
+                    raise QuotaExceededError(message, self.status, code)
+                elif code == 'RESOURCE_ALREADY_EXISTS':
+                    raise ResourceExistsError(message, self.status, code)
+                elif code.startswith('RESOURCE_IN_USE'):
+                    raise ResourceInUseError(message, self.status, code)
+                else:
+                    raise GoogleBaseError(message, self.status, code)
+            else:
+                return body
+
+        elif self.status == httplib.NOT_FOUND:
+            if (not json_error) and ('error' in body):
+                (code, message) = self._get_error(body)
+            else:
+                message = body
+                code = None
+            raise ResourceNotFoundError(message, self.status, code)
+
+        else:
+            if (not json_error) and ('error' in body):
+                (code, message) = self._get_error(body)
+            else:
+                message = body
+                code = None
+            raise GoogleBaseError(message, self.status, code)
+
+
 class GoogleBaseDriver(object):
     name = "Google API"
 
@@ -395,6 +508,11 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection):
 
         super(GoogleBaseConnection, self).__init__(user_id, key, **kwargs)
 
+        python_ver = '%s.%s.%s' % (sys.version_info[0], sys.version_info[1],
+                                   sys.version_info[2])
+        ver_platform = 'Python %s/%s' % (python_ver, sys.platform)
+        self.user_agent_append(ver_platform)
+
     def _now(self):
         return datetime.datetime.utcnow()
 
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index c46b66d..b9cb9cd 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -25,7 +25,9 @@ import getpass
 
 from libcloud.common.google import GoogleResponse
 from libcloud.common.google import GoogleBaseConnection
+from libcloud.common.google import ResourceNotFoundError
 
+from libcloud.common.types import MalformedResponseError
 from libcloud.compute.base import Node, NodeDriver, NodeImage, NodeLocation
 from libcloud.compute.base import NodeSize, StorageVolume, UuidMixin
 from libcloud.compute.providers import Provider
@@ -55,33 +57,6 @@ def timestamp_to_datetime(timestamp):
     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
 
@@ -129,13 +104,53 @@ class GCEAddress(UuidMixin):
 
 class GCEFailedNode(object):
     """Dummy Node object for nodes that are not created."""
-    def __init__(self, name, error):
+    def __init__(self, name, error, code):
         self.name = name
         self.error = error
+        self.code = code
 
     def __repr__(self):
         return '<GCEFailedNode name="%s" error_code="%s">' % (
-            self.name, self.error['code'])
+            self.name, self.code)
+
+
+class GCEHealthCheck(UuidMixin):
+    """A GCE Http Health Check class."""
+    def __init__(self, id, name, path, port, interval, timeout,
+                 unhealthy_threshold, healthy_threshold, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.path = path
+        self.port = port
+        self.interval = interval
+        self.timeout = timeout
+        self.unhealthy_threshold = unhealthy_threshold
+        self.healthy_threshold = healthy_threshold
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCEHealthCheck id="%s" name="%s" path="%s" port="%s">' % (
+            self.id, self.name, self.path, self.port)
+
+    def destroy(self):
+        """
+        Destroy this Health Check.
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        return self.driver.ex_destroy_healthcheck(healthcheck=self)
+
+    def update(self):
+        """
+        Commit updated healthcheck values.
+
+        @return:  Updated Healthcheck object
+        @rtype:   L{GCEHealthcheck}
+        """
+        return self.driver.ex_update_healthcheck(healthcheck=self)
 
 
 class GCEFirewall(UuidMixin):
@@ -165,6 +180,42 @@ class GCEFirewall(UuidMixin):
         """
         return self.driver.ex_destroy_firewall(firewall=self)
 
+    def update(self):
+        """
+        Commit updated firewall values.
+
+        @return:  Updated Firewall object
+        @rtype:   L{GCEFirewall}
+        """
+        return self.driver.ex_update_firewall(firewall=self)
+
+
+class GCEForwardingRule(UuidMixin):
+    def __init__(self, id, name, region, address, protocol, targetpool, driver,
+                 extra=None):
+        self.id = str(id)
+        self.name = name
+        self.region = region
+        self.address = address
+        self.protocol = protocol
+        self.targetpool = targetpool
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCEForwardingRule id="%s" name="%s" address="%s">' % (
+            self.id, self.name, self.address)
+
+    def destroy(self):
+        """
+        Destroy this Forwarding Rule
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        return self.driver.ex_destroy_forwarding_rule(forwarding_rule=self)
+
 
 class GCENetwork(UuidMixin):
     """A GCE Network object class."""
@@ -210,10 +261,105 @@ class GCEProject(UuidMixin):
         self.extra = extra
         UuidMixin.__init__(self)
 
-    def _repr__(self):
+    def __repr__(self):
         return '<GCEProject id="%s" name="%s">' % (self.id, self.name)
 
 
+class GCERegion(UuidMixin):
+    def __init__(self, id, name, status, zones, quotas, deprecated, driver,
+                 extra=None):
+        self.id = str(id)
+        self.name = name
+        self.status = status
+        self.zones = zones
+        self.quotas = quotas
+        self.deprecated = deprecated
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCERegion id="%s" name="%s", status="%s">' % (
+            self.id, self.name, self.status)
+
+
+class GCETargetPool(UuidMixin):
+    def __init__(self, id, name, region, healthchecks, nodes, driver,
+                 extra=None):
+        self.id = str(id)
+        self.name = name
+        self.region = region
+        self.healthchecks = healthchecks
+        self.nodes = nodes
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCETargetPool id="%s" name="%s" region="%s">' % (
+            self.id, self.name, self.region.name)
+
+    def add_node(self, node):
+        """
+        Add a node to this target pool.
+
+        @param  node: Node to add
+        @type   node: C{str} or L{Node}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        return self.driver.ex_targetpool_add_node(targetpool=self, node=node)
+
+    def remove_node(self, node):
+        """
+        Remove a node from this target pool.
+
+        @param  node: Node to remove
+        @type   node: C{str} or L{Node}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        return self.driver.ex_targetpool_remove_node(targetpool=self,
+                                                     node=node)
+
+    def add_healthcheck(self, healthcheck):
+        """
+        Add a healthcheck to this target pool.
+
+        @param  healthcheck: Healthcheck to add
+        @type   healthcheck: C{str} or L{GCEHealthCheck}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        return self.driver.ex_targetpool_add_healthcheck(
+            targetpool=self, healthcheck=healthcheck)
+
+    def remove_healthcheck(self, healthcheck):
+        """
+        Remove a healthcheck from this target pool.
+
+        @param  healthcheck: Healthcheck to remove
+        @type   healthcheck: C{str} or L{GCEHealthCheck}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        return self.driver.ex_targetpool_remove_healthcheck(
+            targetpool=self, healthcheck=healthcheck)
+
+    def destroy(self):
+        """
+        Destroy this Target Pool
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        return self.driver.ex_destroy_targetpool(targetpool=self)
+
+
 class GCEZone(NodeLocation):
     """Subclass of NodeLocation to provide additional information."""
     def __init__(self, id, name, status, maintenance_windows, quotas,
@@ -305,7 +451,15 @@ class GCEZone(NodeLocation):
 
 class GCENodeDriver(NodeDriver):
     """
-    Base class for GCE Node Driver.
+    GCE Node Driver class.
+
+    This is the primary driver for interacting with Google Compute Engine.  It
+    contains all of the standard libcloud methods, plus additional ex_* methods
+    for more features.
+
+    Note that many methods allow either objects or strings (or lists of
+    objects/strings).  In most cases, passing strings instead of objects will
+    result in additional GCE API calls.
     """
     connectionCls = GCEConnection
     api_name = 'googleapis'
@@ -318,6 +472,7 @@ class GCENodeDriver(NodeDriver):
         "STAGING": NodeState.PENDING,
         "RUNNING": NodeState.RUNNING,
         "STOPPED": NodeState.TERMINATED,
+        "STOPPING": NodeState.TERMINATED,
         "TERMINATED": NodeState.TERMINATED
     }
 
@@ -353,7 +508,8 @@ class GCENodeDriver(NodeDriver):
                              '"project" keyword.')
         super(GCENodeDriver, self).__init__(user_id, key, **kwargs)
 
-        # Cache Zone information to reduce API calls and increase speed
+        # Cache Zone and Region 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()
@@ -365,31 +521,81 @@ class GCENodeDriver(NodeDriver):
         else:
             self.zone = None
 
+        self.region_list = self.ex_list_regions()
+        self.region_dict = {}
+        for region in self.region_list:
+            self.region_dict[region.name] = region
+
+        if self.zone:
+            self.region = self._get_region_from_zone(self.zone)
+        else:
+            self.region = None
+
     def _ex_connection_class_kwargs(self):
         return {'auth_type': self.auth_type,
                 'project': self.project}
 
-    def _categorize_error(self, error):
+    def _catch_error(self, ignore_errors=False):
         """
-        Parse error message returned from GCE operation and raise the
-        appropriate Exception.
+        Catch an exception and raise it unless asked to ignore it.
 
-        @param  error: Error dictionary from a GCE Operations response
-        @type   error: C{dict}
+        @keyword  ignore_errors: If true, just return the error.  Otherwise,
+                                 raise the error.
+        @type     ignore_errors: C{bool}
+
+        @return:  The exception that was raised.
+        @rtype:   L{Exception}
         """
-        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)
+        e = sys.exc_info()[1]
+        if ignore_errors:
+            return e
         else:
-            raise GCEError(code, message)
+            raise e
+
+    def _get_components_from_path(self, path):
+        """
+        Return a dictionary containing name & zone/region from a request path.
+
+        @param  path: HTTP request path (e.g.
+                      '/project/pjt-name/zones/us-central1-a/instances/mynode')
+        @type   path: C{str}
 
-    def _find_zone(self, name, res_type, region=False):
+        @return:  Dictionary containing name and zone/region of resource
+        @rtype    C{dict}
         """
-        Find the zone for a named resource.
+        region = None
+        zone = None
+        glob = False
+        components = path.split('/')
+        name = components[-1]
+        if components[-4] == 'regions':
+            region = components[-3]
+        elif components[-4] == 'zones':
+            zone = components[-3]
+        elif components[-3] == 'global':
+            glob = True
+
+        return {'name': name, 'region': region, 'zone': zone, 'global': glob}
+
+    def _get_region_from_zone(self, zone):
+        """
+        Return the Region object that contains the given Zone object.
+
+        @param  zone: Zone object
+        @type   zone: L{GCEZone}
+
+        @return:  Region object that contains the zone
+        @rtype:   L{GCERegion}
+        """
+        for region in self.region_list:
+            zones = [z.name for z in region.zones]
+            if zone.name in zones:
+                return region
+
+    def _find_zone_or_region(self, name, res_type, region=False,
+                             res_name=None):
+        """
+        Find the zone or region for a named resource.
 
         @param  name: Name of resource to find
         @type   name: C{str}
@@ -398,21 +604,36 @@ class GCENodeDriver(NodeDriver):
                           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}
+        @keyword  region: If True, search regions instead of zones
+        @type     region: C{bool}
 
-        @return:  Name of zone (or region) that the resource is in.
-        @rtype:   C{str}
+        @keyword  res_name: The name of the resource type for error messages.
+                            Examples: 'Volume', 'Node', 'Address'
+        @keyword  res_name: C{str}
+
+        @return:  Zone/Region object for the zone/region for the resource.
+        @rtype:   L{GCEZone} or L{GCERegion}
         """
-        request = '/aggregated/%s' % res_type
+        if region:
+            rz = 'region'
+        else:
+            rz = 'zone'
+        rz_name = None
+        res_name = res_name or res_type
+        request = '/aggregated/%s' % (res_type)
         res_list = self.connection.request(request).object
         for k, v in res_list['items'].items():
             for res in v.get(res_type, []):
                 if res['name'] == name:
-                    if region:
-                        return k.replace('regions/', '')
-                    else:
-                        return k.replace('zones/', '')
+                    rz_name = k.replace('%ss/' % (rz), '')
+                    break
+        if not rz_name:
+            raise ResourceNotFoundError(
+                '%s \'%s\' not found in any %s.' % (res_name, name, rz),
+                None, None)
+        else:
+            getrz = getattr(self, 'ex_get_%s' % (rz))
+            return getrz(rz_name)
 
     def _match_images(self, project, partial_name):
         """
@@ -431,8 +652,9 @@ class GCENodeDriver(NodeDriver):
                               image.
         @type   partial_name: C{str}
 
-        @return:  The latest image object that maches the partial name.
-        @rtype:   L{NodeImage}
+        @return:  The latest image object that maches the partial name or None
+                  if no matching image is found.
+        @rtype:   L{NodeImage} or C{None}
         """
         project_images = self.list_images(project)
         partial_match = []
@@ -447,6 +669,44 @@ class GCENodeDriver(NodeDriver):
         if partial_match:
             return partial_match[1]
 
+    def _set_region(self, region):
+        """
+        Return the region to use for listing resources.
+
+        @param  region: A name, region object, None, or 'all'
+        @type   region: C{str} or L{GCERegion} or C{None}
+
+        @return:  A region object or None if all regions should be considered
+        @rtype:   L{GCERegion} or C{None}
+        """
+        region = region or self.region
+
+        if region == 'all' or region is None:
+            return None
+
+        if not hasattr(region, 'name'):
+            region = self.ex_get_region(region)
+        return region
+
+    def _set_zone(self, zone):
+        """
+        Return the zone to use for listing resources.
+
+        @param  zone: A name, zone object, None, or 'all'
+        @type   region: C{str} or L{GCEZone} or C{None}
+
+        @return:  A zone object or None if all zones should be considered
+        @rtype:   L{GCEZone} or C{None}
+        """
+        zone = zone or self.zone
+
+        if zone == 'all' or zone is None:
+            return None
+
+        if not hasattr(zone, 'name'):
+            zone = self.ex_get_zone(zone)
+        return zone
+
     def ex_list_addresses(self, region=None):
         """
         Return a list of static addreses for a region or all.
@@ -461,16 +721,11 @@ class GCENodeDriver(NodeDriver):
         @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
-
+        region = self._set_region(region)
         if region is None:
             request = '/aggregated/addresses'
         else:
-            request = '/regions/%s/addresses' % region
-
+            request = '/regions/%s/addresses' % (region.name)
         response = self.connection.request(request, method='GET').object
 
         if 'items' in response:
@@ -485,6 +740,20 @@ class GCENodeDriver(NodeDriver):
                                   response['items']]
         return list_addresses
 
+    def ex_list_healthchecks(self):
+        """
+        Return the list of health checks.
+
+        @return: A list of health check objects.
+        @rtype: C{list} of L{GCEHealthCheck}
+        """
+        list_healthchecks = []
+        request = '/global/httpHealthChecks'
+        response = self.connection.request(request, method='GET').object
+        list_healthchecks = [self._to_healthcheck(h) for h in
+                             response.get('items', [])]
+        return list_healthchecks
+
     def ex_list_firewalls(self):
         """
         Return the list of firewalls.
@@ -499,6 +768,41 @@ class GCENodeDriver(NodeDriver):
                           response.get('items', [])]
         return list_firewalls
 
+    def ex_list_forwarding_rules(self, region=None):
+        """
+        Return the list of forwarding rules for a region or all.
+
+        @keyword  region: The region to return forwarding rules from.  For
+                          example: 'us-central1'.  If None, will return
+                          forwarding rules from the region of self.region
+                          (which is based on self.zone).  If 'all', will
+                          return all forwarding rules.
+        @type     region: C{str} or L{GCERegion} or C{None}
+
+        @return: A list of forwarding rule objects.
+        @rtype: C{list} of L{GCEForwardingRule}
+        """
+        list_forwarding_rules = []
+        region = self._set_region(region)
+        if region is None:
+            request = '/aggregated/forwardingRules'
+        else:
+            request = '/regions/%s/forwardingRules' % (region.name)
+        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 v in response['items'].values():
+                    region_forwarding_rules = [self._to_forwarding_rule(f) for
+                                               f in v.get('forwardingRules',
+                                                          [])]
+                    list_forwarding_rules.extend(region_forwarding_rules)
+            else:
+                list_forwarding_rules = [self._to_forwarding_rule(f) for f in
+                                         response['items']]
+        return list_forwarding_rules
+
     def list_images(self, ex_project=None):
         """
         Return a list of image objects for a project.
@@ -568,17 +872,11 @@ class GCENodeDriver(NodeDriver):
         @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
+        zone = self._set_zone(ex_zone)
         if zone is None:
             request = '/aggregated/instances'
-        elif hasattr(zone, 'name'):
-            request = '/zones/%s/instances' % zone.name
         else:
-            request = '/zones/%s/instances' % zone
+            request = '/zones/%s/instances' % (zone.name)
 
         response = self.connection.request(request, method='GET').object
 
@@ -593,6 +891,19 @@ class GCENodeDriver(NodeDriver):
                 list_nodes = [self._to_node(i) for i in response['items']]
         return list_nodes
 
+    def ex_list_regions(self):
+        """
+        Return the list of regions.
+
+        @return: A list of region objects.
+        @rtype: C{list} of L{GCERegion}
+        """
+        list_regions = []
+        request = '/regions'
+        response = self.connection.request(request, method='GET').object
+        list_regions = [self._to_region(r) for r in response['items']]
+        return list_regions
+
     def list_sizes(self, location=None):
         """
         Return a list of sizes (machineTypes) in a zone.
@@ -604,21 +915,17 @@ class GCENodeDriver(NodeDriver):
         @rtype:   C{list} of L{GCENodeSize}
         """
         list_sizes = []
-        location = location or self.zone
-        if location == 'all':
-            location = None
-        if location is None:
+        zone = self._set_zone(location)
+        if zone is None:
             request = '/aggregated/machineTypes'
-        elif hasattr(location, 'name'):
-            request = '/zones/%s/machineTypes' % location.name
         else:
-            request = '/zones/%s/machineTypes' % location
+            request = '/zones/%s/machineTypes' % (zone.name)
 
         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:
+            if zone is None:
                 for v in response['items'].values():
                     zone_sizes = [self._to_node_size(s) for s in
                                   v.get('machineTypes', [])]
@@ -627,6 +934,33 @@ class GCENodeDriver(NodeDriver):
                 list_sizes = [self._to_node_size(s) for s in response['items']]
         return list_sizes
 
+    def ex_list_targetpools(self, region=None):
+        """
+        Return the list of target pools.
+
+        @return:  A list of target pool objects
+        @rtype:   C{list} of L{GCETargetPool}
+        """
+        list_targetpools = []
+        region = self._set_region(region)
+        if region is None:
+            request = '/aggregated/targetPools'
+        else:
+            request = '/regions/%s/targetPools' % (region.name)
+        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 v in response['items'].values():
+                    region_targetpools = [self._to_targetpool(t) for t in
+                                          v.get('targetPools', [])]
+                    list_targetpools.extend(region_targetpools)
+            else:
+                list_targetpools = [self._to_targetpool(t) for t in
+                                    response['items']]
+        return list_targetpools
+
     def list_volumes(self, ex_zone=None):
         """
         Return a list of volumes for a zone or all.
@@ -634,22 +968,18 @@ class GCENodeDriver(NodeDriver):
         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}
+        @keyword  ex_zone: The zone to return volumes from.
+        @type     ex_zone: 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
+        zone = self._set_zone(ex_zone)
         if zone is None:
             request = '/aggregated/disks'
-        elif hasattr(zone, 'name'):
-            request = '/zones/%s/disks' % zone.name
         else:
-            request = '/zones/%s/disks' % zone
+            request = '/zones/%s/disks' % (zone.name)
 
         response = self.connection.request(request, method='GET').object
         if 'items' in response:
@@ -684,25 +1014,80 @@ class GCENodeDriver(NodeDriver):
         @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}
+        @keyword  region: Name of region for the address (e.g. 'us-central1')
+        @type     region: C{str} or L{GCERegion}
 
         @return:  Static Address object
         @rtype:   L{GCEAddress}
         """
-        if region is None and self.zone:
-            region = '-'.join(self.zone.name.split('-')[:-1])
+        region = region or self.region
+        if not hasattr(region, 'name'):
+            region = self.ex_get_region(region)
         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
+        request = '/regions/%s/addresses' % (region.name)
         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_healthcheck(self, name, host=None, path=None, port=None,
+                              interval=None, timeout=None,
+                              unhealthy_threshold=None,
+                              healthy_threshold=None):
+        """
+        Create an Http Health Check.
+
+        @param  name: Name of health check
+        @type   name: C{str}
+
+        @keyword  host: Hostname of health check requst.  Defaults to empty and
+                        public IP is used instead.
+        @type     host: C{str}
+
+        @keyword  path: The request path for the check.  Defaults to /.
+        @type     path: C{str}
+
+        @keyword  port: The TCP port number for the check.  Defaults to 80.
+        @type     port: C{int}
+
+        @keyword  interval: How often (in seconds) to check.  Defaults to 5.
+        @type     interval: C{int}
+
+        @keyword  timeout: How long to wait before failing. Defaults to 5.
+        @type     timeout: C{int}
+
+        @keyword  unhealthy_threshold: How many failures before marking
+                                       unhealthy.  Defaults to 2.
+        @type     unhealthy_threshold: C{int}
+
+        @keyword  healthy_threshold: How many successes before marking as
+                                     healthy.  Defaults to 2.
+        @type     healthy_threshold: C{int}
+
+        @return:  Health Check object
+        @rtype:   L{GCEHealthCheck}
+        """
+        hc_data = {}
+        hc_data['name'] = name
+        if host:
+            hc_data['host'] = host
+        # As of right now, the 'default' values aren't getting set when called
+        # through the API, so set them explicitly
+        hc_data['requestPath'] = path or '/'
+        hc_data['port'] = port or 80
+        hc_data['checkIntervalSec'] = interval or 5
+        hc_data['timeoutSec'] = timeout or 5
+        hc_data['unhealthyThreshold'] = unhealthy_threshold or 2
+        hc_data['healthyThreshold'] = healthy_threshold or 2
+
+        request = '/global/httpHealthChecks'
+
+        response = self.connection.async_request(request, method='POST',
+                                                 data=hc_data).object
+        return self.ex_get_healthcheck(name)
+
     def ex_create_firewall(self, name, allowed, network='default',
                            source_ranges=None, source_tags=None):
         """
@@ -731,7 +1116,8 @@ class GCENodeDriver(NodeDriver):
         @type     network: C{str} or L{GCENetwork}
 
         @keyword  source_ranges: A list of IP ranges in CIDR format that the
-                                 firewall should apply to.
+                                 firewall should apply to. Defaults to
+                                 ['0.0.0.0/0']
         @type     source_ranges: C{list} of C{str}
 
         @keyword  source_tags: A list of instance tags which the rules apply
@@ -749,8 +1135,7 @@ class GCENodeDriver(NodeDriver):
         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
+        firewall_data['sourceRanges'] = source_ranges or ['0.0.0.0/0']
         if source_tags is not None:
             firewall_data['sourceTags'] = source_tags
 
@@ -758,10 +1143,63 @@ class GCENodeDriver(NodeDriver):
 
         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_forwarding_rule(self, name, targetpool, region=None,
+                                  protocol='tcp', port_range=None,
+                                  address=None):
+        """
+        Create a forwarding rule.
+
+        @param  name: Name of forwarding rule to be created
+        @type   name: C{str}
+
+        @param  targetpool: Target pool to apply the rule to
+        @param  targetpool: C{str} or L{GCETargetPool}
+
+        @keyword  region: Region to create the forwarding rule in.  Defaults to
+                          self.region
+        @type     region: C{str} or L{GCERegion}
+
+        @keyword  protocol: Should be 'tcp' or 'udp'
+        @type     protocol: C{str}
+
+        @keyword  port_range: Optional single port number or range separated
+                              by a dash.  Examples: '80', '5000-5999'.
+        @type     port_range: C{str}
+
+        @keyword  address: Optional static address for forwarding rule. Must be
+                           in same region.
+        @type     address: C{str} or L{GCEAddress}
+
+        @return:  Forwarding Rule object
+        @rtype:   L{GCEForwardingRule}
+        """
+        forwarding_rule_data = {}
+        region = region or self.region
+        if not hasattr(region, 'name'):
+            region = self.ex_get_region(region)
+        if not hasattr(targetpool, 'name'):
+            targetpool = self.ex_get_targetpool(targetpool, region)
+
+        forwarding_rule_data['name'] = name
+        forwarding_rule_data['region'] = region.extra['selfLink']
+        forwarding_rule_data['target'] = targetpool.extra['selfLink']
+        forwarding_rule_data['protocol'] = protocol.upper()
+        if address:
+            if not hasattr(address, 'name'):
+                address = self.ex_get_address(address, region)
+            forwarding_rule_data['IPAddress'] = address.extra['selfLink']
+        if port_range:
+            forwarding_rule_data['portRange'] = port_range
+
+        request = '/regions/%s/forwardingRules' % (region.name)
+
+        response = self.connection.async_request(
+            request, method='POST', data=forwarding_rule_data).object
+
+        return self.ex_get_forwarding_rule(name)
+
     def ex_create_network(self, name, cidr):
         """
         Create a network.
@@ -783,13 +1221,12 @@ class GCENodeDriver(NodeDriver):
 
         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):
+                         tags=None, metadata=None, boot_disk=None,
+                         persistent_disk=False):
         """
         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}.
@@ -816,9 +1253,14 @@ class GCENodeDriver(NodeDriver):
         @keyword  metadata: Metadata dictionary for instance.
         @type     metadata: C{dict}
 
-        @keyword  boot_disk:  Persistent boot disk to attach
+        @keyword  boot_disk:  Persistent boot disk to attach.
         @type     L{StorageVolume}
 
+        @keyword  persistent_disk: If True, create a persistent disk instead of
+                                   an ephemeral one.  Has no effect if
+                                   boot_disk is specified.
+        @type     persistent_disk: C{bool}
+
         @return:  A tuple containing a request string and a node_data dict.
         @rtype:   C{tuple} of C{str} and C{dict}
         """
@@ -829,6 +1271,9 @@ class GCENodeDriver(NodeDriver):
             node_data['tags'] = {'items': tags}
         if metadata:
             node_data['metadata'] = metadata
+        if (not boot_disk) and persistent_disk:
+            boot_disk = self.create_volume(None, name, location=location,
+                                           image=image)
         if boot_disk:
             disks = [{'kind': 'compute#attachedDisk',
                       'boot': True,
@@ -848,13 +1293,13 @@ class GCENodeDriver(NodeDriver):
                'network': network.extra['selfLink']}]
         node_data['networkInterfaces'] = ni
 
-        request = '/zones/%s/instances' % location.name
+        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):
+                    ex_boot_disk=None, ex_persistent_disk=False):
         """
         Create a new node and return a node object for the node.
 
@@ -881,7 +1326,12 @@ class GCENodeDriver(NodeDriver):
         @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}
+        @type     ex_boot_disk: L{StorageVolume} or C{str}
+
+        @keyword  ex_persistent_disk: If True, create a persistent_disk instead
+                                      of a ephemeral one.  Has no effect if
+                                      ex_boot_disk is specified.
+        @type     ex_persistent_disk: C{bool}
 
         @return:  A Node object for the new node.
         @rtype:   L{Node}
@@ -899,18 +1349,17 @@ class GCENodeDriver(NodeDriver):
         request, node_data = self._create_node_req(name, size, image,
                                                    location, ex_network,
                                                    ex_tags, ex_metadata,
-                                                   ex_boot_disk)
+                                                   ex_boot_disk,
+                                                   ex_persistent_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,
+                                 ignore_errors=True, ex_persistent_disk=False,
                                  timeout=DEFAULT_TASK_COMPLETION_TIMEOUT):
         """
         Create multiple nodes and return a list of Node objects.
@@ -950,6 +1399,10 @@ class GCENodeDriver(NodeDriver):
                                  more nodes fails.
         @type     ignore_errors: C{bool}
 
+        @keyword  persistent_disk: If True, create persistent boot disks
+                                   instead of ephemeral ones.
+        @type     persistent_disk: C{bool}
+
         @keyword  timeout: The number of seconds to wait for all nodes to be
                            created before timing out.
 
@@ -971,9 +1424,9 @@ class GCENodeDriver(NodeDriver):
         responses = []
         for i in range(number):
             name = '%s-%03d' % (base_name, i)
-            request, node_data = self._create_node_req(name, size, image,
-                                                       location, ex_network,
-                                                       ex_tags, ex_metadata)
+            request, node_data = self._create_node_req(
+                name, size, image, location, ex_network, ex_tags, ex_metadata,
+                persistent_disk=ex_persistent_disk)
             response = self.connection.request(request, method='POST',
                                                data=node_data)
             responses.append(response.object)
@@ -988,17 +1441,19 @@ class GCENodeDriver(NodeDriver):
             for i, operation in enumerate(responses):
                 if operation is None:
                     continue
-                response = self.connection.request(
-                    operation['selfLink']).object
+                error = None
+                try:
+                    response = self.connection.request(
+                        operation['selfLink']).object
+                except:
+                    e = self._catch_error(ignore_errors=ignore_errors)
+                    error = e.value
+                    code = e.code
                 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'])
+                    if error:
+                        node_list[i] = GCEFailedNode(name, error, code)
                     else:
                         node_list[i] = self.ex_get_node(name, location.name)
                 else:
@@ -1006,6 +1461,56 @@ class GCENodeDriver(NodeDriver):
                     time.sleep(2)
         return node_list
 
+    def ex_create_targetpool(self, name, region=None, healthchecks=None,
+                             nodes=None):
+        """
+        Create a target pool.
+
+        @param  name: Name of target pool
+        @type   name: C{str}
+
+        @keyword  region: Region to create the target pool in. Defaults to
+                          self.region
+        @type     region: C{str} or L{GCERegion} or C{None}
+
+        @keyword  healthchecks: Optional list of health checks to attach
+        @type     healthchecks: C{list} of C{str} or L{GCEHealthCheck}
+
+        @keyword  nodes:  Optional list of nodes to attach to the pool
+        @type     nodes:  C{list} of C{str} or L{Node}
+
+        @return:  Target Pool object
+        @rtype:   L{GCETargetPool}
+        """
+        region = region or self.region
+        targetpool_data = {}
+        targetpool_data['name'] = name
+        if not hasattr(region, 'name'):
+            region = self.ex_get_region(region)
+        targetpool_data['region'] = region.extra['selfLink']
+
+        if healthchecks:
+            if not hasattr(healthchecks[0], 'name'):
+                hc_list = [self.ex_get_healthcheck(h).extra['selfLink'] for h
+                           in healthchecks]
+            else:
+                hc_list = [h.extra['selfLink'] for h in healthchecks]
+            targetpool_data['healthChecks'] = hc_list
+        if nodes:
+            if not hasattr(nodes[0], 'name'):
+                node_list = [self.ex_get_node(n, 'all').extra['selfLink'] for n
+                             in nodes]
+            else:
+                node_list = [n.extra['selfLink'] for n in nodes]
+            targetpool_data['instances'] = node_list
+
+        request = '/regions/%s/targetPools' % (region.name)
+
+        response = self.connection.async_request(request, method='POST',
+                                                 data=targetpool_data).object
+
+        return self.ex_get_targetpool(name, region)
+
     def create_volume(self, size, name, location=None, image=None,
                       snapshot=None):
         """
@@ -1024,7 +1529,7 @@ class GCENodeDriver(NodeDriver):
         @keyword  image: Image to create disk from.
         @type     image: L{NodeImage} or C{str} or C{None}
 
-        @keyword  snapshot: Snapshot to create image from
+        @keyword  snapshot: Snapshot to create image from (needs full URI)
         @type     snapshot: C{str}
 
         @return:  Storage Volume object
@@ -1039,19 +1544,53 @@ class GCENodeDriver(NodeDriver):
             if not hasattr(image, 'name'):
                 image = self.ex_get_image(image)
             params = {'sourceImage': image.extra['selfLink']}
+            volume_data['description'] = 'Image: %s' % (
+                image.extra['selfLink'])
         if snapshot:
             volume_data['sourceSnapshot'] = snapshot
+            volume_data['description'] = 'Snapshot: %s' % (snapshot)
         location = location or self.zone
         if not hasattr(location, 'name'):
             location = self.ex_get_zone(location)
-        request = '/zones/%s/disks' % location.name
+        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)
+        return self.ex_get_volume(name, location)
+
+    def ex_update_healthcheck(self, healthcheck):
+        """
+        Update a health check with new values.
+
+        To update, change the attributes of the health check object and pass
+        the updated object to the method.
+
+        @param  healthcheck: A healthcheck object with updated values.
+        @type   healthcheck: L{GCEHealthCheck}
+
+        @return:  An object representing the new state of the health check.
+        @rtype:   L{GCEHealthCheck}
+        """
+        hc_data = {}
+        hc_data['name'] = healthcheck.name
+        hc_data['requestPath'] = healthcheck.path
+        hc_data['port'] = healthcheck.port
+        hc_data['checkIntervalSec'] = healthcheck.interval
+        hc_data['timeoutSec'] = healthcheck.timeout
+        hc_data['unhealthyThreshold'] = healthcheck.unhealthy_threshold
+        hc_data['healthyThreshold'] = healthcheck.healthy_threshold
+        if healthcheck.extra['host']:
+            hc_data['host'] = healthcheck.extra['host']
+        if healthcheck.extra['description']:
+            hc_data['description'] = healthcheck.extra['description']
+
+        request = '/global/httpHealthChecks/%s' % (healthcheck.name)
+
+        response = self.connection.async_request(request, method='PUT',
+                                                 data=hc_data).object
+
+        return self.ex_get_healthcheck(healthcheck.name)
 
     def ex_update_firewall(self, firewall):
         """
@@ -1077,15 +1616,134 @@ class GCENodeDriver(NodeDriver):
         if firewall.extra['description']:
             firewall_data['description'] = firewall.extra['description']
 
-        request = '/global/firewalls/%s' % firewall.name
+        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 ex_targetpool_add_node(self, targetpool, node):
+        """
+        Add a node to a target pool.
+
+        @param  targetpool: The targetpool to add node to
+        @type   targetpool: C{str} or L{GCETargetPool}
+
+        @param  node: The node to add
+        @type   node: C{str} or L{Node}
+
+        @returns: True if successful
+        @rtype:   C{bool}
+        """
+        if not hasattr(targetpool, 'name'):
+            targetpool = self.ex_get_targetpool(targetpool)
+        if not hasattr(node, 'name'):
+            node = self.ex_get_node(node, 'all')
+
+        targetpool_data = {'instance': node.extra['selfLink']}
+
+        request = '/regions/%s/targetPools/%s/addInstance' % (
+            targetpool.region.name, targetpool.name)
+        response = self.connection.async_request(request, method='POST',
+                                                 data=targetpool_data).object
+        targetpool.nodes.append(node)
+        return True
+
+    def ex_targetpool_add_healthcheck(self, targetpool, healthcheck):
+        """
+        Add a health check to a target pool.
+
+        @param  targetpool: The targetpool to add health check to
+        @type   targetpool: C{str} or L{GCETargetPool}
+
+        @param  healthcheck: The healthcheck to add
+        @type   healthcheck: C{str} or L{GCEHealthCheck}
+
+        @returns: True if successful
+        @rtype:   C{bool}
+        """
+        if not hasattr(targetpool, 'name'):
+            targetpool = self.ex_get_targetpool(targetpool)
+        if not hasattr(healthcheck, 'name'):
+            healthcheck = self.ex_get_healthcheck(healthcheck)
+
+        targetpool_data = {'healthCheck': healthcheck.extra['selfLink']}
+
+        request = '/regions/%s/targetPools/%s/addHealthCheck' % (
+            targetpool.region.name, targetpool.name)
+        response = self.connection.async_request(request, method='POST',
+                                                 data=targetpool_data).object
+        targetpool.healthchecks.append(healthcheck)
+        return True
+
+    def ex_targetpool_remove_node(self, targetpool, node):
+        """
+        Remove a node from a target pool.
+
+        @param  targetpool: The targetpool to remove node from
+        @type   targetpool: C{str} or L{GCETargetPool}
+
+        @param  node: The node to remove
+        @type   node: C{str} or L{Node}
+
+        @returns: True if successful
+        @rtype:   C{bool}
+        """
+        if not hasattr(targetpool, 'name'):
+            targetpool = self.ex_get_targetpool(targetpool)
+        if not hasattr(node, 'name'):
+            node = self.ex_get_node(node, 'all')
+
+        targetpool_data = {'instance': node.extra['selfLink']}
+
+        request = '/regions/%s/targetPools/%s/removeInstance' % (
+            targetpool.region.name, targetpool.name)
+        response = self.connection.async_request(request, method='POST',
+                                                 data=targetpool_data).object
+        # Remove node object from node list
+        index = None
+        for i, nd in enumerate(targetpool.nodes):
+            if nd.name == node.name:
+                index = i
+                break
+        if index is not None:
+            targetpool.nodes.pop(index)
+        return True
+
+    def ex_targetpool_remove_healthcheck(self, targetpool, healthcheck):
+        """
+        Remove a health check from a target pool.
+
+        @param  targetpool: The targetpool to remove health check from
+        @type   targetpool: C{str} or L{GCETargetPool}
+
+        @param  healthcheck: The healthcheck to remove
+        @type   healthcheck: C{str} or L{GCEHealthCheck}
+
+        @returns: True if successful
+        @rtype:   C{bool}
+        """
+        if not hasattr(targetpool, 'name'):
+            targetpool = self.ex_get_targetpool(targetpool)
+        if not hasattr(healthcheck, 'name'):
+            healthcheck = self.ex_get_healthcheck(healthcheck)
+
+        targetpool_data = {'healthCheck': healthcheck.extra['selfLink']}
+
+        request = '/regions/%s/targetPools/%s/removeHealthCheck' % (
+            targetpool.region.name, targetpool.name)
+        response = self.connection.async_request(request, method='POST',
+                                                 data=targetpool_data).object
+        # Remove healthcheck object from healthchecks list
+        index = None
+        for i, hc in enumerate(targetpool.healthchecks):
+            if hc.name == healthcheck.name:
+                index = i
+        if index is not None:
+            targetpool.healthchecks.pop(index)
+        return True
+
     def reboot_node(self, node):
         """
         Reboot a node.
@@ -1100,10 +1758,7 @@ class GCENodeDriver(NodeDriver):
                                                     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
+        return True
 
     def ex_set_node_tags(self, node, tags):
         """
@@ -1129,13 +1784,10 @@ class GCENodeDriver(NodeDriver):
 
         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
+        new_node = self.ex_get_node(node.name, node.extra['zone'])
+        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):
@@ -1222,10 +1874,7 @@ class GCENodeDriver(NodeDriver):
             node.extra['zone'].name, node.name)
         response = self.connection.async_request(request, method='POST',
                                                  data=volume_data).object
-        if 'error' in response:
-            self._cateforize_error(response['error'])
-        else:
-            return True
+        return True
 
     def detach_volume(self, volume, ex_node=None):
         """
@@ -1247,10 +1896,7 @@ class GCENodeDriver(NodeDriver):
 
         response = self.connection.async_request(request, method='POST',
                                                  data='ignored').object
-        if 'error' in response:
-            self._categorize_error(response['error'])
-        else:
-            return True
+        return True
 
     def ex_destroy_address(self, address):
         """
@@ -1262,14 +1908,27 @@ class GCENodeDriver(NodeDriver):
         @return:  True if successful
         @rtype:   C{bool}
         """
-        request = '/regions/%s/addresses/%s' % (address.region, address.name)
+        request = '/regions/%s/addresses/%s' % (address.region.name,
+                                                address.name)
 
         response = self.connection.async_request(request,
                                                  method='DELETE').object
-        if 'error' in response:
-            self._categorize_error(response['error'])
-        else:
-            return True
+        return True
+
+    def ex_destroy_healthcheck(self, healthcheck):
+        """
+        Destroy a healthcheck.
+
+        @param  healthcheck: Health check object to destroy
+        @type   healthcheck: L{GCEHealthCheck}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/global/httpHealthChecks/%s' % (healthcheck.name)
+        response = self.connection.async_request(request,
+                                                 method='DELETE').object
+        return True
 
     def ex_destroy_firewall(self, firewall):
         """
@@ -1281,13 +1940,26 @@ class GCENodeDriver(NodeDriver):
         @return:  True if successful
         @rtype:   C{bool}
         """
-        request = '/global/firewalls/%s' % firewall.name
+        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
+        return True
+
+    def ex_destroy_forwarding_rule(self, forwarding_rule):
+        """
+        Destroy a forwarding rule.
+
+        @param  forwarding_rule: Forwarding Rule object to destroy
+        @type   forwarding_rule: L{GCEForwardingRule}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/regions/%s/forwardingRules/%s' % (
+            forwarding_rule.region.name, forwarding_rule.name)
+        response = self.connection.async_request(request,
+                                                 method='DELETE').object
+        return True
 
     def ex_destroy_network(self, network):
         """
@@ -1299,13 +1971,10 @@ class GCENodeDriver(NodeDriver):
         @return:  True if successful
         @rtype:   C{bool}
         """
-        request = '/global/networks/%s' % network.name
+        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
+        return True
 
     def destroy_node(self, node):
         """
@@ -1321,10 +1990,7 @@ class GCENodeDriver(NodeDriver):
                                               node.name)
         response = self.connection.async_request(request,
                                                  method='DELETE').object
-        if 'error' in response:
-            self._categorize_error(response['error'])
-        else:
-            return True
+        return True
 
     def ex_destroy_multiple_nodes(self, nodelist, ignore_errors=True,
                                   timeout=DEFAULT_TASK_COMPLETION_TIMEOUT):
@@ -1353,7 +2019,12 @@ class GCENodeDriver(NodeDriver):
         for node in nodelist:
             request = '/zones/%s/instances/%s' % (node.extra['zone'].name,
                                                   node.name)
-            response = self.connection.request(request, method='DELETE').object
+            try:
+                response = self.connection.request(request,
+                                                   method='DELETE').object
+            except:
+                e = self._catch_error(ignore_errors=ignore_errors)
+                response = None
             responses.append(response)
 
         while not complete:
@@ -1364,22 +2035,38 @@ class GCENodeDriver(NodeDriver):
             for i, operation in enumerate(responses):
                 if operation is None:
                     continue
-                response = self.connection.request(
-                    operation['selfLink']).object
+                no_errors = True
+                try:
+                    response = self.connection.request(
+                        operation['selfLink']).object
+                except:
+                    e = self._catch_error(ignore_errors=ignore_errors)
+                    no_errors = False
                 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
+                    success[i] = no_errors
                 else:
                     complete = False
                     time.sleep(2)
         return success
 
+    def ex_destroy_targetpool(self, targetpool):
+        """
+        Destroy a target pool.
+
+        @param  targetpool: TargetPool object to destroy
+        @type   targetpool: L{GCETargetPool}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        request = '/regions/%s/targetPools/%s' % (targetpool.region.name,
+                                                  targetpool.name)
+
+        response = self.connection.async_request(request,
+                                                 method='DELETE').object
+        return True
+
     def destroy_volume(self, volume):
         """
         Destroy a volume.
@@ -1394,10 +2081,7 @@ class GCENodeDriver(NodeDriver):
                                           volume.name)
         response = self.connection.async_request(request,
                                                  method='DELETE').object
-        if 'error' in response:
-            self._categorize_error(response['error'])
-        else:
-            return True
+        return True
 
     def ex_get_address(self, name, region=None):
         """
@@ -1406,18 +2090,33 @@ class GCENodeDriver(NodeDriver):
         @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}
+        @keyword  region: The region to search for the address in (set to
+                          'all' to search all regions)
+        @type     region: C{str} L{GCERegion} 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)
+        region = self._set_region(region) or self._find_zone_or_region(
+            name, 'addresses', region=True, res_name='Address')
+        request = '/regions/%s/addresses/%s' % (region.name, name)
         response = self.connection.request(request, method='GET').object
         return self._to_address(response)
 
+    def ex_get_healthcheck(self, name):
+        """
+        Return a HealthCheck object based on the healthcheck name.
+
+        @param  name: The name of the healthcheck
+        @type   name: C{str}
+
+        @return:  A GCEHealthCheck object
+        @rtype:   L{GCEHealthCheck}
+        """
+        request = '/global/httpHealthChecks/%s' % (name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_healthcheck(response)
+
     def ex_get_firewall(self, name):
         """
         Return a Firewall object based on the firewall name.
@@ -1428,10 +2127,30 @@ class GCENodeDriver(NodeDriver):
         @return:  A GCEFirewall object
         @rtype:   L{GCEFirewall}
         """
-        request = '/global/firewalls/%s' % name
+        request = '/global/firewalls/%s' % (name)
         response = self.connection.request(request, method='GET').object
         return self._to_firewall(response)
 
+    def ex_get_forwarding_rule(self, name, region=None):
+        """
+        Return a Forwarding Rule object based on the forwarding rule name.
+
+        @param  name: The name of the forwarding rule
+        @type   name: C{str}
+
+        @keyword  region: The region to search for the rule in (set to 'all'
+                          to search all regions).
+        @type     region: C{str} or C{None}
+
+        @return:  A GCEForwardingRule object
+        @rtype:   L{GCEForwardingRule}
+        """
+        region = self._set_region(region) or self._find_zone_or_region(
+            name, 'forwardingRules', region=True, res_name='ForwardingRule')
+        request = '/regions/%s/forwardingRules/%s' % (region.name, name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_forwarding_rule(response)
+
     def ex_get_image(self, partial_name):
         """
         Return an NodeImage object based on the name or link provided.
@@ -1440,8 +2159,9 @@ class GCENodeDriver(NodeDriver):
                               image.
         @type   partial_name: C{str}
 
-        @return:  NodeImage object based on provided information
-        @rtype:   L{NodeImage}
+        @return:  NodeImage object based on provided information or None if an
+                  image with that name is not found.
+        @rtype:   L{NodeImage} or C{None}
         """
         if partial_name.startswith('https://'):
             response = self.connection.request(partial_name, method='GET')
@@ -1465,7 +2185,7 @@ class GCENodeDriver(NodeDriver):
         @return:  A Network object for the network
         @rtype:   L{GCENetwork}
         """
-        request = '/global/networks/%s' % name
+        request = '/global/networks/%s' % (name)
         response = self.connection.request(request, method='GET').object
         return self._to_network(response)
 
@@ -1476,15 +2196,15 @@ class GCENodeDriver(NodeDriver):
         @param  name: The name of the node
         @type   name: C{str}
 
-        @keyword  zone: The zone to search for the node in
+        @keyword  zone: The zone to search for the node in.  If set to 'all',
+                        search all zones for the instance.
         @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)
+        zone = self._set_zone(zone) or self._find_zone_or_region(
+            name, 'instances', res_name='Node')
         request = '/zones/%s/instances/%s' % (zone.name, name)
         response = self.connection.request(request, method='GET').object
         return self._to_node(response)
@@ -1526,19 +2246,62 @@ class GCENodeDriver(NodeDriver):
         @param  name: The name of the volume
         @type   name: C{str}
 
-        @keyword  zone: The zone to search for the volume in
+        @keyword  zone: The zone to search for the volume in (set to 'all' to
+                        search all zones)
         @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)
+        zone = self._set_zone(zone) or self._find_zone_or_region(
+            name, 'disks', res_name='Volume')
         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_region(self, name):
+        """
+        Return a Region object based on the region name.
+
+        @param  name: The name of the region.
+        @type   name: C{str}
+
+        @return:  A GCERegion object for the region
+        @rtype:   L{GCERegion}
+        """
+        if name.startswith('https://'):
+            short_name = self._get_components_from_path(name)['name']
+            request = name
+        else:
+            short_name = name
+            request = '/regions/%s' % (name)
+        # Check region cache first
+        if short_name in self.region_dict:
+            return self.region_dict[short_name]
+        # Otherwise, look up region information
+        response = self.connection.request(request, method='GET').object
+        return self._to_region(response)
+
+    def ex_get_targetpool(self, name, region=None):
+        """
+        Return a TargetPool object based on a name and optional region.
+
+        @param  name: The name of the target pool
+        @type   name: C{str}
+
+        @keyword  region: The region to search for the target pool in (set to
+                          'all' to search all regions).
+        @type     region: C{str} or L{GCERegion} or C{None}
+
+        @return:  A TargetPool object for the pool
+        @rtype:   L{GCETargetPool}
+        """
+        region = self._set_region(region) or self._find_zone_or_region(
+            name, 'targetPools', region=True, res_name='TargetPool')
+        request = '/regions/%s/targetPools/%s' % (region.name, name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_targetpool(response)
+
     def ex_get_zone(self, name):
         """
         Return a Zone object based on the zone name.
@@ -1546,20 +2309,23 @@ class GCENodeDriver(NodeDriver):
         @param  name: The name of the zone.
         @type   name: C{str}
 
-        @return:  A GCEZone object for the zone
-        @rtype:   L{GCEZone}
+        @return:  A GCEZone object for the zone or None if not found
+        @rtype:   L{GCEZone} or C{None}
         """
         if name.startswith('https://'):
-            short_name = name.split('/')[-1]
+            short_name = self._get_components_from_path(name)['name']
             request = name
         else:
             short_name = name
-            request = '/zones/%s' % 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
+        try:
+            response = self.connection.request(request, method='GET').object
+        except ResourceNotFoundError:
+            return None
         return self._to_zone(response)
 
     def _to_address(self, address):
@@ -1574,16 +2340,41 @@ class GCENodeDriver(NodeDriver):
         """
         extra = {}
 
+        region = self.ex_get_region(address['region'])
+
         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_healthcheck(self, healthcheck):
+        """
+        Return a HealthCheck object from the json-response dictionary.
+
+        @param  healthcheck: The dictionary describing the healthcheck.
+        @type   healthcheck: C{dict}
+
+        @return: HealthCheck object
+        @rtype: L{GCEHealthCheck}
+        """
+        extra = {}
+        extra['selfLink'] = healthcheck['selfLink']
+        extra['creationTimestamp'] = healthcheck['creationTimestamp']
+        extra['description'] = healthcheck.get('description')
+        extra['host'] = healthcheck.get('host')
+
+        return GCEHealthCheck(
+            id=healthcheck['id'], name=healthcheck['name'],
+            path=healthcheck['requestPath'], port=healthcheck['port'],
+            interval=healthcheck['checkIntervalSec'],
+            timeout=healthcheck['timeoutSec'],
+            unhealthy_threshold=healthcheck['unhealthyThreshold'],
+            healthy_threshold=healthcheck['healthyThreshold'],
+            driver=self, extra=extra)
+
     def _to_firewall(self, firewall):
         """
         Return a Firewall object from the json-response dictionary.
@@ -1598,7 +2389,8 @@ class GCENodeDriver(NodeDriver):
         extra['selfLink'] = firewall['selfLink']
         extra['creationTimestamp'] = firewall['creationTimestamp']
         extra['description'] = firewall.get('description')
-        extra['network_name'] = firewall['network'].split('/')[-1]
+        extra['network_name'] = self._get_components_from_path(
+            firewall['network'])['name']
 
         network = self.ex_get_network(extra['network_name'])
         source_ranges = firewall.get('sourceRanges')
@@ -1610,6 +2402,34 @@ class GCENodeDriver(NodeDriver):
                            source_tags=source_tags,
                            driver=self, extra=extra)
 
+    def _to_forwarding_rule(self, forwarding_rule):
+        """
+        Return a Forwarding Rule object from the json-response dictionary.
+
+        @param  forwarding_rule: The dictionary describing the rule.
+        @type   forwarding_rule: C{dict}
+
+        @return: ForwardingRule object
+        @rtype: L{GCEForwardingRule}
+        """
+        extra = {}
+        # Use .get to work around a current API bug.
+        extra['selfLink'] = forwarding_rule.get('selfLink')
+        extra['portRange'] = forwarding_rule['portRange']
+        extra['creationTimestamp'] = forwarding_rule['creationTimestamp']
+        extra['description'] = forwarding_rule.get('description')
+
+        region = self.ex_get_region(forwarding_rule['region'])
+        targetpool = self.ex_get_targetpool(
+            self._get_components_from_path(forwarding_rule['target'])['name'])
+
+        return GCEForwardingRule(id=forwarding_rule['id'],
+                                 name=forwarding_rule['name'], region=region,
+                                 address=forwarding_rule['IPAddress'],
+                                 protocol=forwarding_rule['IPProtocol'],
+                                 targetpool=targetpool,
+                                 driver=self, extra=extra)
+
     def _to_network(self, network):
         """
         Return a Network object from the json-response dictionary.
@@ -1681,6 +2501,7 @@ class GCENodeDriver(NodeDriver):
         extra['description'] = node.get('description')
         extra['zone'] = self.ex_get_zone(node['zone'])
         extra['image'] = node.get('image')
+        extra['machineType'] = node['machineType']
         extra['disks'] = node['disks']
         extra['networkInterfaces'] = node['networkInterfaces']
         extra['id'] = node['id']
@@ -1700,11 +2521,18 @@ class GCENodeDriver(NodeDriver):
             for access_config in network_interface['accessConfigs']:
                 public_ips.append(access_config['natIP'])
 
+        # For the node attributes, use just machine and image names, not full
+        # paths.  Full paths are available in the "extra" dict.
+        if extra['image']:
+            image = self._get_components_from_path(extra['image'])['name']
+        else:
+            image = None
+        size = self._get_components_from_path(node['machineType'])['name']
+
         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)
+                    driver=self, size=size, image=image, extra=extra)
 
     def _to_node_size(self, machine_type):
         """
@@ -1752,6 +2580,33 @@ class GCENodeDriver(NodeDriver):
                           metadata=metadata, quotas=project['quotas'],
                           driver=self, extra=extra)
 
+    def _to_region(self, region):
+        """
+        Return a Region object from the json-response dictionary.
+
+        @param  region: The dictionary describing the region.
+        @type   region: C{dict}
+
+        @return: Region object
+        @rtype: L{GCERegion}
+        """
+        extra = {}
+        extra['selfLink'] = region['selfLink']
+        extra['creationTimestamp'] = region['creationTimestamp']
+        extra['description'] = region['description']
+
+        quotas = region.get('quotas')
+        zones = [self.ex_get_zone(z) for z in region['zones']]
+        # Work around a bug that will occasionally list missing zones in the
+        # region output
+        zones = [z for z in zones if z is not None]
+        deprecated = region.get('deprecated')
+
+        return GCERegion(id=region['id'], name=region['name'],
+                         status=region['status'], zones=zones,
+                         quotas=quotas, deprecated=deprecated,
+                         driver=self, extra=extra)
+
     def _to_storage_volume(self, volume):
         """
         Return a Volume object from the json-response dictionary.
@@ -1767,10 +2622,43 @@ class GCENodeDriver(NodeDriver):
         extra['zone'] = self.ex_get_zone(volume['zone'])
         extra['status'] = volume['status']
         extra['creationTimestamp'] = volume['creationTimestamp']
+        extra['description'] = volume.get('description')
 
         return StorageVolume(id=volume['id'], name=volume['name'],
                              size=volume['sizeGb'], driver=self, extra=extra)
 
+    def _to_targetpool(self, targetpool):
+        """
+        Return a Target Pool object from the json-response dictionary.
+
+        @param  targetpool: The dictionary describing the volume.
+        @type   targetpool: C{dict}
+
+        @return: Target Pool object
+        @rtype:  L{GCETargetPool}
+        """
+        extra = {}
+        extra['selfLink'] = targetpool['selfLink']
+        extra['description'] = targetpool.get('description')
+        region = self.ex_get_region(targetpool['region'])
+        healthcheck_list = [self.ex_get_healthcheck(h.split('/')[-1]) for h
+                            in targetpool.get('healthChecks', [])]
+        node_list = []
+        for n in targetpool.get('instances', []):
+            # Nodes that do not exist can be part of a target pool.  If the
+            # node does not exist, use the URL of the node instead of the node
+            # object.
+            comp = self._get_components_from_path(n)
+            try:
+                node = self.ex_get_node(comp['name'], comp['zone'])
+            except ResourceNotFoundError:
+                node = n
+            node_list.append(node)
+
+        return GCETargetPool(id=targetpool['id'], name=targetpool['name'],
+                             region=region, healthchecks=healthcheck_list,
+                             nodes=node_list, driver=self, extra=extra)
+
     def _to_zone(self, zone):
         """
         Return a Zone object from the json-response dictionary.
@@ -1789,6 +2677,6 @@ class GCENodeDriver(NodeDriver):
         deprecated = zone.get('deprecated')
 
         return GCEZone(id=zone['id'], name=zone['name'], status=zone['status'],
-                       maintenance_windows=zone['maintenanceWindows'],
+                       maintenance_windows=zone.get('maintenanceWindows'),
                        quotas=zone['quotas'], deprecated=deprecated,
                        driver=self, extra=extra)
diff --git a/libcloud/loadbalancer/drivers/gce.py b/libcloud/loadbalancer/drivers/gce.py
new file mode 100644
index 0000000..3f1df14
--- /dev/null
+++ b/libcloud/loadbalancer/drivers/gce.py
@@ -0,0 +1,363 @@
+# 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.
+
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
+from libcloud.loadbalancer.base import LoadBalancer, Member, Driver, Algorithm
+from libcloud.compute.drivers.gce import GCEConnection, GCENodeDriver
+
+# GCE doesn't actually give you a algorithm choice, but this is here simply as
+# the closest match.  The actual algorithm is described here:
+# https://developers.google.com/compute/docs/load-balancing/#overview
+DEFAULT_ALGORITHM = Algorithm.RANDOM
+
+
+class GCELBDriver(Driver):
+    connectionCls = GCEConnection
+    apiname = 'googleapis'
+    name = 'Google Compute Engine'
+    website = 'https://www.googleapis.com/'
+
+    _VALUE_TO_ALGORITHM_MAP = {
+        'RANDOM': Algorithm.RANDOM
+    }
+
+    def __init__(self, *args, **kwargs):
+
+        if kwargs.get('gce_driver'):
+            self.gce = kwargs['gce_driver']
+        else:
+            self.gce = GCENodeDriver(*args, **kwargs)
+
+        self.connection = self.gce.connection
+
+    def _get_node_from_ip(self, ip):
+        """
+        Return the node object that matches a given public IP address.
+
+        @param  ip: Public IP address to search for
+        @type   ip: C{str}
+
+        @return:  Node object that has the given IP, or None if not found.
+        @rtype:   L{Node} or None
+        """
+        all_nodes = self.gce.list_nodes(ex_zone='all')
+        for node in all_nodes:
+            if ip in node.public_ips:
+                return node
+        return None
+
+    def list_protocols(self):
+        """
+        Return a list of supported protocols.
+
+        For GCE, this is simply a hardcoded list.
+
+        @rtype: C{list} of C{str}
+        """
+        return ['TCP', 'UDP']
+
+    def list_balancers(self, ex_region=None):
+        """
+        List all loadbalancers
+
+        @keyword  ex_region: The region to return balancers from.  If None,
+                             will default to self.region.  If 'all', will
+                             return all balancers.
+        @type     ex_region: C{str} or L{GCERegion} or C{None}
+
+        @rtype: C{list} of L{LoadBalancer}
+        """
+        balancers = []
+        for fwr in self.gce.ex_list_forwarding_rules(region=ex_region):
+            balancers.append(self._forwarding_rule_to_loadbalancer(fwr))
+        return balancers
+
+    def create_balancer(self, name, port, protocol, algorithm, members,
+                        ex_region=None, ex_healthchecks=None, ex_address=None):
+        """
+        Create a new load balancer instance.
+
+        For GCE, this means creating a forwarding rule and a matching target
+        pool, then adding the members to the target pool.
+
+        @param  name: Name of the new load balancer (required)
+        @type   C{str}
+
+        @param  port: Port or range of ports the load balancer should listen
+                      on, defaults to all ports.  Examples: '80', '5000-5999'
+        @type   port: C{str}
+
+        @param  protocol: Load balancer protocol.  Should be 'tcp' or 'udp',
+                          defaults to 'tcp'.
+        @type   protocol: C{str}
+
+        @param  members: List of Members to attach to balancer.  Can be Member
+                         objects or Node objects.  Node objects are preferred
+                         for GCE, but Member objects are accepted to comply
+                         with the established libcloud API.  Note that the
+                         'port' attribute of the members is ignored.
+        @type   members: C{list} of L{Member} or L{Node}
+
+        @param  algorithm: Load balancing algorithm.  Ignored for GCE which
+                           uses a hashing-based algorithm.
+        @type   algorithm: L{Algorithm} or C{None}
+
+        @keyword  ex_region:  Optional region to create the load balancer in.
+                              Defaults to the default region of the GCE Node
+                              Driver.
+        @type     ex_region:  C{GCERegion} or C{str}
+
+        @keyword  ex_healthchecks: Optional list of healthcheck objects or
+                                   names to add to the load balancer.
+        @type     ex_healthchecks: C{list} of L{GCEHealthCheck} or C{str}
+
+        @keyword  ex_address: Optional static address object to be assigned to
+                              the load balancer.
+        @type     ex_address: C{GCEAddress}
+
+        @return:  LoadBalancer object
+        @rtype:   L{LoadBalancer}
+        """
+        unused = algorithm
+
+        node_list = []
+        for member in members:
+            # Member object
+            if hasattr(member, 'ip'):
+                if member.extra.get('node'):
+                    node_list.append(member.extra['node'])
+                else:
+                    node_list.append(self._get_node_from_ip(member.ip))
+            # Node object
+            elif hasattr(member, 'name'):
+                node_list.append(member)
+            # Assume it's a node name otherwise
+            else:
+                node_list.append(self.gce.ex_get_node(member, 'all'))
+
+        # Create Target Pool
+        tp_name = '%s-tp' % name
+        targetpool = self.gce.ex_create_targetpool(
+            tp_name, region=ex_region, healthchecks=ex_healthchecks,
+            nodes=node_list)
+
+        # Create the Forwarding rule, but if it fails, delete the target pool.
+        try:
+            forwarding_rule = self.gce.ex_create_forwarding_rule(
+                name, targetpool, region=ex_region, protocol=protocol,
+                port_range=port, address=ex_address)
+        except:
+            targetpool.destroy()
+            raise
+
+        # Reformat forwarding rule to LoadBalancer object
+        return self._forwarding_rule_to_loadbalancer(forwarding_rule)
+
+    def destroy_balancer(self, balancer):
+        """
+        Destroy a load balancer.
+
+        For GCE, this means destroying the associated forwarding rule, then
+        destroying the target pool that was attached to the forwarding rule.
+
+        @param  balancer: LoadBalancer which should be used
+        @type   balancer: L{LoadBalancer}
+
+        @return:  True if successful
+        @rtype:   C{bool}
+        """
+        destroy = balancer.extra['forwarding_rule'].destroy()
+        if destroy:
+            tp_destroy = balancer.extra['targetpool'].destroy()
+            return tp_destroy
+        else:
+            return destroy
+
+    def get_balancer(self, balancer_id):
+        """
+        Return a L{LoadBalancer} object.
+
+        @param  balancer_id: Name of load balancer you wish to fetch.  For GCE,
+                             this is the name of the associated forwarding
+                             rule.
+        @param  balancer_id: C{str}
+
+        @rtype: L{LoadBalancer}
+        """
+        fwr = self.gce.ex_get_forwarding_rule(balancer_id)
+        return self._forwarding_rule_to_loadbalancer(fwr)
+
+    def balancer_attach_compute_node(self, balancer, node):
+        """
+        Attach a compute node as a member to the load balancer.
+
+        @param balancer: LoadBalancer which should be used
+        @type  balancer: L{LoadBalancer}
+
+        @param node: Node to join to the balancer
+        @type  node: L{Node}
+
+        @return: Member after joining the balancer.
+        @rtype: L{Member}
+        """
+        add_node = balancer.extra['targetpool'].add_node(node)
+        if add_node:
+            return self._node_to_member(node, balancer)
+
+    def balancer_attach_member(self, balancer, member):
+        """
+        Attach a member to balancer
+
+        @param balancer: LoadBalancer which should be used
+        @type  balancer: L{LoadBalancer}
+
+        @param member: Member to join to the balancer
+        @type member: L{Member}
+
+        @return: Member after joining the balancer.
+        @rtype: L{Member}
+        """
+        node = member.extra.get('node') or self._get_node_from_ip(member.ip)
+        add_node = balancer.extra['targetpool'].add_node(node)
+        if add_node:
+            return self._node_to_member(node, balancer)
+
+    def balancer_detach_member(self, balancer, member):
+        """
+        Detach member from balancer
+
+        @param balancer: LoadBalancer which should be used
+        @type  balancer: L{LoadBalancer}
+
+        @param member: Member which should be used
+        @type member: L{Member}
+
+        @return: True if member detach was successful, otherwise False
+        @rtype: C{bool}
+        """
+        node = member.extra.get('node') or self._get_node_from_ip(member.ip)
+        remove_node = balancer.extra['targetpool'].remove_node(node)
+        return remove_node
+
+    def balancer_list_members(self, balancer):
+        """
+        Return list of members attached to balancer
+
+        @param balancer: LoadBalancer which should be used
+        @type  balancer: L{LoadBalancer}
+
+        @rtype: C{list} of L{Member}
+        """
+        return [self._node_to_member(n, balancer) for n in
+                balancer.extra['targetpool'].nodes]
+
+    def ex_create_healthcheck(self, *args, **kwargs):
+        return self.gce.ex_create_healthcheck(*args, **kwargs)
+
+    def ex_list_healthchecks(self):
+        return self.gce.ex_list_healthchecks()
+
+    def ex_balancer_attach_healthcheck(self, balancer, healthcheck):
+        """
+        Attach a healthcheck to balancer
+
+        @param balancer: LoadBalancer which should be used
+        @type  balancer: L{LoadBalancer}
+
+        @param healthcheck: Healthcheck to add
+        @type  healthcheck: L{GCEHealthCheck}
+
+        @return: True if successful
+        @rtype:  C{bool}
+        """
+        return balancer.extra['targetpool'].add_healthcheck(healthcheck)
+
+    def ex_balancer_detach_healthcheck(self, balancer, healthcheck):
+        """
+        Detach healtcheck from balancer
+
+        @param balancer: LoadBalancer which should be used
+        @type  balancer: L{LoadBalancer}
+
+        @param healthcheck: Healthcheck to remove
+        @type  healthcheck: L{GCEHealthCheck}
+
+        @return: True if successful
+        @rtype: C{bool}
+        """
+        return balancer.extra['targetpool'].remove_healthcheck(healthcheck)
+
+    def ex_balancer_list_healthchecks(self, balancer):
+        """
+        Return list of healthchecks attached to balancer
+
+        @param  balancer: LoadBalancer which should be used
+        @type   balancer: L{LoadBalancer}
+
+        @rtype: C{list} of L{HealthChecks}
+        """
+        return balancer.extra['healthchecks']
+
+    def _node_to_member(self, node, balancer):
+        """
+        Return a Member object based on a Node.
+
+        @param  node: Node object
+        @type   node: L{Node}
+
+        @keyword  balancer: The balancer the member is attached to.
+        @type     balancer: L{LoadBalancer}
+
+        @return:  Member object
+        @rtype:   L{Member}
+        """
+        # A balancer can have a node as a member, even if the node doesn't
+        # exist.  In this case, 'node' is simply a string to where the resource
+        # would be found if it was there.
+        if hasattr(node, 'name'):
+            member_id = node.name
+            member_ip = node.public_ips[0]
+        else:
+            member_id = node
+            member_ip = None
+
+        extra = {'node': node}
+        return Member(id=member_id, ip=member_ip, port=balancer.port,
+                      balancer=balancer, extra=extra)
+
+    def _forwarding_rule_to_loadbalancer(self, forwarding_rule):
+        """
+        Return a Load Balancer object based on a GCEForwardingRule object.
+
+        @param  forwarding_rule: ForwardingRule object
+        @type   forwarding_rule: L{GCEForwardingRule}
+
+        @return:  LoadBalancer object
+        @rtype:   L{LoadBalancer}
+        """
+        extra = {}
+        extra['forwarding_rule'] = forwarding_rule
+        extra['targetpool'] = forwarding_rule.targetpool
+        extra['healthchecks'] = forwarding_rule.targetpool.healthchecks
+
+        return LoadBalancer(id=forwarding_rule.id,
+                            name=forwarding_rule.name, state=None,
+                            ip=forwarding_rule.address,
+                            port=forwarding_rule.extra['portRange'],
+                            driver=self, extra=extra)
diff --git a/libcloud/loadbalancer/providers.py b/libcloud/loadbalancer/providers.py
index 3ed140d..54ce72b 100644
--- a/libcloud/loadbalancer/providers.py
+++ b/libcloud/loadbalancer/providers.py
@@ -37,8 +37,9 @@ DRIVERS = {
         Provider.ELB:
             ('libcloud.loadbalancer.drivers.elb', 'ElasticLBDriver'),
         Provider.CLOUDSTACK:
-            ('libcloud.loadbalancer.drivers.cloudstack', 'CloudStackLBDriver')
-
+            ('libcloud.loadbalancer.drivers.cloudstack', 'CloudStackLBDriver'),
+        Provider.GCE:
+            ('libcloud.loadbalancer.drivers.gce', 'GCELBDriver')
 }
 
 
diff --git a/libcloud/loadbalancer/types.py b/libcloud/loadbalancer/types.py
index 6608ca4..1cac530 100644
--- a/libcloud/loadbalancer/types.py
+++ b/libcloud/loadbalancer/types.py
@@ -39,6 +39,7 @@ class Provider(object):
     BRIGHTBOX = 'brightbox'
     ELB = 'elb'
     CLOUDSTACK = 'cloudstack'
+    GCE = 'gce'
 
 
 class State(object):
diff --git a/libcloud/test/compute/fixtures/gce/aggregated_forwardingRules.json b/libcloud/test/compute/fixtures/gce/aggregated_forwardingRules.json
new file mode 100644
index 0000000..c6f648a
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/aggregated_forwardingRules.json
@@ -0,0 +1,57 @@
+{
+  "id": "projects/project_name/aggregated/forwardingRules",
+  "items": {
+    "regions/europe-west1": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "regions/europe-west1"
+          }
+        ],
+        "message": "There are no results for scope 'regions/europe-west1' on this page."
+      }
+    },
+    "regions/us-central1": {
+      "forwardingRules": [
+        {
+          "IPAddress": "173.255.119.224",
+          "IPProtocol": "TCP",
+          "creationTimestamp": "2013-09-03T00:17:25.544-07:00",
+          "id": "10901665092293158938",
+          "kind": "compute#forwardingRule",
+          "name": "lcforwardingrule",
+          "portRange": "8000-8500",
+          "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+          "target": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/libcloud-lb-demo-lb-tp"
+        },
+        {
+          "IPAddress": "173.255.119.185",
+          "IPProtocol": "TCP",
+          "creationTimestamp": "2013-09-02T22:25:50.575-07:00",
+          "id": "15826316229163619337",
+          "kind": "compute#forwardingRule",
+          "name": "libcloud-lb-demo-lb",
+          "portRange": "80-80",
+          "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+          "target": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/libcloud-lb-demo-lb-tp"
+        }
+      ]
+    },
+    "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#forwardingRuleAggregatedList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/aggregated/forwardingRules"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/aggregated_targetPools.json b/libcloud/test/compute/fixtures/gce/aggregated_targetPools.json
new file mode 100644
index 0000000..f57aad5
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/aggregated_targetPools.json
@@ -0,0 +1,64 @@
+{
+  "id": "projects/project_name/aggregated/targetPools",
+  "items": {
+    "regions/europe-west1": {
+      "warning": {
+        "code": "NO_RESULTS_ON_PAGE",
+        "data": [
+          {
+            "key": "scope",
+            "value": "regions/europe-west1"
+          }
+        ],
+        "message": "There are no results for scope 'regions/europe-west1' on this page."
+      }
+    },
+    "regions/us-central1": {
+      "targetPools": [
+        {
+          "creationTimestamp": "2013-09-03T00:51:05.300-07:00",
+          "healthChecks": [
+            "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/libcloud-lb-demo-healthcheck"
+          ],
+          "id": "11690726329826021866",
+          "instances": [
+            "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-000",
+            "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-001"
+          ],
+          "kind": "compute#targetPool",
+          "name": "lctargetpool",
+          "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1"
+        },
+        {
+          "creationTimestamp": "2013-09-02T22:25:45.817-07:00",
+          "healthChecks": [
+            "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/libcloud-lb-demo-healthcheck"
+          ],
+          "id": "16605251746746338499",
+          "instances": [
+            "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-002",
+            "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-001",
+            "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-000"
+          ],
+          "kind": "compute#targetPool",
+          "name": "libcloud-lb-demo-lb-tp",
+          "region": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1"
+        }
+      ]
+    },
+    "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#targetPoolAggregatedList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/aggregated/targetPools"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_httpHealthChecks.json b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks.json
new file mode 100644
index 0000000..051fded
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks.json
@@ -0,0 +1,35 @@
+{
+  "id": "projects/project_name/global/httpHealthChecks",
+  "items": [
+    {
+      "checkIntervalSec": 5,
+      "creationTimestamp": "2013-08-19T14:42:28.947-07:00",
+      "description": "",
+      "healthyThreshold": 2,
+      "host": "",
+      "id": "7660832580304455442",
+      "kind": "compute#httpHealthCheck",
+      "name": "basic-check",
+      "port": 80,
+      "requestPath": "/",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/basic-check",
+      "timeoutSec": 5,
+      "unhealthyThreshold": 2
+    },
+    {
+      "checkIntervalSec": 5,
+      "creationTimestamp": "2013-09-02T22:25:44.759-07:00",
+      "healthyThreshold": 2,
+      "id": "16372093408499501663",
+      "kind": "compute#httpHealthCheck",
+      "name": "libcloud-lb-demo-healthcheck",
+      "port": 80,
+      "requestPath": "/",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/libcloud-lb-demo-healthcheck",
+      "timeoutSec": 5,
+      "unhealthyThreshold": 2
+    }
+  ],
+  "kind": "compute#httpHealthCheckList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_basic-check.json b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_basic-check.json
new file mode 100644
index 0000000..0812ecd
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_basic-check.json
@@ -0,0 +1,15 @@
+{
+  "checkIntervalSec": 5,
+  "creationTimestamp": "2013-08-19T14:42:28.947-07:00",
+  "description": "",
+  "healthyThreshold": 2,
+  "host": "",
+  "id": "7660832580304455442",
+  "kind": "compute#httpHealthCheck",
+  "name": "basic-check",
+  "port": 80,
+  "requestPath": "/",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/basic-check",
+  "timeoutSec": 5,
+  "unhealthyThreshold": 2
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck.json b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck.json
new file mode 100644
index 0000000..76bb117
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck.json
@@ -0,0 +1,14 @@
+{
+  "checkIntervalSec": 10,
+  "creationTimestamp": "2013-09-02T22:18:01.180-07:00",
+  "healthyThreshold": 3,
+  "host": "lchost",
+  "id": "06860603312991823381",
+  "kind": "compute#httpHealthCheck",
+  "name": "lchealthcheck",
+  "port": 8000,
+  "requestPath": "/lc",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/lchealthcheck",
+  "timeoutSec": 10,
+  "unhealthyThreshold": 4
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck_delete.json b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck_delete.json
new file mode 100644
index 0000000..03f97e4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck_delete.json
@@ -0,0 +1,14 @@
+{
+  "id": "1159296103027566387",
+  "insertTime": "2013-09-02T22:18:02.509-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_httpHealthChecks_lchealthcheck_delete",
+  "operationType": "delete",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_httpHealthChecks_lchealthcheck_delete",
+  "startTime": "2013-09-02T22:18:02.558-07:00",
+  "status": "PENDING",
+  "targetId": "06860603312991823381",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/healthChecks/lchealthcheck",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck_put.json b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck_put.json
new file mode 100644
index 0000000..1daa897
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_lchealthcheck_put.json
@@ -0,0 +1,14 @@
+{
+  "id": "6717642434182216609",
+  "insertTime": "2013-09-03T02:19:55.574-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_httpHealthChecks_lchealthcheck_put",
+  "operationType": "update",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_httpHealthChecks_lchealthcheck_put",
+  "startTime": "2013-09-03T02:19:55.628-07:00",
+  "status": "PENDING",
+  "targetId": "0742691415598204878",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/healthChecks/lchealthcheck",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_libcloud-lb-demo-healthcheck.json b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_libcloud-lb-demo-healthcheck.json
new file mode 100644
index 0000000..6e772aa
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_libcloud-lb-demo-healthcheck.json
@@ -0,0 +1,13 @@
+{
+  "checkIntervalSec": 5,
+  "creationTimestamp": "2013-09-02T22:25:44.759-07:00",
+  "healthyThreshold": 2,
+  "id": "16372093408499501663",
+  "kind": "compute#httpHealthCheck",
+  "name": "libcloud-lb-demo-healthcheck",
+  "port": 80,
+  "requestPath": "/",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/libcloud-lb-demo-healthcheck",
+  "timeoutSec": 5,
+  "unhealthyThreshold": 2
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_post.json b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_post.json
new file mode 100644
index 0000000..f2f7873
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_httpHealthChecks_post.json
@@ -0,0 +1,13 @@
+{
+  "id": "3903393118268087410",
+  "insertTime": "2013-09-03T02:19:54.629-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_httpHealthChecks_post",
+  "operationType": "insert",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_httpHealthChecks_post",
+  "startTime": "2013-09-03T02:19:54.718-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/healthChecks/lchealthcheck",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_lchealthcheck_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_lchealthcheck_delete.json
new file mode 100644
index 0000000..17cef7b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_lchealthcheck_delete.json
@@ -0,0 +1,14 @@
+{
+  "id": "1159296103027566387",
+  "insertTime": "2013-09-02T22:18:02.509-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_httpHealthChecks_lchealthcheck_delete",
+  "operationType": "delete",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_httpHealthChecks_lchealthcheck_delete",
+  "startTime": "2013-09-02T22:18:02.558-07:00",
+  "status": "DONE",
+  "targetId": "06860603312991823381",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/healthChecks/lchealthcheck",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_lchealthcheck_put.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_lchealthcheck_put.json
new file mode 100644
index 0000000..b69e548
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_lchealthcheck_put.json
@@ -0,0 +1,15 @@
+{
+  "endTime": "2013-09-03T02:20:02.194-07:00",
+  "id": "6717642434182216609",
+  "insertTime": "2013-09-03T02:19:55.574-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_httpHealthChecks_lchealthcheck_put",
+  "operationType": "update",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_httpHealthChecks_lchealthcheck_put",
+  "startTime": "2013-09-03T02:19:55.628-07:00",
+  "status": "DONE",
+  "targetId": "0742691415598204878",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/healthChecks/lchealthcheck",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_post.json
new file mode 100644
index 0000000..534688e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_httpHealthChecks_post.json
@@ -0,0 +1,14 @@
+{
+  "id": "3903393118268087410",
+  "insertTime": "2013-09-03T02:19:54.629-07:00",
+  "kind": "compute#operation",
+  "name": "operation-global_httpHealthChecks_post",
+  "operationType": "insert",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/operations/operation-global_httpHealthChecks_post",
+  "startTime": "2013-09-03T02:19:54.718-07:00",
+  "status": "DONE",
+  "targetId": "0742691415598204878",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/healthChecks/lchealthcheck",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_forwardingRules_lcforwardingrule_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_forwardingRules_lcforwardingrule_delete.json
new file mode 100644
index 0000000..974c636
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_forwardingRules_lcforwardingrule_delete.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-09-03T00:17:43.917-07:00",
+  "id": "09064254309855814339",
+  "insertTime": "2013-09-03T00:17:36.062-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_forwardingRules_lcforwardingrule_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_forwardingRules_lcforwardingrule_delete",
+  "startTime": "2013-09-03T00:17:36.168-07:00",
+  "status": "DONE",
+  "targetId": "10901665092293158938",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/forwardingRules/lcforwardingrule",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_forwardingRules_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_forwardingRules_post.json
new file mode 100644
index 0000000..4c83db1
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_forwardingRules_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-09-03T00:17:33.965-07:00",
+  "id": "0651769405845333112",
+  "insertTime": "2013-09-03T00:17:25.381-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_forwardingRules_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_forwardingRules_post",
+  "startTime": "2013-09-03T00:17:25.434-07:00",
+  "status": "DONE",
+  "targetId": "10901665092293158938",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/forwardingRules/lcforwardingrule",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json
new file mode 100644
index 0000000..8f4a405
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-09-03T01:28:49.271-07:00",
+  "id": "17341029456963557514",
+  "insertTime": "2013-09-03T01:28:40.774-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_addHealthCheck_post",
+  "operationType": "addHealthCheck",
+  "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_targetPools_lctargetpool_addHealthCheck_post",
+  "startTime": "2013-09-03T01:28:40.838-07:00",
+  "status": "DONE",
+  "targetId": "16862638289615591831",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_addInstance_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_addInstance_post.json
new file mode 100644
index 0000000..ecac372
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_addInstance_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-09-03T01:29:07.021-07:00",
+  "id": "04072826501537092633",
+  "insertTime": "2013-09-03T01:29:03.082-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_addInstance_post",
+  "operationType": "addInstance",
+  "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_targetPools_lctargetpool_addInstance_post",
+  "startTime": "2013-09-03T01:29:03.145-07:00",
+  "status": "DONE",
+  "targetId": "16862638289615591831",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_delete.json
new file mode 100644
index 0000000..a790311
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "13500662190763995965",
+  "insertTime": "2013-09-03T00:51:06.799-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_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_targetPools_lctargetpool_delete",
+  "startTime": "2013-09-03T00:51:06.840-07:00",
+  "status": "DONE",
+  "targetId": "13598380121688918358",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json
new file mode 100644
index 0000000..6c4eeb5
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-09-03T01:28:37.095-07:00",
+  "id": "14738174613993796821",
+  "insertTime": "2013-09-03T01:28:32.889-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post",
+  "operationType": "removeHealthCheck",
+  "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_targetPools_lctargetpool_removeHealthCheck_post",
+  "startTime": "2013-09-03T01:28:32.942-07:00",
+  "status": "DONE",
+  "targetId": "16862638289615591831",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_removeInstance_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_removeInstance_post.json
new file mode 100644
index 0000000..cb24504
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_lctargetpool_removeInstance_post.json
@@ -0,0 +1,16 @@
+{
+  "endTime": "2013-09-03T01:28:59.247-07:00",
+  "id": "1815686149437875016",
+  "insertTime": "2013-09-03T01:28:53.049-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_removeInstance_post",
+  "operationType": "removeInstance",
+  "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_targetPools_lctargetpool_removeInstance_post",
+  "startTime": "2013-09-03T01:28:53.109-07:00",
+  "status": "DONE",
+  "targetId": "16862638289615591831",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_post.json
new file mode 100644
index 0000000..0e4b666
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_targetPools_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "7487852523793007955",
+  "insertTime": "2013-09-03T00:51:05.064-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_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_targetPools_post",
+  "startTime": "2013-09-03T00:51:05.115-07:00",
+  "status": "DONE",
+  "targetId": "13598380121688918358",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions.json b/libcloud/test/compute/fixtures/gce/regions.json
new file mode 100644
index 0000000..edf3a37
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions.json
@@ -0,0 +1,45 @@
+{
+  "id": "projects/project_name/regions",
+  "items": [
+    {
+      "creationTimestamp": "2013-04-19T17:58:16.641-07:00",
+      "description": "europe-west1",
+      "id": "0827308347805275727",
+      "kind": "compute#region",
+      "name": "europe-west1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/europe-west1",
+      "status": "UP",
+      "zones": [
+        "https://www.googleapis.com/compute/v1beta15/zones/europe-west1-a",
+        "https://www.googleapis.com/compute/v1beta15/zones/europe-west1-b"
+      ]
+    },
+    {
+      "creationTimestamp": "2013-04-19T18:17:05.050-07:00",
+      "description": "us-central1",
+      "id": "06713580496607310378",
+      "kind": "compute#region",
+      "name": "us-central1",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1",
+      "status": "UP",
+      "zones": [
+        "https://www.googleapis.com/compute/v1beta15/zones/us-central1-a",
+        "https://www.googleapis.com/compute/v1beta15/zones/us-central1-b"
+      ]
+    },
+    {
+      "creationTimestamp": "2013-04-19T18:19:05.482-07:00",
+      "description": "us-central2",
+      "id": "04157375529195793136",
+      "kind": "compute#region",
+      "name": "us-central2",
+      "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central2",
+      "status": "UP",
+      "zones": [
+        "https://www.googleapis.com/compute/v1beta15/zones/us-central2-a"
+      ]
+    }
+  ],
+  "kind": "compute#regionList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules.json
new file mode 100644
index 0000000..fbc1b0e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules.json
@@ -0,0 +1,31 @@
+{
+  "id": "projects/project_name/regions/us-central1/forwardingRules",
+  "items": [
+    {
+      "IPAddress": "173.255.119.224",
+      "IPProtocol": "TCP",
+      "creationTimestamp": "2013-09-03T00:17:25.544-07:00",
+      "id": "10901665092293158938",
+      "kind": "compute#forwardingRule",
+      "name": "lcforwardingrule",
+      "portRange": "8000-8500",
+      "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/forwardingRules/lcforwardingrule",
+      "target": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/libcloud-lb-demo-lb-tp"
+    },
+    {
+      "IPAddress": "173.255.119.185",
+      "IPProtocol": "TCP",
+      "creationTimestamp": "2013-09-02T22:25:50.575-07:00",
+      "id": "15826316229163619337",
+      "kind": "compute#forwardingRule",
+      "name": "libcloud-lb-demo-lb",
+      "portRange": "80-80",
+      "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/forwardingRules/libcloud-lb-demo-lb",
+      "target": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/libcloud-lb-demo-lb-tp"
+    }
+  ],
+  "kind": "compute#forwardingRuleList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/forwardingRules"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_lcforwardingrule.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_lcforwardingrule.json
new file mode 100644
index 0000000..d4c9051
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_lcforwardingrule.json
@@ -0,0 +1,12 @@
+{
+  "IPAddress": "173.255.119.224",
+  "IPProtocol": "TCP",
+  "creationTimestamp": "2013-09-03T00:17:25.544-07:00",
+  "id": "10901665092293158938",
+  "kind": "compute#forwardingRule",
+  "name": "lcforwardingrule",
+  "portRange": "8000-8500",
+  "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/forwardingRules/lcforwardingrule",
+  "target": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool"
+}
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_lcforwardingrule_delete.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_lcforwardingrule_delete.json
new file mode 100644
index 0000000..1c15638
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_lcforwardingrule_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "09064254309855814339",
+  "insertTime": "2013-09-03T00:17:36.062-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_forwardingRules_lcforwardingrule_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_forwardingRules_lcforwardingrule_delete",
+  "startTime": "2013-09-03T00:17:36.168-07:00",
+  "status": "PENDING",
+  "targetId": "10901665092293158938",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/forwardingRules/lcforwardingrule",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_libcloud-lb-demo-lb.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_libcloud-lb-demo-lb.json
new file mode 100644
index 0000000..99a310e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_libcloud-lb-demo-lb.json
@@ -0,0 +1,12 @@
+{
+  "IPAddress": "108.59.83.110",
+  "IPProtocol": "TCP",
+  "creationTimestamp": "2013-09-29T13:30:00.702-07:00",
+  "id": "1077550228014866104",
+  "kind": "compute#forwardingRule",
+  "name": "libcloud-lb-demo-lb",
+  "portRange": "80-80",
+  "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/forwardingRules/libcloud-lb-demo-lb",
+  "target": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/libcloud-lb-demo-lb-tp"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_post.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_post.json
new file mode 100644
index 0000000..1b08b75
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_forwardingRules_post.json
@@ -0,0 +1,14 @@
+{
+  "id": "0651769405845333112",
+  "insertTime": "2013-09-03T00:17:25.381-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_forwardingRules_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_forwardingRules_post",
+  "startTime": "2013-09-03T00:17:25.434-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/forwardingRules/lcforwardingrule",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools.json
new file mode 100644
index 0000000..331065b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools.json
@@ -0,0 +1,38 @@
+{
+  "id": "projects/project_name/regions/us-central1/targetPools",
+  "items": [
+    {
+      "creationTimestamp": "2013-09-03T00:51:05.300-07:00",
+      "healthChecks": [
+        "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/libcloud-lb-demo-healthcheck"
+      ],
+      "id": "13598380121688918358",
+      "instances": [
+        "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-000",
+        "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-001"
+      ],
+      "kind": "compute#targetPool",
+      "name": "lctargetpool",
+      "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/targetPools/lctargetpool"
+    },
+    {
+      "creationTimestamp": "2013-09-02T22:25:45.817-07:00",
+      "healthChecks": [
+        "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/libcloud-lb-demo-healthcheck"
+      ],
+      "id": "16862638289615591831",
+      "instances": [
+        "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-002",
+        "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-001",
+        "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-000"
+      ],
+      "kind": "compute#targetPool",
+      "name": "libcloud-lb-demo-lb-tp",
+      "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/targetPools/libcloud-lb-demo-lb-tp"
+    }
+  ],
+  "kind": "compute#targetPoolList",
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool.json
new file mode 100644
index 0000000..81828f8
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool.json
@@ -0,0 +1,15 @@
+{
+  "creationTimestamp": "2013-09-03T00:51:05.300-07:00",
+  "healthChecks": [
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/libcloud-lb-demo-healthcheck"
+  ],
+  "id": "13598380121688918358",
+  "instances": [
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-000",
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-001"
+  ],
+  "kind": "compute#targetPool",
+  "name": "lctargetpool",
+  "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/targetPools/lctargetpool"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json
new file mode 100644
index 0000000..7743b89
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "17341029456963557514",
+  "insertTime": "2013-09-03T01:28:40.774-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_addHealthCheck_post",
+  "operationType": "addHealthCheck",
+  "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_targetPools_lctargetpool_addHealthCheck_post",
+  "startTime": "2013-09-03T01:28:40.838-07:00",
+  "status": "PENDING",
+  "targetId": "16862638289615591831",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_addInstance_post.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_addInstance_post.json
new file mode 100644
index 0000000..1af9354
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_addInstance_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "04072826501537092633",
+  "insertTime": "2013-09-03T01:29:03.082-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_addInstance_post",
+  "operationType": "addInstance",
+  "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_targetPools_lctargetpool_addInstance_post",
+  "startTime": "2013-09-03T01:29:03.145-07:00",
+  "status": "PENDING",
+  "targetId": "16862638289615591831",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_delete.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_delete.json
new file mode 100644
index 0000000..196a1e0
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_delete.json
@@ -0,0 +1,15 @@
+{
+  "id": "13500662190763995965",
+  "insertTime": "2013-09-03T00:51:06.799-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_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_targetPools_lctargetpool_delete",
+  "startTime": "2013-09-03T00:51:06.840-07:00",
+  "status": "PENDING",
+  "targetId": "13598380121688918358",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json
new file mode 100644
index 0000000..be21bbe
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "14738174613993796821",
+  "insertTime": "2013-09-03T01:28:32.889-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post",
+  "operationType": "removeHealthCheck",
+  "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_targetPools_lctargetpool_removeHealthCheck_post",
+  "startTime": "2013-09-03T01:28:32.942-07:00",
+  "status": "PENDING",
+  "targetId": "16862638289615591831",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_removeInstance_post.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_removeInstance_post.json
new file mode 100644
index 0000000..435e2d2
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_lctargetpool_removeInstance_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "1815686149437875016",
+  "insertTime": "2013-09-03T01:28:53.049-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_lctargetpool_removeInstance_post",
+  "operationType": "removeInstance",
+  "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_targetPools_lctargetpool_removeInstance_post",
+  "startTime": "2013-09-03T01:28:53.109-07:00",
+  "status": "PENDING",
+  "targetId": "16862638289615591831",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_libcloud-lb-demo-lb-tp.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_libcloud-lb-demo-lb-tp.json
new file mode 100644
index 0000000..47179b7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_libcloud-lb-demo-lb-tp.json
@@ -0,0 +1,16 @@
+{
+  "creationTimestamp": "2013-09-02T22:25:45.817-07:00",
+  "healthChecks": [
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/libcloud-lb-demo-healthcheck"
+  ],
+  "id": "16862638289615591831",
+  "instances": [
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-002",
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-001",
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-000"
+  ],
+  "kind": "compute#targetPool",
+  "name": "libcloud-lb-demo-lb-tp",
+  "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/targetPools/libcloud-lb-demo-lb-tp"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_post.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_post.json
new file mode 100644
index 0000000..32f57cc
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_post.json
@@ -0,0 +1,14 @@
+{
+  "id": "7487852523793007955",
+  "insertTime": "2013-09-03T00:51:05.064-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_targetPools_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_targetPools_post",
+  "startTime": "2013-09-03T00:51:05.115-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/regions/us-central1/targetPools/lctargetpool",
+  "user": "user@gserviceaccount.com"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_www-pool.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_www-pool.json
new file mode 100644
index 0000000..797bf39
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_targetPools_www-pool.json
@@ -0,0 +1,17 @@
+{
+  "creationTimestamp": "2013-08-19T14:43:25.289-07:00",
+  "description": "",
+  "healthChecks": [
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/httpHealthChecks/basic-check"
+  ],
+  "id": "09965129111508633746",
+  "instances": [
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/www1",
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/www2",
+    "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/www3"
+  ],
+  "kind": "compute#targetPool",
+  "name": "www-pool",
+  "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/targetPools/www-pool"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-000.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-000.json
new file mode 100644
index 0000000..6a3e984
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-000.json
@@ -0,0 +1,52 @@
+{
+  "canIpForward": false,
+  "creationTimestamp": "2013-09-02T22:25:15.878-07:00",
+  "disks": [
+    {
+      "index": 0,
+      "kind": "compute#attachedDisk",
+      "mode": "READ_WRITE",
+      "type": "SCRATCH"
+    }
+  ],
+  "id": "16051209904934263688",
+  "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130723",
+  "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-b/machineTypes/n1-standard-1",
+  "metadata": {
+    "fingerprint": "n9EGknQZPSA=",
+    "items": [
+      {
+        "key": "startup-script",
+        "value": "apt-get -y update && apt-get -y install apache2 && hostname > /var/www/index.html"
+      }
+    ],
+    "kind": "compute#metadata"
+  },
+  "name": "libcloud-lb-demo-www-000",
+  "networkInterfaces": [
+    {
+      "accessConfigs": [
+        {
+          "kind": "compute#accessConfig",
+          "name": "External NAT",
+          "natIP": "173.255.113.234",
+          "type": "ONE_TO_ONE_NAT"
+        }
+      ],
+      "name": "nic0",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+      "networkIP": "10.240.138.154"
+    }
+  ],
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-000",
+  "status": "RUNNING",
+  "tags": {
+    "fingerprint": "XI0he92M8l8=",
+    "items": [
+      "libcloud-lb-demo-www"
+    ]
+  },
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-001.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-001.json
new file mode 100644
index 0000000..29d4b4b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-001.json
@@ -0,0 +1,52 @@
+{
+  "canIpForward": false,
+  "creationTimestamp": "2013-09-02T22:25:16.253-07:00",
+  "disks": [
+    {
+      "index": 0,
+      "kind": "compute#attachedDisk",
+      "mode": "READ_WRITE",
+      "type": "SCRATCH"
+    }
+  ],
+  "id": "11204787063927654129",
+  "image": "https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130723",
+  "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-b/machineTypes/n1-standard-1",
+  "metadata": {
+    "fingerprint": "n9EGknQZPSA=",
+    "items": [
+      {
+        "key": "startup-script",
+        "value": "apt-get -y update && apt-get -y install apache2 && hostname > /var/www/index.html"
+      }
+    ],
+    "kind": "compute#metadata"
+  },
+  "name": "libcloud-lb-demo-www-001",
+  "networkInterfaces": [
+    {
+      "accessConfigs": [
+        {
+          "kind": "compute#accessConfig",
+          "name": "External NAT",
+          "natIP": "173.255.114.210",
+          "type": "ONE_TO_ONE_NAT"
+        }
+      ],
+      "name": "nic0",
+      "network": "https://www.googleapis.com/compute/v1beta15/projects/project_name/global/networks/default",
+      "networkIP": "10.240.0.123"
+    }
+  ],
+  "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b/instances/libcloud-lb-demo-www-001",
+  "status": "RUNNING",
+  "tags": {
+    "fingerprint": "XI0he92M8l8=",
+    "items": [
+      "libcloud-lb-demo-www"
+    ]
+  },
+  "zone": "https://www.googleapis.com/compute/v1beta15/projects/project_name/zones/us-central1-b"
+}
\ No newline at end of file
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-002.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-002.json
new file mode 100644
index 0000000..4800d57
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-002.json
@@ -0,0 +1,13 @@
+{
+  "error": {
+    "code": 404,
+    "errors": [
+      {
+        "domain": "global",
+        "message": "The resource 'projects/project-name/zones/us-central1-b/instances/libcloud-lb-demo-www-002' was not found",
+        "reason": "notFound"
+      }
+    ],
+    "message": "The resource 'projects/project-name/zones/us-central1-b/instances/libcloud-lb-demo-www-002' was not found"
+  }
+}
diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py
index 9146d6a..747faef 100644
--- a/libcloud/test/compute/test_gce.py
+++ b/libcloud/test/compute/test_gce.py
@@ -22,13 +22,15 @@ 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)
+                                          GCEAddress, GCEHealthCheck,
+                                          GCEFirewall, GCEForwardingRule,
+                                          GCENetwork, GCENodeSize, GCEProject,
+                                          GCERegion, GCETargetPool, GCEZone)
 from libcloud.common.google import (GoogleBaseAuthConnection,
                                     GoogleInstalledAppAuthConnection,
-                                    GoogleBaseConnection)
+                                    GoogleBaseConnection,
+                                    ResourceNotFoundError, ResourceExistsError,
+                                    QuotaExceededError)
 from libcloud.test.common.test_google import GoogleAuthMockHttp
 from libcloud.compute.base import (Node, NodeImage, NodeSize, NodeLocation,
                                    StorageVolume)
@@ -71,15 +73,26 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         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_get_region_from_zone(self):
+        zone1 = self.driver.ex_get_zone('us-central1-a')
+        expected_region1 = 'us-central1'
+        region1 = self.driver._get_region_from_zone(zone1)
+        self.assertEqual(region1.name, expected_region1)
+        zone2 = self.driver.ex_get_zone('europe-west1-b')
+        expected_region2 = 'europe-west1'
+        region2 = self.driver._get_region_from_zone(zone2)
+        self.assertEqual(region2.name, expected_region2)
+
+    def test_find_zone_or_region(self):
+        zone1 = self.driver._find_zone_or_region('libcloud-demo-np-node',
+                                                 'instances')
+        self.assertEqual(zone1.name, 'us-central1-a')
+        zone2 = self.driver._find_zone_or_region(
+            'libcloud-demo-europe-np-node', 'instances')
+        self.assertEqual(zone2.name, 'europe-west1-a')
+        region = self.driver._find_zone_or_region('libcloud-demo-address',
+                                                  'addresses', region=True)
+        self.assertEqual(region.name, 'us-central1')
 
     def test_match_images(self):
         project = 'debian-cloud'
@@ -96,13 +109,31 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         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')
+        names = [a.name for a in address_list_all]
+        self.assertTrue('libcloud-demo-address' in names)
+
+    def test_ex_list_healthchecks(self):
+        healthchecks = self.driver.ex_list_healthchecks()
+        self.assertEqual(len(healthchecks), 2)
+        self.assertEqual(healthchecks[0].name, 'basic-check')
 
     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_ex_list_forwarding_rules(self):
+        forwarding_rules = self.driver.ex_list_forwarding_rules()
+        forwarding_rules_all = self.driver.ex_list_forwarding_rules('all')
+        forwarding_rules_uc1 = self.driver.ex_list_forwarding_rules(
+            'us-central1')
+        self.assertEqual(len(forwarding_rules), 2)
+        self.assertEqual(len(forwarding_rules_all), 2)
+        self.assertEqual(forwarding_rules[0].name, 'lcforwardingrule')
+        self.assertEqual(forwarding_rules_uc1[0].name, 'lcforwardingrule')
+        names = [f.name for f in forwarding_rules_all]
+        self.assertTrue('lcforwardingrule' in names)
+
     def test_list_images(self):
         local_images = self.driver.list_images()
         debian_images = self.driver.list_images(ex_project='debian-cloud')
@@ -129,7 +160,25 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         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')
+        names = [n.name for n in nodes_all]
+        self.assertTrue('node-name' in names)
+
+    def test_ex_list_regions(self):
+        regions = self.driver.ex_list_regions()
+        self.assertEqual(len(regions), 3)
+        self.assertEqual(regions[0].name, 'europe-west1')
+
+    def ex_list_targetpools(self):
+        target_pools = self.driver.ex_list_targetpools()
+        target_pools_all = self.driver.ex_list_targetpools('all')
+        target_pools_uc1 = self.driver.ex_list_targetpools('us-central1')
+        self.assertEqual(len(target_pools), 3)
+        self.assertEqual(len(target_pools_all), 4)
+        self.assertEqual(len(target_pools_uc1), 3)
+        self.assertEqual(target_pools[0].name, 'www-pool')
+        self.assertEqual(target_pools_uc1[0].name, 'www-pool')
+        names = [t.name for t in target_pools_all]
+        self.assertTrue('www-pool' in names)
 
     def test_list_sizes(self):
         sizes = self.driver.list_sizes()
@@ -138,8 +187,8 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         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')
+        names = [s.name for s in sizes_all]
+        self.assertEqual(names.count('n1-standard-1'), 5)
 
     def test_list_volumes(self):
         volumes = self.driver.list_volumes()
@@ -149,8 +198,9 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         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')
+        names = [v.name for v in volumes_all]
+        self.assertTrue('test-disk' in names)
 
     def test_ex_list_zones(self):
         zones = self.driver.ex_list_zones()
@@ -163,6 +213,22 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertTrue(isinstance(address, GCEAddress))
         self.assertEqual(address.name, address_name)
 
+    def test_ex_create_healthcheck(self):
+        healthcheck_name = 'lchealthcheck'
+        kwargs = {'host': 'lchost',
+                  'path': '/lc',
+                  'port': 8000,
+                  'interval': 10,
+                  'timeout': 10,
+                  'unhealthy_threshold': 4,
+                  'healthy_threshold': 3}
+        hc = self.driver.ex_create_healthcheck(healthcheck_name, **kwargs)
+        self.assertTrue(isinstance(hc, GCEHealthCheck))
+        self.assertEqual(hc.name, healthcheck_name)
+        self.assertEqual(hc.path, '/lc')
+        self.assertEqual(hc.port, 8000)
+        self.assertEqual(hc.interval, 10)
+
     def test_ex_create_firewall(self):
         firewall_name = 'lcfirewall'
         allowed = [{'IPProtocol': 'tcp', 'ports': ['4567']}]
@@ -172,6 +238,16 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertTrue(isinstance(firewall, GCEFirewall))
         self.assertEqual(firewall.name, firewall_name)
 
+    def test_ex_create_forwarding_rule(self):
+        fwr_name = 'lcforwardingrule'
+        targetpool = 'lctargetpool'
+        region = 'us-central1'
+        fwr = self.driver.ex_create_forwarding_rule(fwr_name, targetpool,
+                                                    region=region,
+                                                    port_range='8000-8500')
+        self.assertTrue(isinstance(fwr, GCEForwardingRule))
+        self.assertEqual(fwr.name, fwr_name)
+
     def test_ex_create_network(self):
         network_name = 'lcnetwork'
         cidr = '10.11.0.0/16'
@@ -227,6 +303,22 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(nodes[0].name, '%s-000' % base_name)
         self.assertEqual(nodes[1].name, '%s-001' % base_name)
 
+    def test_ex_create_targetpool(self):
+        targetpool_name = 'lctargetpool'
+        region = 'us-central1'
+        healthchecks = ['libcloud-lb-demo-healthcheck']
+        node1 = self.driver.ex_get_node('libcloud-lb-demo-www-000',
+                                        'us-central1-b')
+        node2 = self.driver.ex_get_node('libcloud-lb-demo-www-001',
+                                        'us-central1-b')
+        nodes = [node1, node2]
+        targetpool = self.driver.ex_create_targetpool(
+            targetpool_name, region=region, healthchecks=healthchecks,
+            nodes=nodes)
+        self.assertEqual(targetpool.name, targetpool_name)
+        self.assertEqual(len(targetpool.nodes), len(nodes))
+        self.assertEqual(targetpool.region.name, region)
+
     def test_create_volume(self):
         volume_name = 'lcdisk'
         size = 1
@@ -234,6 +326,13 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertTrue(isinstance(volume, StorageVolume))
         self.assertEqual(volume.name, volume_name)
 
+    def test_ex_update_healthcheck(self):
+        healthcheck_name = 'lchealthcheck'
+        healthcheck = self.driver.ex_get_healthcheck(healthcheck_name)
+        healthcheck.port = 9000
+        healthcheck2 = self.driver.ex_update_healthcheck(healthcheck)
+        self.assertTrue(isinstance(healthcheck2, GCEHealthCheck))
+
     def test_ex_update_firewall(self):
         firewall_name = 'lcfirewall'
         firewall = self.driver.ex_get_firewall(firewall_name)
@@ -242,6 +341,32 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         firewall2 = self.driver.ex_update_firewall(firewall)
         self.assertTrue(isinstance(firewall2, GCEFirewall))
 
+    def test_ex_targetpool_remove_add_node(self):
+        targetpool = self.driver.ex_get_targetpool('lctargetpool')
+        node = self.driver.ex_get_node('libcloud-lb-demo-www-001',
+                                       'us-central1-b')
+        remove_node = self.driver.ex_targetpool_remove_node(targetpool, node)
+        self.assertTrue(remove_node)
+        self.assertEqual(len(targetpool.nodes), 1)
+
+        add_node = self.driver.ex_targetpool_add_node(targetpool, node)
+        self.assertTrue(add_node)
+        self.assertEqual(len(targetpool.nodes), 2)
+
+    def test_ex_targetpool_remove_add_healthcheck(self):
+        targetpool = self.driver.ex_get_targetpool('lctargetpool')
+        healthcheck = self.driver.ex_get_healthcheck(
+            'libcloud-lb-demo-healthcheck')
+        remove_healthcheck = self.driver.ex_targetpool_remove_healthcheck(
+            targetpool, healthcheck)
+        self.assertTrue(remove_healthcheck)
+        self.assertEqual(len(targetpool.healthchecks), 0)
+
+        add_healthcheck = self.driver.ex_targetpool_add_healthcheck(
+            targetpool, healthcheck)
+        self.assertTrue(add_healthcheck)
+        self.assertEqual(len(targetpool.healthchecks), 1)
+
     def test_reboot_node(self):
         node = self.driver.ex_get_node('node-name')
         reboot = self.driver.reboot_node(node)
@@ -274,11 +399,21 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         destroyed = address.destroy()
         self.assertTrue(destroyed)
 
+    def test_ex_destroy_healthcheck(self):
+        hc = self.driver.ex_get_healthcheck('lchealthcheck')
+        destroyed = hc.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_forwarding_rule(self):
+        fwr = self.driver.ex_get_forwarding_rule('lcforwardingrule')
+        destroyed = fwr.destroy()
+        self.assertTrue(destroyed)
+
     def test_ex_destroy_network(self):
         network = self.driver.ex_get_network('lcnetwork')
         destroyed = network.destroy()
@@ -297,9 +432,14 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         for d in destroyed:
             self.assertTrue(d)
 
+    def test_destroy_targetpool(self):
+        targetpool = self.driver.ex_get_targetpool('lctargetpool')
+        destroyed = targetpool.destroy()
+        self.assertTrue(destroyed)
+
     def test_destroy_volume(self):
-        address = self.driver.ex_get_address('lcaddress')
-        destroyed = address.destroy()
+        disk = self.driver.ex_get_volume('lcdisk')
+        destroyed = disk.destroy()
         self.assertTrue(destroyed)
 
     def test_ex_get_address(self):
@@ -307,9 +447,16 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         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.region.name, 'us-central1')
         self.assertEqual(address.extra['status'], 'RESERVED')
 
+    def test_ex_get_healthcheck(self):
+        healthcheck_name = 'lchealthcheck'
+        healthcheck = self.driver.ex_get_healthcheck(healthcheck_name)
+        self.assertEqual(healthcheck.name, healthcheck_name)
+        self.assertEqual(healthcheck.port, 8000)
+        self.assertEqual(healthcheck.path, '/lc')
+
     def test_ex_get_firewall(self):
         firewall_name = 'lcfirewall'
         firewall = self.driver.ex_get_firewall(firewall_name)
@@ -317,6 +464,14 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(firewall.network.name, 'default')
         self.assertEqual(firewall.source_tags, ['libcloud'])
 
+    def test_ex_get_forwarding_rule(self):
+        fwr_name = 'lcforwardingrule'
+        fwr = self.driver.ex_get_forwarding_rule(fwr_name)
+        self.assertEqual(fwr.name, fwr_name)
+        self.assertEqual(fwr.extra['portRange'], '8000-8500')
+        self.assertEqual(fwr.targetpool.name, 'lctargetpool')
+        self.assertEqual(fwr.protocol, 'TCP')
+
     def test_ex_get_image(self):
         partial_name = 'debian-7'
         image = self.driver.ex_get_image(partial_name)
@@ -336,6 +491,19 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(network.cidr, '10.11.0.0/16')
         self.assertEqual(network.extra['gatewayIPv4'], '10.11.0.1')
 
+    def test_ex_get_node(self):
+        node_name = 'node-name'
+        zone = 'us-central1-a'
+        node = self.driver.ex_get_node(node_name, zone)
+        self.assertEqual(node.name, node_name)
+        self.assertEqual(node.size, 'n1-standard-1')
+        removed_node = 'libcloud-lb-demo-www-002'
+        self.assertRaises(ResourceNotFoundError, self.driver.ex_get_node,
+                          removed_node, 'us-central1-b')
+        missing_node = 'dummy-node'
+        self.assertRaises(ResourceNotFoundError, self.driver.ex_get_node,
+                          missing_node, 'all')
+
     def test_ex_get_project(self):
         project = self.driver.ex_get_project()
         self.assertEqual(project.name, 'project_name')
@@ -343,6 +511,13 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(instances_quota['usage'], 7.0)
         self.assertEqual(instances_quota['limit'], 8.0)
 
+    def test_ex_get_region(self):
+        region_name = 'us-central1'
+        region = self.driver.ex_get_region(region_name)
+        self.assertEqual(region.name, region_name)
+        self.assertEqual(region.status, 'UP')
+        self.assertEqual(region.zones[0].name, 'us-central1-a')
+
     def test_ex_get_size(self):
         size_name = 'n1-standard-1'
         size = self.driver.ex_get_size(size_name)
@@ -352,6 +527,13 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(size.ram, 3840)
         self.assertEqual(size.extra['guestCpus'], 1)
 
+    def test_ex_get_targetpool(self):
+        targetpool_name = 'lctargetpool'
+        targetpool = self.driver.ex_get_targetpool(targetpool_name)
+        self.assertEqual(targetpool.name, targetpool_name)
+        self.assertEqual(len(targetpool.nodes), 2)
+        self.assertEqual(targetpool.region.name, 'us-central1')
+
     def test_ex_get_volume(self):
         volume_name = 'lcdisk'
         volume = self.driver.ex_get_volume(volume_name)
@@ -397,6 +579,10 @@ class GCEMockHttp(MockHttpTestCase):
         body = self.fixtures.load('aggregated_disks.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _aggregated_forwardingRules(self, method, url, body, headers):
+        body = self.fixtures.load('aggregated_forwardingRules.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])
@@ -405,6 +591,36 @@ class GCEMockHttp(MockHttpTestCase):
         body = self.fixtures.load('aggregated_machineTypes.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _global_httpHealthChecks(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load('global_httpHealthChecks_post.json')
+        else:
+            body = self.fixtures.load('global_httpHealthChecks.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_httpHealthChecks_basic_check(self, method, url, body, headers):
+        body = self.fixtures.load('global_httpHealthChecks_basic-check.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_httpHealthChecks_libcloud_lb_demo_healthcheck(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'global_httpHealthChecks_libcloud-lb-demo-healthcheck.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_httpHealthChecks_lchealthcheck(self, method, url, body,
+                                               headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'global_httpHealthChecks_lchealthcheck_delete.json')
+        elif method == 'PUT':
+            body = self.fixtures.load(
+                'global_httpHealthChecks_lchealthcheck_put.json')
+        else:
+            body = self.fixtures.load(
+                'global_httpHealthChecks_lchealthcheck.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')
@@ -455,6 +671,24 @@ class GCEMockHttp(MockHttpTestCase):
             body = self.fixtures.load('global_networks_lcnetwork.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _global_operations_operation_global_httpHealthChecks_lchealthcheck_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_httpHealthChecks_lchealthcheck_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_httpHealthChecks_lchealthcheck_put(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_httpHealthChecks_lchealthcheck_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_httpHealthChecks_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_httpHealthChecks_post.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(
@@ -497,6 +731,54 @@ class GCEMockHttp(MockHttpTestCase):
             'operations_operation_regions_us-central1_addresses_post.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _regions_us_central1_operations_operation_regions_us_central1_forwardingRules_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_forwardingRules_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_forwardingRules_lcforwardingrule_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_forwardingRules_lcforwardingrule_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_targetPools_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_targetPools_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_targetPools_lctargetpool_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_targetPools_lctargetpool_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_targetPools_lctargetpool_removeHealthCheck_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_targetPools_lctargetpool_addHealthCheck_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_targetPools_lctargetpool_removeInstance_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_targetPools_lctargetpool_removeInstance_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_operations_operation_regions_us_central1_targetPools_lctargetpool_addInstance_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_targetPools_lctargetpool_addInstance_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(
@@ -571,6 +853,11 @@ class GCEMockHttp(MockHttpTestCase):
         body = self.fixtures.load('projects_debian-cloud_global_images.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _regions(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'regions.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(
@@ -589,6 +876,79 @@ class GCEMockHttp(MockHttpTestCase):
                 'regions_us-central1_addresses_lcaddress.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _regions_us_central1_forwardingRules(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                'regions_us-central1_forwardingRules_post.json')
+        else:
+            body = self.fixtures.load(
+                'regions_us-central1_forwardingRules.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_forwardingRules_libcloud_lb_demo_lb(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'regions_us-central1_forwardingRules_libcloud-lb-demo-lb.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_forwardingRules_lcforwardingrule(
+            self, method, url, body, headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'regions_us-central1_forwardingRules_lcforwardingrule_delete.json')
+        else:
+            body = self.fixtures.load(
+                'regions_us-central1_forwardingRules_lcforwardingrule.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_targetPools(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                'regions_us-central1_targetPools_post.json')
+        else:
+            body = self.fixtures.load('regions_us-central1_targetPools.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_targetPools_lctargetpool(self, method, url,
+                                                      body, headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'regions_us-central1_targetPools_lctargetpool_delete.json')
+        else:
+            body = self.fixtures.load(
+                'regions_us-central1_targetPools_lctargetpool.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_targetPools_libcloud_lb_demo_lb_tp(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'regions_us-central1_targetPools_libcloud-lb-demo-lb-tp.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_targetPools_lctargetpool_removeHealthCheck(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'regions_us-central1_targetPools_lctargetpool_removeHealthCheck_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_targetPools_lctargetpool_addHealthCheck(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'regions_us-central1_targetPools_lctargetpool_addHealthCheck_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_targetPools_lctargetpool_removeInstance(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'regions_us-central1_targetPools_lctargetpool_removeInstance_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_targetPools_lctargetpool_addInstance(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'regions_us-central1_targetPools_lctargetpool_addInstance_post.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])
@@ -678,6 +1038,25 @@ class GCEMockHttp(MockHttpTestCase):
                 'zones_us-central1-a_instances_lcnode-001.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _zones_us_central1_b_instances_libcloud_lb_demo_www_000(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-b_instances_libcloud-lb-demo-www-000.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_b_instances_libcloud_lb_demo_www_001(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-b_instances_libcloud-lb-demo-www-001.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_b_instances_libcloud_lb_demo_www_002(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-b_instances_libcloud-lb-demo-www-002.json')
+        return (httplib.NOT_FOUND, body, self.json_hdr,
+                httplib.responses[httplib.NOT_FOUND])
+
     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])
diff --git a/libcloud/test/loadbalancer/test_gce.py b/libcloud/test/loadbalancer/test_gce.py
new file mode 100644
index 0000000..b60977f
--- /dev/null
+++ b/libcloud/test/loadbalancer/test_gce.py
@@ -0,0 +1,207 @@
+# 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 Load Balancer Driver
+"""
+import sys
+import unittest
+
+from libcloud.common.google import (GoogleBaseAuthConnection,
+                                    GoogleInstalledAppAuthConnection,
+                                    GoogleBaseConnection)
+from libcloud.compute.drivers.gce import (GCENodeDriver)
+from libcloud.loadbalancer.drivers.gce import (GCELBDriver)
+from libcloud.test.common.test_google import GoogleAuthMockHttp
+from libcloud.test.compute.test_gce import GCEMockHttp
+
+from libcloud.test import MockHttpTestCase, LibcloudTestCase
+
+from libcloud.test.secrets import GCE_PARAMS, GCE_KEYWORD_PARAMS
+
+
+class GCELoadBalancerTest(LibcloudTestCase):
+    GoogleBaseConnection._get_token_info_from_file = lambda x: None
+    GoogleBaseConnection._write_token_info_to_file = lambda x: None
+    GoogleInstalledAppAuthConnection.get_code = lambda x: '1234'
+    datacenter = 'us-central1-a'
+
+    def setUp(self):
+        GCEMockHttp.test = self
+        GCELBDriver.connectionCls.conn_classes = (GCEMockHttp, GCEMockHttp)
+        GCENodeDriver.connectionCls.conn_classes = (GCEMockHttp, GCEMockHttp)
+        GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp,
+                                                 GoogleAuthMockHttp)
+        GCEMockHttp.type = None
+        kwargs = GCE_KEYWORD_PARAMS.copy()
+        kwargs['auth_type'] = 'IA'
+        kwargs['datacenter'] = self.datacenter
+        self.driver = GCELBDriver(*GCE_PARAMS, **kwargs)
+
+    def test_get_node_from_ip(self):
+        ip = '173.255.115.146'
+        expected_name = 'node-name'
+        node = self.driver._get_node_from_ip(ip)
+        self.assertEqual(node.name, expected_name)
+
+        dummy_ip = '8.8.8.8'
+        node = self.driver._get_node_from_ip(dummy_ip)
+        self.assertTrue(node is None)
+
+    def test_list_protocols(self):
+        expected_protocols = ['TCP', 'UDP']
+        protocols = self.driver.list_protocols()
+        self.assertEqual(protocols, expected_protocols)
+
+    def test_list_balancers(self):
+        balancers = self.driver.list_balancers()
+        balancers_all = self.driver.list_balancers(ex_region='all')
+        balancer_name = 'lcforwardingrule'
+        self.assertEqual(len(balancers), 2)
+        self.assertEqual(len(balancers_all), 2)
+        self.assertEqual(balancers[0].name, balancer_name)
+
+    def test_create_balancer(self):
+        balancer_name = 'libcloud-lb-demo-lb'
+        tp_name = '%s-tp' % (balancer_name)
+        port = '80'
+        protocol = 'tcp'
+        algorithm = None
+        node0 = self.driver.gce.ex_get_node('libcloud-lb-demo-www-000',
+                                            'us-central1-b')
+        node1 = self.driver.gce.ex_get_node('libcloud-lb-demo-www-001',
+                                            'us-central1-b')
+        members = [node0, node1]
+        balancer = self.driver.create_balancer(balancer_name, port, protocol,
+                                               algorithm, members)
+        self.assertEqual(balancer.name, balancer_name)
+        self.assertEqual(balancer.extra['targetpool'].name, tp_name)
+        self.assertEqual(len(balancer.list_members()), 3)
+
+    def test_destory_balancer(self):
+        balancer_name = 'lcforwardingrule'
+        balancer = self.driver.get_balancer(balancer_name)
+        destroyed = balancer.destroy()
+        self.assertTrue(destroyed)
+
+    def test_get_balancer(self):
+        balancer_name = 'lcforwardingrule'
+        tp_name = 'lctargetpool'
+        balancer_ip = '173.255.119.224'
+        balancer = self.driver.get_balancer(balancer_name)
+        self.assertEqual(balancer.name, balancer_name)
+        self.assertEqual(balancer.extra['forwarding_rule'].name, balancer_name)
+        self.assertEqual(balancer.ip, balancer_ip)
+        self.assertEqual(balancer.extra['targetpool'].name, tp_name)
+
+    def test_attach_compute_node(self):
+        node = self.driver.gce.ex_get_node('libcloud-lb-demo-www-001',
+                                           'us-central1-b')
+        balancer = self.driver.get_balancer('lcforwardingrule')
+        member = self.driver._node_to_member(node, balancer)
+        # Detach member first
+        detach_member = balancer.detach_member(member)
+        self.assertEqual(len(balancer.list_members()), 1)
+        # Attach Node
+        attach_node = balancer.attach_compute_node(node)
+        self.assertEqual(len(balancer.list_members()), 2)
+
+    def test_detach_attach_member(self):
+        node = self.driver.gce.ex_get_node('libcloud-lb-demo-www-001',
+                                           'us-central1-b')
+        balancer = self.driver.get_balancer('lcforwardingrule')
+        member = self.driver._node_to_member(node, balancer)
+
+        # Check that balancer has 2 members
+        self.assertEqual(len(balancer.list_members()), 2)
+
+        # Remove a member and check that it now has 1 member
+        detach_member = balancer.detach_member(member)
+        self.assertEqual(len(balancer.list_members()), 1)
+
+        # Reattach member and check that it has 2 members again
+        attach_member = balancer.attach_member(member)
+        self.assertEqual(len(balancer.list_members()), 2)
+
+    def test_balancer_list_members(self):
+        balancer = self.driver.get_balancer('lcforwardingrule')
+        members = balancer.list_members()
+        self.assertEqual(len(members), 2)
+        member_ips = [m.ip for m in members]
+        self.assertTrue('173.255.113.234' in member_ips)
+
+    def test_ex_create_healthcheck(self):
+        healthcheck_name = 'lchealthcheck'
+        kwargs = {'host': 'lchost',
+                  'path': '/lc',
+                  'port': 8000,
+                  'interval': 10,
+                  'timeout': 10,
+                  'unhealthy_threshold': 4,
+                  'healthy_threshold': 3}
+        hc = self.driver.ex_create_healthcheck(healthcheck_name, **kwargs)
+        self.assertEqual(hc.name, healthcheck_name)
+        self.assertEqual(hc.path, '/lc')
+        self.assertEqual(hc.port, 8000)
+        self.assertEqual(hc.interval, 10)
+
+    def test_ex_list_healthchecks(self):
+        healthchecks = self.driver.ex_list_healthchecks()
+        self.assertEqual(len(healthchecks), 2)
+        self.assertEqual(healthchecks[0].name, 'basic-check')
+
+    def test_ex_balancer_detach_attach_healthcheck(self):
+        healthcheck = self.driver.gce.ex_get_healthcheck(
+            'libcloud-lb-demo-healthcheck')
+        balancer = self.driver.get_balancer('lcforwardingrule')
+
+        healthchecks = self.driver.ex_balancer_list_healthchecks(balancer)
+        self.assertEqual(len(healthchecks), 1)
+        # Detach Healthcheck
+        detach_healthcheck = self.driver.ex_balancer_detach_healthcheck(
+            balancer, healthcheck)
+        self.assertTrue(detach_healthcheck)
+        healthchecks = self.driver.ex_balancer_list_healthchecks(balancer)
+        self.assertEqual(len(healthchecks), 0)
+
+        # Reattach Healthcheck
+        attach_healthcheck = self.driver.ex_balancer_attach_healthcheck(
+            balancer, healthcheck)
+        self.assertTrue(attach_healthcheck)
+        healthchecks = self.driver.ex_balancer_list_healthchecks(balancer)
+        self.assertEqual(len(healthchecks), 1)
+
+    def test_ex_balancer_list_healthchecks(self):
+        balancer = self.driver.get_balancer('lcforwardingrule')
+        healthchecks = self.driver.ex_balancer_list_healthchecks(balancer)
+        self.assertEqual(healthchecks[0].name, 'libcloud-lb-demo-healthcheck')
+
+    def test_node_to_member(self):
+        node = self.driver.gce.ex_get_node('libcloud-lb-demo-www-001',
+                                           'us-central1-b')
+        balancer = self.driver.get_balancer('lcforwardingrule')
+        member = self.driver._node_to_member(node, balancer)
+        self.assertEqual(member.ip, node.public_ips[0])
+        self.assertEqual(member.id, node.name)
+        self.assertEqual(member.port, balancer.port)
+
+    def test_forwarding_rule_to_loadbalancer(self):
+        fwr = self.driver.gce.ex_get_forwarding_rule('lcforwardingrule')
+        balancer = self.driver._forwarding_rule_to_loadbalancer(fwr)
+        self.assertEqual(fwr.name, balancer.name)
+        self.assertEqual(fwr.address, balancer.ip)
+        self.assertEqual(fwr.extra['portRange'], balancer.port)
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())
-- 
1.7.9.5

