libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From erjoh...@apache.org
Subject libcloud git commit: [google compute] Add support for Subnetworks
Date Sat, 07 May 2016 23:02:16 GMT
Repository: libcloud
Updated Branches:
  refs/heads/trunk 9eb0507fa -> 32465669c


[google compute] Add support for Subnetworks

Signed-off-by: Eric Johnson <erjohnso@google.com>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/32465669
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/32465669
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/32465669

Branch: refs/heads/trunk
Commit: 32465669cf2c0c9b46054f15c3b767b584137ba2
Parents: 9eb0507
Author: Eric Johnson <erjohnso@google.com>
Authored: Tue May 3 17:23:35 2016 +0000
Committer: Eric Johnson <erjohnso@google.com>
Committed: Sat May 7 23:01:49 2016 +0000

----------------------------------------------------------------------
 CHANGES.rst                                     |   4 +
 demos/gce_demo.py                               |  82 +++++-
 libcloud/compute/drivers/gce.py                 | 292 ++++++++++++++++++-
 .../fixtures/gce/aggregated_subnetworks.json    |  66 +++++
 .../compute/fixtures/gce/global_networks.json   |  75 ++---
 .../fixtures/gce/global_networks_cf.json        |  14 +
 ...on_regions_us-central1_subnetworks_post.json |  15 +
 .../fixtures/gce/regions_asia-east1.json        |  65 +++++
 .../fixtures/gce/regions_europe-west1.json      |  64 ++++
 .../fixtures/gce/regions_us-central1.json       |  65 +++++
 .../gce/regions_us-central1_subnetworks.json    |  18 ++
 ...entral1_subnetworks_cf_972cf02e6ad49112.json |  11 +
 .../regions_us-central1_subnetworks_post.json   |  14 +
 .../compute/fixtures/gce/regions_us-east1.json  |  64 ++++
 .../fixtures/gce/zones_asia-east1-b.json        |  10 +
 .../compute/fixtures/gce/zones_us-east1-b.json  |  10 +
 libcloud/test/compute/test_gce.py               | 132 ++++++++-
 17 files changed, 954 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 55e914a..3d6ebc8 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -24,6 +24,10 @@ General
 Compute
 ~~~~~~~
 
+- Add Subnetworks to GCE driver
+  (GITHUB-780)
+  [Eric Johnson]
+
 - Fix missing pricing data for GCE
   (LIBCLOUD-713, GITHUB-779)
   [Eric Johnson]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/demos/gce_demo.py
----------------------------------------------------------------------
diff --git a/demos/gce_demo.py b/demos/gce_demo.py
index 03201d9..696ebae 100755
--- a/demos/gce_demo.py
+++ b/demos/gce_demo.py
@@ -270,7 +270,6 @@ def clean_up(gce, base_name, node_list=None, resource_list=None):
                 raise
 
 
-# ==== COMPUTE CODE STARTS HERE ====
 def main_compute():
     start_time = datetime.datetime.now()
     display('Compute demo/test start time: %s' % str(start_time))
@@ -300,6 +299,9 @@ def main_compute():
     firewalls = gce.ex_list_firewalls()
     display('Firewalls:', firewalls)
 
+    subnetworks = gce.ex_list_subnetworks()
+    display('Subnetworks:', subnetworks)
+
     networks = gce.ex_list_networks()
     display('Networks:', networks)
 
@@ -317,8 +319,78 @@ def main_compute():
 
     # == Clean up any old demo resources ==
     display('Cleaning up any "%s" resources' % DEMO_BASE_NAME)
+    # Delete subnetworks first, networks last
+    clean_up(gce, DEMO_BASE_NAME, None, subnetworks)
     clean_up(gce, DEMO_BASE_NAME, all_nodes,
-             all_addresses + all_volumes + firewalls + networks + snapshots)
+             all_addresses + all_volumes + firewalls + snapshots + networks)
+
+    # == Create a Legacy Network ==
+    display('Creating Legacy Network:')
+    name = '%s-legacy-network' % DEMO_BASE_NAME
+    cidr = '10.10.0.0/16'
+    network_legacy = gce.ex_create_network(name, cidr)
+    display('  Network %s created' % name)
+
+    # == Delete the Legacy Network ==
+    display('Delete Legacy Network:')
+    network_legacy.destroy()
+    display('  Network %s delete' % name)
+
+    # == Create an auto network ==
+    display('Creating Auto Network:')
+    name = '%s-auto-network' % DEMO_BASE_NAME
+    network_auto = gce.ex_create_network(name, cidr=None, mode='auto')
+    display('  AutoNetwork %s created' % network_auto.name)
+
+    # == Display subnetworks from the auto network ==
+    subnets = []
+    for sn in network_auto.subnetworks:
+        subnets.append(gce.ex_get_subnetwork(sn))
+    display('Display subnetworks:', subnets)
+
+    # == Delete the auto network ==
+    display('Delete Auto Network:')
+    network_auto.destroy()
+    display('  AutoNetwork %s deleted' % name)
+
+    # == Create an custom network ==
+    display('Creating Custom Network:')
+    name = '%s-custom-network' % DEMO_BASE_NAME
+    network_custom = gce.ex_create_network(name, cidr=None, mode='custom')
+    display('  Custom Network %s created' % network_custom.name)
+
+    # == Create a subnetwork ==
+    display('Creating Subnetwork:')
+    sname = '%s-subnetwork' % DEMO_BASE_NAME
+    region = 'us-central1'
+    cidr = '192.168.17.0/24'
+    subnet = gce.ex_create_subnetwork(sname, cidr, network_custom, region)
+    display('  Subnetwork %s created' % subnet.name)
+    # Refresh object, now that it has a subnet
+    network_custom = gce.ex_get_network(name)
+
+    # == Display subnetworks from the auto network ==
+    subnets = []
+    for sn in network_custom.subnetworks:
+        subnets.append(gce.ex_get_subnetwork(sn))
+    display('Display custom subnetworks:', subnets)
+
+    # == Delete an subnetwork ==
+    display('Delete Custom Subnetwork:')
+    subnet.destroy()
+    display('  Custom Subnetwork %s deleted' % sname)
+    is_deleted = False
+    while not is_deleted:
+        time.sleep(3)
+        try:
+            subnet = gce.ex_get_subnetwork(sname, region)
+        except ResourceNotFoundError:
+            is_deleted = True
+
+    # == Delete the auto network ==
+    display('Delete Custom Network:')
+    network_custom.destroy()
+    display('  Custom Network %s deleted' % name)
 
     # == Create Node with disk auto-created ==
     if MAX_NODES > 1:
