Fix LIBCLOUD-997, implements LIBCLOUD-998 and LIBCLOUD-604

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

Branch: refs/heads/trunk
Commit: c7464b57dcc7e09fdfa01348de85f85046dac3a1
Parents: 271813f
Author: micafer <[email protected]>
Authored: Fri May 25 09:05:49 2018 +0200
Committer: micafer <[email protected]>
Committed: Fri May 25 09:05:49 2018 +0200

----------------------------------------------------------------------
 docs/compute/drivers/openstack.rst              |  10 +-
 libcloud/common/openstack.py                    |   1 +
 libcloud/compute/drivers/openstack.py           | 110 ++++++++++++++++++-
 .../fixtures/openstack/_v2_0__networks.json     |  33 ++++++
 .../fixtures/openstack/_v2_0__subnets.json      |  62 +++++++++++
 libcloud/test/compute/test_openstack.py         |  26 ++++-
 6 files changed, 239 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/c7464b57/docs/compute/drivers/openstack.rst
----------------------------------------------------------------------
diff --git a/docs/compute/drivers/openstack.rst 
b/docs/compute/drivers/openstack.rst
index 199fa38..887057a 100644
--- a/docs/compute/drivers/openstack.rst
+++ b/docs/compute/drivers/openstack.rst
@@ -88,9 +88,17 @@ Available arguments:
 * ``ex_force_service_type``
 * ``ex_force_service_name``
 * ``ex_force_service_region``
-* ``ex_force_base_url`` - Base URL to the OpenStack API endpoint. By default,
+* ``ex_force_base_url`` - Base URL to the OpenStack nova API endpoint. By 
default,
   driver obtains API endpoint URL from the server catalog, but if this argument
   is provided, this step is skipped and the provided value is used directly.
+* ``ex_force_network_url`` - Base URL to the OpenStack neutron API endpoint. 
By default,
+  driver obtains API endpoint URL from the server catalog, but if this argument
+  is provided, this step is skipped and the provided value is used directly. 
Only valid 
+  in case of api_version >= 2.0.
+* ``ex_force_image_url`` - Base URL to the OpenStack glance API endpoint. By 
default,
+  driver obtains API endpoint URL from the server catalog, but if this argument
+  is provided, this step is skipped and the provided value is used directly. 
Only valid 
+  in case of api_version >= 2.0.
 
 Some examples which show how to use this arguments can be found in the section
 below.

http://git-wip-us.apache.org/repos/asf/libcloud/blob/c7464b57/libcloud/common/openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/common/openstack.py b/libcloud/common/openstack.py
index 954a15d..6b7327a 100644
--- a/libcloud/common/openstack.py
+++ b/libcloud/common/openstack.py
@@ -156,6 +156,7 @@ class OpenStackBaseConnection(ConnectionUserAndKey):
         if ex_force_auth_version:
             self._auth_version = ex_force_auth_version
 
+        self.base_url = ex_force_base_url
         self._ex_force_base_url = ex_force_base_url
         self._ex_force_auth_url = ex_force_auth_url
         self._ex_force_auth_token = ex_force_auth_token

http://git-wip-us.apache.org/repos/asf/libcloud/blob/c7464b57/libcloud/compute/drivers/openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/openstack.py 
b/libcloud/compute/drivers/openstack.py
index 9bacad2..dc164fb 100644
--- a/libcloud/compute/drivers/openstack.py
+++ b/libcloud/compute/drivers/openstack.py
@@ -2479,6 +2479,13 @@ class OpenStack_2_Connection(OpenStackComputeConnection):
     accept_format = 'application/json'
     default_content_type = 'application/json; charset=UTF-8'
 
+    def __init__(self, *args, **kwargs):
+        if 'ex_force_image_url' in kwargs:
+            del kwargs['ex_force_image_url']
+        if 'ex_force_network_url' in kwargs:
+            del kwargs['ex_force_network_url']
+        super(OpenStack_2_Connection, self).__init__(*args, **kwargs)
+
     def encode_data(self, data):
         return json.dumps(data)
 