@@ -480,6 +552,9 @@ def main_compute():
     firewalls = gce.ex_list_firewalls()
     display('Firewalls:', firewalls)
 
+    subnetworks = gce.ex_list_subnetworks()
+    display('Subnetworks:', subnetworks)
+
     networks = gce.ex_list_networks()
     display('Networks:', networks)
 
@@ -488,8 +563,9 @@ def main_compute():
 
     if CLEANUP:
         display('Cleaning up %s resources created' % DEMO_BASE_NAME)
+        clean_up(gce, DEMO_BASE_NAME, None, subnetworks)
         clean_up(gce, DEMO_BASE_NAME, nodes,
-                 addresses + firewalls + networks + snapshots)
+                 addresses + firewalls + snapshots + networks)
         volumes = gce.list_volumes()
         clean_up(gce, DEMO_BASE_NAME, None, volumes)
     end_time = datetime.datetime.now()

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/compute/drivers/gce.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 371e91d..45a2ce3 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -519,6 +519,33 @@ class GCENodeImage(NodeImage):
                                               deprecated, obsolete, deleted)
 
 
+class GCESubnetwork(UuidMixin):
+    """A GCE Subnetwork object class."""
+    def __init__(self, id, name, cidr, network, region, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.cidr = cidr
+        self.network = network
+        self.region = region
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def destroy(self):
+        """
+        Destroy this subnetwork
+
+        :return: True if successful
+        :rtype:  ``bool``
+        """
+        return self.driver.ex_destroy_subnetwork(self)
+
+    def __repr__(self):
+        return '<GCESubnetwork id="%s" name="%s" region="%s" network="%s" ' \
+               'cidr="%s">' % (self.id, self.name, self.region.name,
+                               self.network.name, self.cidr)
+
+
 class GCENetwork(UuidMixin):
     """A GCE Network object class."""
     def __init__(self, id, name, cidr, driver, extra=None):
@@ -527,6 +554,11 @@ class GCENetwork(UuidMixin):
         self.cidr = cidr
         self.driver = driver
         self.extra = extra
+        self.mode = 'legacy'
+        self.subnetworks = []
+        if 'mode' in extra and extra['mode'] != 'legacy':
+            self.mode = extra['mode']
+            self.subnetworks = extra['subnetworks']
         UuidMixin.__init__(self)
 
     def destroy(self):
@@ -539,8 +571,8 @@ class GCENetwork(UuidMixin):
         return self.driver.ex_destroy_network(network=self)
 
     def __repr__(self):
-        return '<GCENetwork id="%s" name="%s" cidr="%s">' % (
-            self.id, self.name, self.cidr)
+        return '<GCENetwork id="%s" name="%s" cidr="%s" mode="%s">' % (
+            self.id, self.name, self.cidr, self.mode)
 
 
 class GCERoute(UuidMixin):
@@ -1587,6 +1619,43 @@ class GCENodeDriver(NodeDriver):
                        response.get('items', [])]
         return list_routes
 
+    def ex_list_subnetworks(self, region=None):
+        """
+        Return the list of subnetworks.
+
+        :keyword  region: Region for the subnetwork. Specify 'all' to return
+                          the aggregated list of subnetworks.
+        :type     region: ``str`` or :class:`GCERegion`
+
+        :return: A list of subnetwork objects.
+        :rtype: ``list`` of :class:`GCESubNetwork`
+        """
+        region = self._set_region(region)
+        if region is None:
+            request = '/aggregated/subnetworks'
+        else:
+            request = '/regions/%s/subnetworks' % (region.name)
+
+        list_subnetworks = []
+        response = self.connection.request(request, method='GET').object
+
+        if 'items' in response:
+            if region is None:
+                for v in response['items'].values():
+                    for i in v.get('subnetworks', []):
+                        try:
+                            list_subnetworks.append(self._to_subnetwork(i))
+                        except ResourceNotFoundError:
+                            pass
+            else:
+                for i in response['items']:
+                    try:
+                        list_subnetworks.append(self._to_subnetwork(i))
+                    except ResourceNotFoundError:
+                        pass
+
+        return list_subnetworks
+
     def ex_list_networks(self):
         """
         Return the list of networks.
@@ -2252,29 +2321,112 @@ class GCENodeDriver(NodeDriver):
 
         return self.ex_get_route(name)
 
-    def ex_create_network(self, name, cidr, description=None):
+    def ex_create_subnetwork(self, name, cidr=None, network=None, region=None,
+                             description=None):
         """