@@ -2492,6 +2499,18 @@ class 
OpenStack_2_ImageConnection(OpenStackImageConnection):
         return json.dumps(data)
 
 
+class OpenStack_2_NetworkConnection(OpenStackBaseConnection):
+    service_type = 'network'
+    service_name = 'neutron'
+    service_region = 'RegionOne'
+    responseCls = OpenStack_1_1_Response
+    accept_format = 'application/json'
+    default_content_type = 'application/json; charset=UTF-8'
+
+    def encode_data(self, data):
+        return json.dumps(data)
+
+
 class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver):
     """
     OpenStack node driver.
@@ -2514,11 +2533,14 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver):
     # See https://developer.openstack.org/api-ref/
     # image/v2/index.html#list-image-members
     image_connectionCls = OpenStack_2_ImageConnection
+    network_connectionCls = OpenStack_2_NetworkConnection
     image_connection = None
+    network_connection = None
     type = Provider.OPENSTACK
 
     features = {"create_node": ["generates_password"]}
-    _networks_url_prefix = '/os-networks'
+    _networks_url_prefix = '/v2.0/networks'
+    _subnets_url_prefix = '/v2.0/subnets'
 
     def __init__(self, *args, **kwargs):
         original_connectionCls = self.connectionCls
@@ -2527,14 +2549,31 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver):
         if 'ex_force_auth_version' not in kwargs:
             kwargs['ex_force_auth_version'] = '3.x_password'
 
+        original_ex_force_base_url = kwargs.get('ex_force_base_url', None)
+
         # We run the init once to get the Glance V2 API connection
         # and put that on the object under self.image_connection.
+        self._ex_force_base_url = str(kwargs.pop('ex_force_image_url',
+                                                None))
+        kwargs['ex_force_base_url'] = self._ex_force_base_url
         self.connectionCls = self.image_connectionCls
         super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs)
         self.image_connection = self.connection
 
+        # We run the init once to get the Neutron V2 API connection
+        # and put that on the object under self.image_connection.
+        self._ex_force_base_url = str(kwargs.pop('ex_force_network_url',
+                                                    None))
+        kwargs['ex_force_base_url'] = self._ex_force_base_url
+        self.connectionCls = self.network_connectionCls
+        super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs)
+        self.network_connection = self.connection
+
         # We run the init again to get the compute API connection
         # and that's put under self.connection as normal.
+        self._ex_force_base_url = original_ex_force_base_url
+        if original_ex_force_base_url:
+            kwargs['ex_force_base_url'] = self._ex_force_base_url
         self.connectionCls = original_connectionCls
         super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs)
 
@@ -2698,6 +2737,57 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver):
         return self._to_image_member(response.object)
 
 
+    def _to_networks(self, obj):
+        networks = obj['networks']
+        return [self._to_network(network) for network in networks]
+
+    def _to_network(self, obj):
+        extra = {}
+        if obj.get('router:external', None):
+            extra['router:external'] = obj.get('router:external')
+        if obj.get('subnets', None):
+            extra['subnets'] = obj.get('subnets')
+        return OpenStackNetwork(id=obj['id'],
+                                name=obj['name'],
+                                cidr=None,
+                                driver=self,
+                                extra=extra)
+
+    def ex_list_networks(self):
+        """
+        Get a list of Networks that are available.
+
+        :rtype: ``list`` of :class:`OpenStackNetwork`
+        """
+        response = 
self.network_connection.request(self._networks_url_prefix).object
+        return self._to_networks(response)
+
+    def _to_subnets(self, obj):
+        subnets = obj['subnets']
+        return [self._to_subnet(subnet) for subnet in subnets]
+
+    def _to_subnet(self, obj):
+        extra = {}
+        if obj.get('router:external', None):
+            extra['router:external'] = obj.get('router:external')
+        if obj.get('subnets', None):
+            extra['subnets'] = obj.get('subnets')
+        return OpenStack_2_SubNet(id=obj['id'],
+                                  name=obj['name'],
+                                  cidr=None,
+                                  driver=self,
+                                  extra=extra)
+
+    def ex_list_subnets(self):
+        """
+        Get a list of Subnet that are available.
+
+        :rtype: ``list`` of :class:`OpenStack_2_SubNet`
+        """
+        response = 
self.network_connection.request(self._subnets_url_prefix).object
+        return self._to_subnets(response)
+
+
 class OpenStack_1_1_FloatingIpPool(object):
     """
     Floating IP Pool info.
@@ -2801,3 +2891,21 @@ class OpenStack_1_1_FloatingIpAddress(object):
         return ('<OpenStack_1_1_FloatingIpAddress: id=%s, ip_addr=%s,'
                 ' pool=%s, driver=%s>'
                 % (self.id, self.ip_address, self.pool, self.driver))
+
+
+class OpenStack_2_SubNet(object):
+    """
+    A Virtual SubNet.
+    """
+
+    def __init__(self, id, name, cidr, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.cidr = cidr
+        self.driver = driver
+        self.extra = extra or {}
+
+    def __repr__(self):
+        return '<OpenStack_2_SubNet id="%s" name="%s" cidr="%s">' % (self.id,
+                                                                     self.name,
+                                                                     self.cidr)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/c7464b57/libcloud/test/compute/fixtures/openstack/_v2_0__networks.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/openstack/_v2_0__networks.json 
b/libcloud/test/compute/fixtures/openstack/_v2_0__networks.json
new file mode 100644
index 0000000..7c54850
--- /dev/null
+++ b/libcloud/test/compute/fixtures/openstack/_v2_0__networks.json
@@ -0,0 +1,33 @@
+{
+    "network": {
+        "admin_state_up": true,
+        "availability_zone_hints": [],
+        "availability_zones": [
+            "nova"
+        ],
+        "created_at": "2016-03-08T20:19:41",
+        "dns_domain": "my-domain.org.",
+        "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+        "ipv4_address_scope": null,
+        "ipv6_address_scope": null,
+        "l2_adjacency": false,
+        "mtu": 1500,
+        "name": "private-network",
+        "port_security_enabled": true,
+        "project_id": "4fd44f30292945e481c7b8a0c8908869",
+        "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
+        "revision_number": 1,
+        "router:external": false,
+        "shared": true,
+        "status": "ACTIVE",
+        "subnets": [
+            "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+        ],
+        "tags": ["tag1,tag2"],
+        "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+        "updated_at": "2016-03-08T20:19:41",
+        "vlan_transparent": false,
+        "description": "",
+        "is_default": true
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/c7464b57/libcloud/test/compute/fixtures/openstack/_v2_0__subnets.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/openstack/_v2_0__subnets.json 
b/libcloud/test/compute/fixtures/openstack/_v2_0__subnets.json
new file mode 100644
index 0000000..d3f2763
--- /dev/null
+++ b/libcloud/test/compute/fixtures/openstack/_v2_0__subnets.json
@@ -0,0 +1,62 @@
+{
+    "subnets": [
+        {
+            "name": "private-subnet",
+            "enable_dhcp": true,
+            "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+            "segment_id": null,
+            "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+            "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+            "dns_nameservers": [],
+            "allocation_pools": [
+                {
+                    "start": "10.0.0.2",
+                    "end": "10.0.0.254"
+                }
+            ],
+            "host_routes": [],
+            "ip_version": 4,
+            "gateway_ip": "10.0.0.1",
+            "cidr": "10.0.0.0/24",
+            "id": "08eae331-0402-425a-923c-34f7cfe39c1b",
+            "created_at": "2016-10-10T14:35:34Z",
+            "description": "",
+            "ipv6_address_mode": null,
+            "ipv6_ra_mode": null,
+            "revision_number": 2,
+            "service_types": [],
+            "subnetpool_id": null,
+            "tags": ["tag1,tag2"],
+            "updated_at": "2016-10-10T14:35:34Z"
+        },
+        {
+            "name": "my_subnet",
+            "enable_dhcp": true,
+            "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+            "segment_id": null,
+            "project_id": "4fd44f30292945e481c7b8a0c8908869",
+            "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+            "dns_nameservers": [],
+            "allocation_pools": [
+                {
+                    "start": "192.0.0.2",
+                    "end": "192.255.255.254"
+                }
+            ],
+            "host_routes": [],
+            "ip_version": 4,
+            "gateway_ip": "192.0.0.1",
+            "cidr": "192.0.0.0/8",
+            "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b",
+            "created_at": "2016-10-10T14:35:47Z",
+            "description": "",
+            "ipv6_address_mode": null,
+            "ipv6_ra_mode": null,
+            "revision_number": 2,
+            "service_types": [],
+            "subnetpool_id": null,
+            "tags": ["tag1,tag2"],
+            "updated_at": "2016-10-10T14:35:47Z"
+        }
+    ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/c7464b57/libcloud/test/compute/test_openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_openstack.py 
b/libcloud/test/compute/test_openstack.py
index 54606ef..e1dd271 100644
--- a/libcloud/test/compute/test_openstack.py
+++ b/libcloud/test/compute/test_openstack.py
@@ -1568,7 +1568,9 @@ class OpenStack_2_Tests(OpenStack_1_1_Tests):
     driver_type = OpenStack_2_NodeDriver
     driver_kwargs = {
         'ex_force_auth_version': '2.0',
-        'ex_force_auth_url': 'https://auth.api.example.com'
+        'ex_force_auth_url': 'https://auth.api.example.com:8774',
+        'ex_force_image_url': 'https://auth.api.example.com:9292',
+        'ex_force_network_url': 'https://auth.api.example.com:9696'
     }
 
     def setUp(self):
@@ -1692,6 +1694,21 @@ class OpenStack_2_Tests(OpenStack_1_1_Tests):
         self.assertEqual(image_member.extra['updated'], '2018-03-02T14:20:37Z')
         self.assertEqual(image_member.extra['schema'], '/v2/schemas/member')
 
+    def test_ex_list_networks(self):
+        networks = self.driver.ex_list_networks()
+        network = networks[0]
+
+        self.assertEqual(len(networks), 1)
+        self.assertEqual(network.name, 'private-network')
+        self.assertEqual(network.extra['subnets'], 
['54d6f61d-db07-451c-9ab3-b9609b6b6f0b'])
+
+    def test_ex_list_subnets(self):
+        subnets = self.driver.ex_list_subnets()
+        subnet = subnets[0]
+
+        self.assertEqual(len(subnets), 2)
+        self.assertEqual(subnet.name, 'private-subnet')
+        self.assertEqual(subnet.cidr, '10.0.0.0/24')
 
 class OpenStack_1_1_FactoryMethodTests(OpenStack_1_1_Tests):
     should_list_locations = False
@@ -2144,6 +2161,13 @@ class OpenStack_2_0_MockHttp(OpenStack_1_1_MockHttp):
             setattr(self, new_name, method_type(method, self,
                                                 OpenStack_2_0_MockHttp))
 
+    def _v2_0_networks(self, method, url, body, headers):
+        body = self.auth_fixtures.load('_v2_0__networks.json')
+        return (httplib.OK, body, self.json_content_headers, 
httplib.responses[httplib.OK])
+
+    def _v2_0_subnets(self, method, url, body, headers):
+        body = self.auth_fixtures.load('_v2_0__subnets.json')
+        return (httplib.OK, body, self.json_content_headers, 
httplib.responses[httplib.OK])
 
 class OpenStack_1_1_Auth_2_0_Tests(OpenStack_1_1_Tests):
     driver_args = OPENSTACK_PARAMS + ('1.1',)

Reply via email to