-        Create a network.
+        Create a subnetwork.
 
-        :param  name: Name of network to be created
+        :param  name: Name of subnetwork to be created
         :type   name: ``str``
 
         :param  cidr: Address range of network in CIDR format.
         :type   cidr: ``str``
 
+        :param  network: The network name or object this subnet belongs to.
+        :type   network: ``str`` or :class:`GCENetwork`
+
+        :param  region: The region the subnetwork belongs to.
+        :type   region: ``str`` or :class:`GCERegion`
+
         :param  description: Custom description for the network.
         :type   description: ``str`` or ``None``
 
+        :return:  Subnetwork object
+        :rtype:   :class:`GCESubnetwork`
+        """
+        if not cidr:
+            raise ValueError("Must provide an IP network in CIDR notation.")
+
+        if not network:
+            raise ValueError("Must provide a network for the subnetwork.")
+        else:
+            if isinstance(network, GCENetwork):
+                network_url = network.extra['selfLink']
+            else:
+                if network.startswith('https://'):
+                    network_url = network
+                else:
+                    network_obj = self.ex_get_network(network)
+                    network_url = network_obj.extra['selfLink']
+
+        if not region:
+            raise ValueError("Must provide a region for the subnetwork.")
+        else:
+            if isinstance(region, GCERegion):
+                region_url = region.extra['selfLink']
+            else:
+                if region.startswith('https://'):
+                    region_url = region
+                else:
+                    region_obj = self.ex_get_region(region)
+                    region_url = region_obj.extra['selfLink']
+
+        subnet_data = {}
+        subnet_data['name'] = name
+        subnet_data['description'] = description
+        subnet_data['ipCidrRange'] = cidr
+        subnet_data['network'] = network_url
+        subnet_data['region'] = region_url
+        region_name = region_url.split('/')[-1]
+
+        request = '/regions/%s/subnetworks' % (region_name)
+        self.connection.async_request(request, method='POST',
+                                      data=subnet_data)
+
+        return self.ex_get_subnetwork(name, region_name)
+
+    def ex_create_network(self, name, cidr, description=None, mode="legacy"):
+        """
+        Create a network. In November 2015, Google introduced Subnetworks and
+        suggests using networks with 'auto' generated subnetworks. See, the
+        `subnet docs <https://cloud.google.com/compute/docs/subnetworks>`_ for
+        more details. Note that libcloud follows the usability pattern from
+        the Cloud SDK (e.g. 'gcloud compute' command-line utility) and uses
+        'mode' to specify 'auto', 'custom', or 'legacy'.
+
+        :param  name: Name of network to be created
+        :type   name: ``str``
+
+        :param  cidr: Address range of network in CIDR format.
+        :type   cidr: ``str`` or ``None``
+
+        :param  description: Custom description for the network.
+        :type   description: ``str`` or ``None``
+
+        :param  mode: Create a 'auto', 'custom', or 'legacy' network.
+        :type   mode: ``str``
+
         :return:  Network object
         :rtype:   :class:`GCENetwork`
         """
         network_data = {}
         network_data['name'] = name
-        network_data['IPv4Range'] = cidr
         network_data['description'] = description
+        if mode.lower() not in ['auto', 'custom', 'legacy']:
+            raise ValueError("Invalid network mode: '%s'. Must be 'auto', "
+                             "'custom', or 'legacy'." % mode)
+        if cidr and mode in ['auto', 'custom']:
+            raise ValueError("Can only specify IPv4Range with 'legacy' mode.")
 
         request = '/global/networks'
 
+        if mode == 'legacy':
+            if not cidr:
+                raise ValueError("Must specify IPv4Range with 'legacy' mode.")
+            network_data['IPv4Range'] = cidr
+        else:
+            network_data['autoCreateSubnetworks'] = (mode.lower() == 'auto')
+
         self.connection.async_request(request, method='POST',
                                       data=network_data)
 
@@ -4254,6 +4406,91 @@ class GCENodeDriver(NodeDriver):
         response = self.connection.request(request, method='GET').object
         return self._to_route(response)
 
+    def ex_destroy_subnetwork(self, name, region=None):
+        """
+        Delete a Subnetwork object based on name and region.
+
+        :param  name: The name, URL or object of the subnetwork
+        :type   name: ``str`` or :class:`GCESubnetwork`
+
+        :param  name: The region object, name, or URL of the subnetwork
+        :type   name: ``str`` or :class:`GCERegion` or ``None``
+
+        :return:  True if successful
+        :rtype:   ``bool``
+        """
+        region_name = None
+        subnet_name = None
+        if region:
+            if isinstance(region, GCERegion):
+                region_name = region.name
+            else:
+                if region.startswith('https://'):
+                    region_name = region.split('/')[-1]
+                else:
+                    region_name = region
+        if isinstance(name, GCESubnetwork):
+            subnet_name = name.name
+            if not region_name:
+                region_name = name.region.name
+        else:
+            if name.startswith('https://'):
+                url_parts = self._get_components_from_path(name)
+                subnet_name = url_parts['name']
+                if not region_name:
+                    region_name = url_parts['region']
+            else:
+                subnet_name = name
+
+        if not region_name:
+            region = self._set_region(region)
+            if not region:
+                raise ("Could not determine region for subnetwork.")
+            else:
+                region_name = region.name
+
+        request = '/regions/%s/subnetworks/%s' % (region_name, subnet_name)
+        self.connection.request(request, method='DELETE').object
+        return True
+
+    def ex_get_subnetwork(self, name, region=None):
+        """
+        Return a Subnetwork object based on name and region.
+
+        :param  name: The name or URL of the subnetwork
+        :type   name: ``str``
+
+        :param  name: The region of the subnetwork
+        :type   name: ``str`` or :class:`GCERegion` or ``None``
+
+        :return:  A Subnetwork object
+        :rtype:   :class:`GCESubnetwork`
+        """
+        region_name = None
+        if name.startswith('https://'):
+            parts = self._get_components_from_path(name)
+            name = parts['name']
+            region_name = parts['region']
+        else:
+            if isinstance(region, GCERegion):
+                region_name = region.name
+            elif isinstance(region, str):
+                if region.startswith('https://'):
+                    region_name = region.split('/')[-1]
+                else:
+                    region_name = region
+
+        if not region_name:
+            region = self._set_region(region)
+            if not region:
+                raise ("Could not determine region for subnetwork.")
+            else:
+                region_name = region.name
+
+        request = '/regions/%s/subnetworks/%s' % (region_name, name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_subnetwork(response)
+
     def ex_get_network(self, name):
         """
         Return a Network object based on a network name.
@@ -5336,6 +5573,33 @@ class GCENodeDriver(NodeDriver):
                                  protocol=forwarding_rule.get('IPProtocol'),
                                  targetpool=target, driver=self, extra=extra)
 
+    def _to_subnetwork(self, subnetwork):
+        """
+        Return a Subnetwork object from the JSON-response dictionary.
+
+        :param  subnetwork: The dictionary describing the subnetwork.
+        :type   subnetwork: ``dict``
+
+        :return: Subnetwork object
+        :rtype: :class:`GCESubnetwork`
+        """
+        extra = {}
+
+        extra['creationTimestamp'] = subnetwork.get('creationTimestamp')
+        extra['description'] = subnetwork.get('description')
+        extra['gatewayAddress'] = subnetwork.get('gatewayAddress')
+        extra['ipCidrRange'] = subnetwork.get('ipCidrRange')
+        extra['network'] = subnetwork.get('network')
+        extra['region'] = subnetwork.get('region')
+        extra['selfLink'] = subnetwork.get('selfLink')
+        network = self._get_object_by_kind(subnetwork.get('network'))
+        region = self._get_object_by_kind(subnetwork.get('region'))
+
+        return GCESubnetwork(id=subnetwork['id'], name=subnetwork['name'],
+                             cidr=subnetwork.get('ipCidrRange'),
+                             network=network, region=region,
+                             driver=self, extra=extra)
+
     def _to_network(self, network):
         """
         Return a Network object from the JSON-response dictionary.
@@ -5349,9 +5613,23 @@ class GCENodeDriver(NodeDriver):
         extra = {}
 
         extra['selfLink'] = network.get('selfLink')
-        extra['gatewayIPv4'] = network.get('gatewayIPv4')
         extra['description'] = network.get('description')
         extra['creationTimestamp'] = network.get('creationTimestamp')
+        # 'legacy'
+        extra['gatewayIPv4'] = network.get('gatewayIPv4')
+        extra['IPv4Range'] = network.get('IPv4Range')
+        # 'auto' or 'custom'
+        extra['autoCreateSubnetworks'] = network.get('autoCreateSubnetworks')
+        extra['subnetworks'] = network.get('subnetworks')
+
+        # match Cloud SDK 'gcloud'
+        if 'autoCreateSubnetworks' in network:
+            if network['autoCreateSubnetworks']:
+                extra['mode'] = 'auto'
+            else:
+                extra['mode'] = 'custom'
+        else:
+            extra['mode'] = 'legacy'
 
         return GCENetwork(id=network['id'], name=network['name'],
                           cidr=network.get('IPv4Range'),

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/aggregated_subnetworks.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/aggregated_subnetworks.json b/libcloud/test/compute/fixtures/gce/aggregated_subnetworks.json
new file mode 100644
index 0000000..6b4adb6
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/aggregated_subnetworks.json
@@ -0,0 +1,66 @@
+{
+ "kind": "compute#subnetworkAggregatedList",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/aggregated/subnetworks",
+ "items": {
+  "regions/us-central1": {
+   "subnetworks": [
+    {
+     "kind": "compute#subnetwork",
+     "id": "4297043163355844284",
+     "creationTimestamp": "2016-03-25T05:34:27.209-07:00",
+     "gatewayAddress": "10.128.0.1",
+     "name": "cf-972cf02e6ad49112",
+     "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+     "ipCidrRange": "10.128.0.0/20",
+     "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1",
+     "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112"
+    }
+   ]
+  },
+  "regions/europe-west1": {
+   "subnetworks": [
+    {
+     "kind": "compute#subnetwork",
+     "id": "447043451408125628",
+     "creationTimestamp": "2016-03-25T05:34:27.272-07:00",
+     "gatewayAddress": "10.132.0.1",
+     "name": "cf-df1837b06a6f927b",
+     "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+     "ipCidrRange": "10.132.0.0/20",
+     "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/europe-west1",
+     "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/europe-west1/subnetworks/cf-df1837b06a6f927b"
+    }
+   ]
+  },
+  "regions/asia-east1": {
+   "subnetworks": [
+    {
+     "kind": "compute#subnetwork",
+     "id": "1240429769038270140",
+     "creationTimestamp": "2016-03-25T05:34:27.413-07:00",
+     "gatewayAddress": "10.140.0.1",
+     "name": "cf-4c2da366a0381eb9",
+     "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+     "ipCidrRange": "10.140.0.0/20",
+     "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/asia-east1",
+     "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/asia-east1/subnetworks/cf-4c2da366a0381eb9"
+    }
+   ]
+  },
+  "regions/us-east1": {
+   "subnetworks": [
+    {
+     "kind": "compute#subnetwork",
+     "id": "648244394139881148",
+     "creationTimestamp": "2016-03-25T05:34:27.475-07:00",
+     "gatewayAddress": "10.142.0.1",
+     "name": "cf-daf1e2124a902a47",
+     "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+     "ipCidrRange": "10.142.0.0/20",
+     "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-east1",
+     "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-east1/subnetworks/cf-daf1e2124a902a47"
+    }
+   ]
+  }
+ }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/global_networks.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/global_networks.json b/libcloud/test/compute/fixtures/gce/global_networks.json
index dc7ca55..1e041f4 100644
--- a/libcloud/test/compute/fixtures/gce/global_networks.json
+++ b/libcloud/test/compute/fixtures/gce/global_networks.json
@@ -1,34 +1,43 @@
 {
-  "id": "projects/project_name/global/networks",
-  "items": [
-    {
-      "IPv4Range": "10.240.0.0/16",
-      "creationTimestamp": "2013-06-19T12:37:13.233-07:00",
-      "gatewayIPv4": "10.240.0.1",
-      "id": "08257021638942464470",
-      "kind": "compute#network",
-      "name": "default",
-      "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/default"
-    },
-    {
-      "IPv4Range": "10.10.0.0/16",
-      "creationTimestamp": "2013-06-26T09:51:34.018-07:00",
-      "gatewayIPv4": "10.10.0.1",
-      "id": "13254259054875092094",
-      "kind": "compute#network",
-      "name": "libcloud-demo-europe-network",
-      "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/libcloud-demo-europe-network"
-    },
-    {
-      "IPv4Range": "10.10.0.0/16",
-      "creationTimestamp": "2013-06-26T09:48:15.703-07:00",
-      "gatewayIPv4": "10.10.0.1",
-      "id": "17172579178188075621",
-      "kind": "compute#network",
-      "name": "libcloud-demo-network",
-      "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/libcloud-demo-network"
-    }
-  ],
-  "kind": "compute#networkList",
-  "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks"
-}
\ No newline at end of file
+ "kind": "compute#networkList",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks",
+ "id": "projects/project_name/global/networks",
+ "items": [
+  {
+   "kind": "compute#network",
+   "id": "5125152985904090792",
+   "creationTimestamp": "2016-03-25T05:34:15.077-07:00",
+   "name": "cf",
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+   "autoCreateSubnetworks": true,
+   "subnetworks": [
+    "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112",
+    "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-east1/subnetworks/cf-daf1e2124a902a47",
+    "https://www.googleapis.com/compute/v1/projects/project_name/regions/asia-east1/subnetworks/cf-4c2da366a0381eb9",
+    "https://www.googleapis.com/compute/v1/projects/project_name/regions/europe-west1/subnetworks/cf-df1837b06a6f927b"
+   ]
+  },
+  {
+   "kind": "compute#network",
+   "id": "7887441312352916157",
+   "creationTimestamp": "2016-04-30T10:33:06.252-07:00",
+   "name": "custom",
+   "description": "Custom network",
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/custom",
+   "autoCreateSubnetworks": false,
+   "subnetworks": [
+    "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/subnet1"
+   ]
+  },
+  {
+   "kind": "compute#network",
+   "id": "2672023774255449680",
+   "creationTimestamp": "2014-01-21T10:30:55.392-08:00",
+   "name": "default",
+   "description": "Default network for the project",
+   "IPv4Range": "10.240.0.0/16",
+   "gatewayIPv4": "10.240.0.1",
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/default"
+  }
+ ]
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/global_networks_cf.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/global_networks_cf.json b/libcloud/test/compute/fixtures/gce/global_networks_cf.json
new file mode 100644
index 0000000..410b4d3
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_networks_cf.json
@@ -0,0 +1,14 @@
+{
+ "kind": "compute#network",
+ "id": "5125152985904090792",
+ "creationTimestamp": "2016-03-25T05:34:15.077-07:00",
+ "name": "cf",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+ "autoCreateSubnetworks": true,
+ "subnetworks": [
+  "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112",
+  "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-east1/subnetworks/cf-daf1e2124a902a47",
+  "https://www.googleapis.com/compute/v1/projects/project_name/regions/asia-east1/subnetworks/cf-4c2da366a0381eb9",
+  "https://www.googleapis.com/compute/v1/projects/project_name/regions/europe-west1/subnetworks/cf-df1837b06a6f927b"
+ ]
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_subnetworks_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_subnetworks_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_subnetworks_post.json
new file mode 100644
index 0000000..f117253
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_regions_us-central1_subnetworks_post.json
@@ -0,0 +1,15 @@
+{
+  "id": "16064059851942653139",
+  "insertTime": "2013-06-26T12:21:40.299-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_subnetworks_post",
+  "operationType": "insert",
+  "progress": 100,
+  "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1",
+  "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/operations/operation-regions_us-central1_subnetworks_post",
+  "startTime": "2013-06-26T12:21:40.358-07:00",
+  "status": "DONE",
+  "targetId": "01531551729918243104",
+  "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112",
+  "user": "897001307951@developer.gserviceaccount.com"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/regions_asia-east1.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/regions_asia-east1.json b/libcloud/test/compute/fixtures/gce/regions_asia-east1.json
new file mode 100644
index 0000000..adc4569
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_asia-east1.json
@@ -0,0 +1,65 @@
+{
+ "kind": "compute#region",
+ "id": "1220",
+ "creationTimestamp": "2014-05-30T18:35:16.514-07:00",
+ "name": "asia-east1",
+ "description": "asia-east1",
+ "status": "UP",
+ "zones": [
+  "https://www.googleapis.com/compute/v1/projects/project_name/zones/asia-east1-a",
+  "https://www.googleapis.com/compute/v1/projects/project_name/zones/asia-east1-b"
+ ],
+ "quotas": [
+  {
+   "metric": "CPUS",
+   "limit": 24.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "DISKS_TOTAL_GB",
+   "limit": 10240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "STATIC_ADDRESSES",
+   "limit": 7.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "IN_USE_ADDRESSES",
+   "limit": 23.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "SSD_TOTAL_GB",
+   "limit": 2048.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "LOCAL_SSD_TOTAL_GB",
+   "limit": 10240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCE_GROUPS",
+   "limit": 100.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCE_GROUP_MANAGERS",
+   "limit": 50.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCES",
+   "limit": 240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "AUTOSCALERS",
+   "limit": 50.0,
+   "usage": 0.0
+  }
+ ],
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/asia-east1"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/regions_europe-west1.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/regions_europe-west1.json b/libcloud/test/compute/fixtures/gce/regions_europe-west1.json
new file mode 100644
index 0000000..59d174d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_europe-west1.json
@@ -0,0 +1,64 @@
+{
+ "kind": "compute#region",
+ "id": "1100",
+ "creationTimestamp": "2014-05-30T18:35:16.413-07:00",
+ "name": "europe-west1",
+ "description": "europe-west1",
+ "status": "UP",
+ "zones": [
+  "https://www.googleapis.com/compute/v1/projects/project_name/zones/europe-west1-b"
+ ],
+ "quotas": [
+  {
+   "metric": "CPUS",
+   "limit": 24.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "DISKS_TOTAL_GB",
+   "limit": 10240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "STATIC_ADDRESSES",
+   "limit": 7.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "IN_USE_ADDRESSES",
+   "limit": 23.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "SSD_TOTAL_GB",
+   "limit": 2048.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "LOCAL_SSD_TOTAL_GB",
+   "limit": 10240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCE_GROUPS",
+   "limit": 100.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCE_GROUP_MANAGERS",
+   "limit": 50.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCES",
+   "limit": 240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "AUTOSCALERS",
+   "limit": 50.0,
+   "usage": 0.0
+  }
+ ],
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/europe-west1"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/regions_us-central1.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1.json b/libcloud/test/compute/fixtures/gce/regions_us-central1.json
new file mode 100644
index 0000000..a070fbc
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1.json
@@ -0,0 +1,65 @@
+{
+ "kind": "compute#region",
+ "id": "1000",
+ "creationTimestamp": "2014-05-30T18:35:16.413-07:00",
+ "name": "us-central1",
+ "description": "us-central1",
+ "status": "UP",
+ "zones": [
+  "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+  "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b"
+ ],
+ "quotas": [
+  {
+   "metric": "CPUS",
+   "limit": 1050.0,
+   "usage": 30.0
+  },
+  {
+   "metric": "DISKS_TOTAL_GB",
+   "limit": 20000.0,
+   "usage": 344.0
+  },
+  {
+   "metric": "STATIC_ADDRESSES",
+   "limit": 10.0,
+   "usage": 2.0
+  },
+  {
+   "metric": "IN_USE_ADDRESSES",
+   "limit": 1050.0,
+   "usage": 11.0
+  },
+  {
+   "metric": "SSD_TOTAL_GB",
+   "limit": 2048.0,
+   "usage": 500.0
+  },
+  {
+   "metric": "LOCAL_SSD_TOTAL_GB",
+   "limit": 10240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCE_GROUPS",
+   "limit": 100.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCE_GROUP_MANAGERS",
+   "limit": 50.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCES",
+   "limit": 10500.0,
+   "usage": 11.0
+  },
+  {
+   "metric": "AUTOSCALERS",
+   "limit": 50.0,
+   "usage": 0.0
+  }
+ ],
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks.json
new file mode 100644
index 0000000..902ce25
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks.json
@@ -0,0 +1,18 @@
+{
+ "kind": "compute#subnetworkList",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks",
+ "id": "projects/project_name/regions/us-central1/subnetworks",
+ "items": [
+  {
+   "kind": "compute#subnetwork",
+   "id": "4297043163355844284",
+   "creationTimestamp": "2016-03-25T05:34:27.209-07:00",
+   "gatewayAddress": "10.128.0.1",
+   "name": "cf-972cf02e6ad49112",
+   "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+   "ipCidrRange": "10.128.0.0/20",
+   "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1",
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112"
+  }
+ ]
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks_cf_972cf02e6ad49112.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks_cf_972cf02e6ad49112.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks_cf_972cf02e6ad49112.json
new file mode 100644
index 0000000..855eda5
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks_cf_972cf02e6ad49112.json
@@ -0,0 +1,11 @@
+{
+ "kind": "compute#subnetwork",
+ "id": "4297043163355844284",
+ "creationTimestamp": "2016-03-25T05:34:27.209-07:00",
+ "gatewayAddress": "10.128.0.1",
+ "name": "cf-972cf02e6ad49112",
+ "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+ "ipCidrRange": "10.128.0.0/20",
+ "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks_post.json b/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks_post.json
new file mode 100644
index 0000000..57a540f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-central1_subnetworks_post.json
@@ -0,0 +1,14 @@
+{
+  "id": "16064059851942653139",
+  "insertTime": "2013-06-26T12:21:40.299-07:00",
+  "kind": "compute#operation",
+  "name": "operation-regions_us-central1_subnetworks_post",
+  "operationType": "insert",
+  "progress": 0,
+  "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1",
+  "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/operations/operation-regions_us-central1_subnetworks_post",
+  "startTime": "2013-06-26T12:21:40.358-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112",
+  "user": "897001307951@developer.gserviceaccount.com"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/regions_us-east1.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-east1.json b/libcloud/test/compute/fixtures/gce/regions_us-east1.json
new file mode 100644
index 0000000..071bb40
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-east1.json
@@ -0,0 +1,64 @@
+{
+ "kind": "compute#region",
+ "id": "1230",
+ "creationTimestamp": "2014-09-03T16:13:49.013-07:00",
+ "name": "us-east1",
+ "description": "us-east1",
+ "status": "UP",
+ "zones": [
+  "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b"
+ ],
+ "quotas": [
+  {
+   "metric": "CPUS",
+   "limit": 24.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "DISKS_TOTAL_GB",
+   "limit": 10240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "STATIC_ADDRESSES",
+   "limit": 7.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "IN_USE_ADDRESSES",
+   "limit": 23.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "SSD_TOTAL_GB",
+   "limit": 2048.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "LOCAL_SSD_TOTAL_GB",
+   "limit": 10240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCE_GROUPS",
+   "limit": 100.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCE_GROUP_MANAGERS",
+   "limit": 50.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "INSTANCES",
+   "limit": 240.0,
+   "usage": 0.0
+  },
+  {
+   "metric": "AUTOSCALERS",
+   "limit": 50.0,
+   "usage": 0.0
+  }
+ ],
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-east1"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/zones_asia-east1-b.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_asia-east1-b.json b/libcloud/test/compute/fixtures/gce/zones_asia-east1-b.json
new file mode 100644
index 0000000..1c3ac1b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_asia-east1-b.json
@@ -0,0 +1,10 @@
+{
+ "kind": "compute#zone",
+ "id": "2220",
+ "creationTimestamp": "2014-05-30T18:35:16.575-07:00",
+ "name": "asia-east1-a",
+ "description": "asia-east1-a",
+ "status": "UP",
+ "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/asia-east1",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/asia-east1-a"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/fixtures/gce/zones_us-east1-b.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-east1-b.json b/libcloud/test/compute/fixtures/gce/zones_us-east1-b.json
new file mode 100644
index 0000000..1460ee2
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-east1-b.json
@@ -0,0 +1,10 @@
+{
+ "kind": "compute#zone",
+ "id": "2231",
+ "creationTimestamp": "2015-09-08T16:57:06.746-07:00",
+ "name": "us-east1-b",
+ "description": "us-east1-b",
+ "status": "UP",
+ "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-east1",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/32465669/libcloud/test/compute/test_gce.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py
index e8024dc..77e3775 100644
--- a/libcloud/test/compute/test_gce.py
+++ b/libcloud/test/compute/test_gce.py
@@ -1,4 +1,4 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
+# License 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
@@ -25,9 +25,9 @@ from libcloud.compute.drivers.gce import (GCENodeDriver, API_VERSION,
                                           GCEAddress, GCEBackendService,
                                           GCEFirewall, GCEForwardingRule,
                                           GCEHealthCheck, GCENetwork,
-                                          GCENodeImage, GCERoute,
+                                          GCENodeImage, GCERoute, GCERegion,
                                           GCETargetHttpProxy, GCEUrlMap,
-                                          GCEZone)
+                                          GCEZone, GCESubnetwork)
 from libcloud.common.google import (GoogleBaseAuthConnection,
                                     ResourceNotFoundError, ResourceExistsError,
                                     InvalidRequestError, GoogleBaseError)
@@ -128,6 +128,7 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
                         d.ex_list_forwarding_rules,
                         d.ex_list_healthchecks,
                         d.ex_list_networks,
+                        d.ex_list_subnetworks,
                         d.ex_list_project_images,
                         d.ex_list_regions,
                         d.ex_list_routes,
@@ -233,10 +234,72 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         self.assertEqual(len(routes), 3)
         self.assertTrue('lcdemoroute' in [route.name for route in routes])
 
+    def test_ex_list_subnetworks(self):
+        subnetworks = self.driver.ex_list_subnetworks()
+        self.assertEqual(len(subnetworks), 1)
+        self.assertEqual(subnetworks[0].name, 'cf-972cf02e6ad49112')
+        self.assertEqual(subnetworks[0].cidr, '10.128.0.0/20')
+        subnetworks = self.driver.ex_list_subnetworks('all')
+        self.assertEqual(len(subnetworks), 4)
+
+    def test_ex_create_subnetwork(self):
+        name = 'cf-972cf02e6ad49112'
+        cidr = '10.128.0.0/20'
+        network_name = 'cf'
+        network = self.driver.ex_get_network(network_name)
+        region_name = 'us-central1'
+        region = self.driver.ex_get_region(region_name)
+        # test by network/region name
+        subnet = self.driver.ex_create_subnetwork(name, cidr, network_name, region_name)
+        self.assertTrue(isinstance(subnet, GCESubnetwork))
+        self.assertTrue(isinstance(subnet.region, GCERegion))
+        self.assertTrue(isinstance(subnet.network, GCENetwork))
+        self.assertEqual(subnet.name, name)
+        self.assertEqual(subnet.cidr, cidr)
+        # test by network/region object
+        subnet = self.driver.ex_create_subnetwork(name, cidr, network, region)
+        self.assertTrue(isinstance(subnet, GCESubnetwork))
+        self.assertTrue(isinstance(subnet.region, GCERegion))
+        self.assertTrue(isinstance(subnet.network, GCENetwork))
+        self.assertEqual(subnet.name, name)
+        self.assertEqual(subnet.cidr, cidr)
+
+    def test_ex_destroy_subnetwork(self):
+        name = 'cf-972cf02e6ad49112'
+        region_name = 'us-central1'
+        region = self.driver.ex_get_region(region_name)
+        # delete with no region
+        self.assertTrue(self.driver.ex_destroy_subnetwork(name))
+        # delete with region name
+        self.assertTrue(self.driver.ex_destroy_subnetwork(name, region_name))
+        # delete with region object
+        self.assertTrue(self.driver.ex_destroy_subnetwork(name, region))
+
+    def test_ex_get_subnetwork(self):
+        name = 'cf-972cf02e6ad49112'
+        region_name = 'us-central1'
+        region = self.driver.ex_get_region(region_name)
+        # fetch by no region
+        subnetwork = self.driver.ex_get_subnetwork(name)
+        self.assertEqual(subnetwork.name, name)
+        # fetch by region name
+        subnetwork = self.driver.ex_get_subnetwork(name, region_name)
+        self.assertEqual(subnetwork.name, name)
+        # fetch by region object
+        subnetwork = self.driver.ex_get_subnetwork(name, region)
+        self.assertEqual(subnetwork.name, name)
+
     def test_ex_list_networks(self):
         networks = self.driver.ex_list_networks()
         self.assertEqual(len(networks), 3)
-        self.assertEqual(networks[0].name, 'default')
+        self.assertEqual(networks[0].name, 'cf')
+        self.assertEqual(networks[0].mode, 'auto')
+        self.assertEqual(len(networks[0].subnetworks), 4)
+        self.assertEqual(networks[1].name, 'custom')
+        self.assertEqual(networks[1].mode, 'custom')
+        self.assertEqual(len(networks[1].subnetworks), 1)
+        self.assertEqual(networks[2].name, 'default')
+        self.assertEqual(networks[2].mode, 'legacy')
 
     def test_list_nodes(self):
         nodes = self.driver.list_nodes()
@@ -479,6 +542,16 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         self.assertEqual(network.name, network_name)
         self.assertEqual(network.cidr, cidr)
 
+    def test_ex_create_network_bad_options(self):
+        network_name = 'lcnetwork'
+        cidr = '10.11.0.0/16'
+        self.assertRaises(ValueError, self.driver.ex_create_network,
+                          network_name, cidr, mode='auto')
+        self.assertRaises(ValueError, self.driver.ex_create_network,
+                          network_name, cidr, mode='foobar')
+        self.assertRaises(ValueError, self.driver.ex_create_network,
+                          network_name, None, mode='legacy')
+
     def test_ex_set_machine_type_notstopped(self):
         # get running node, change machine type
         zone = 'us-central1-a'
@@ -1573,6 +1646,10 @@ class GCEMockHttp(MockHttpTestCase):
             body = self.fixtures.load('setCommonInstanceMetadata_post.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _aggregated_subnetworks(self, method, url, body, headers):
+        body = self.fixtures.load('aggregated_subnetworks.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _aggregated_addresses(self, method, url, body, headers):
         body = self.fixtures.load('aggregated_addresses.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
@@ -1733,6 +1810,10 @@ class GCEMockHttp(MockHttpTestCase):
             body = self.fixtures.load('global_networks.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _global_networks_cf(self, method, url, body, headers):
+        body = self.fixtures.load('global_networks_cf.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _global_networks_default(self, method, url, body, headers):
         body = self.fixtures.load('global_networks_default.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
@@ -1949,6 +2030,11 @@ class GCEMockHttp(MockHttpTestCase):
             body = self.fixtures.load('global_urlMaps_web_map.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _regions_us_central1_subnetworks_cf_972cf02e6ad49112(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'regions_us-central1_subnetworks_cf_972cf02e6ad49112.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _regions_us_central1_operations_operation_regions_us_central1_addresses_lcaddress_delete(
             self, method, url, body, headers):
         body = self.fixtures.load(
@@ -1967,6 +2053,12 @@ 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_subnetworks_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_regions_us-central1_subnetworks_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(
@@ -2232,6 +2324,30 @@ class GCEMockHttp(MockHttpTestCase):
             body = self.fixtures.load('global_addresses.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _regions_europe_west1(self, method, url, body, headers):
+        body = self.fixtures.load('regions_europe-west1.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_asia_east1(self, method, url, body, headers):
+        body = self.fixtures.load('regions_asia-east1.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1(self, method, url, body, headers):
+        body = self.fixtures.load('regions_us-central1.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_east1(self, method, url, body, headers):
+        body = self.fixtures.load('regions_us-east1.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _regions_us_central1_subnetworks(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                'regions_us-central1_subnetworks_post.json')
+        else:
+            body = self.fixtures.load('regions_us-central1_subnetworks.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(
@@ -2384,6 +2500,14 @@ class GCEMockHttp(MockHttpTestCase):
         body = self.fixtures.load('zones_asia-east1-a.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _zones_asia_east1_b(self, method, url, body, headers):
+        body = self.fixtures.load('zones_asia-east1-b.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_east1_b(self, method, url, body, headers):
+        body = self.fixtures.load('zones_us-east1-b.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _zones_us_central1_a_diskTypes(self, method, url, body, headers):
         body = self.fixtures.load('zones_us-central1-a_diskTypes.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])


Mime
View raw message