libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From to...@apache.org
Subject svn commit: r1199274 - in /libcloud/trunk: CHANGES libcloud/common/openstack.py test/compute/fixtures/openstack/_v2_0__auth.json test/compute/test_openstack.py
Date Tue, 08 Nov 2011 15:02:22 GMT
Author: tomaz
Date: Tue Nov  8 15:02:21 2011
New Revision: 1199274

URL: http://svn.apache.org/viewvc?rev=1199274&view=rev
Log:
Add support for Auth 2.0 API (keystone) to the OpenStack Auth connection class.
This patch has been submitted by Brad Morgan <morgabra at cloudkick dot com> and
is part of LIBCLOUD-128.

Added:
    libcloud/trunk/test/compute/fixtures/openstack/_v2_0__auth.json
Modified:
    libcloud/trunk/CHANGES
    libcloud/trunk/libcloud/common/openstack.py
    libcloud/trunk/test/compute/test_openstack.py

Modified: libcloud/trunk/CHANGES
URL: http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1199274&r1=1199273&r2=1199274&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Tue Nov  8 15:02:21 2011
@@ -1,6 +1,15 @@
                                                         -*- coding: utf-8 -*-
 
+Changes with Apache Libcloud in development:
+
+  *) Compute:
+
+     - Add support for Auth 2.0 API (keystone) to the OpenStack Auth
+       connection class.
+       [Brad Morgan]
+
 Changes with Apache Libcloud 0.6.1:
+
   *) General:
 
      - Modify ParamikoSSHClient.connect so it supports authentication using a

Modified: libcloud/trunk/libcloud/common/openstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/openstack.py?rev=1199274&r1=1199273&r2=1199274&view=diff
==============================================================================
--- libcloud/trunk/libcloud/common/openstack.py (original)
+++ libcloud/trunk/libcloud/common/openstack.py Tue Nov  8 15:02:21 2011
@@ -96,6 +96,8 @@ class OpenStackAuthConnection(Connection
             return self.authenticate_1_0()
         elif self.auth_version == "1.1":
             return self.authenticate_1_1()
+        elif self.auth_version == "2.0":
+            return self.authenticate_2_0()
         else:
             raise LibcloudError('Unsupported Auth Version requested')
 
@@ -151,6 +153,30 @@ class OpenStackAuthConnection(Connection
             except KeyError, e:
                 raise MalformedResponseError('Auth JSON response is missing required elements',
e)
 
+    # 'keystone' - http://docs.openstack.org/api/openstack-identity-service/2.0/content/Identity-Service-Concepts-e1362.html
+    def authenticate_2_0(self):
+        reqbody = json.dumps({'auth':{'passwordCredentials':{'username':self.user_id, 'password':self.key}}})
+        resp = self.request('tokens/',
+                    data=reqbody,
+                    headers={'Content-Type':'application/json'},
+                    method='POST')
+        if resp.status == httplib.UNAUTHORIZED:
+            raise InvalidCredsError()
+        elif resp.status not in [httplib.OK, httplib.NON_AUTHORITATIVE_INFORMATION]:
+            raise MalformedResponseError('Malformed response',
+                    body='code: %s body: %s' % (resp.status, resp.body),
+                    driver=self.driver)
+        else:
+            try:
+                body = json.loads(resp.body)
+            except Exception, e:
+                raise MalformedResponseError('Failed to parse JSON', e)
+            try:
+                self.auth_token = body['access']['token']['id']
+                self.urls = body['access']['serviceCatalog']
+            except KeyError, e:
+                raise MalformedResponseError('Auth JSON response is missing required elements',
e)
+
 class OpenStackBaseConnection(ConnectionUserAndKey):
 
     auth_url = None
@@ -173,21 +199,18 @@ class OpenStackBaseConnection(Connection
             self._auth_version = '1.1'
 
         super(OpenStackBaseConnection, self).__init__(
-            user_id, key)
+            user_id, key, secure=secure)
 
     def add_default_headers(self, headers):
         headers['X-Auth-Token'] = self.auth_token
         headers['Accept'] = self.accept_format
         return headers
 
-    def morph_action(self, action):
-        key = self._url_key
-
-        value = getattr(self, key, None)
+    def morph_action_hook(self, action):
+        value = getattr(self, self._url_key, None)
         if not value:
             self._populate_hosts_and_request_paths()
-
-        request_path = getattr(self, '__request_path_%s' % (key), '')
+        request_path = getattr(self, '__request_path_%s' % (self._url_key), '')
         action = request_path + action
         return action
 
@@ -206,10 +229,6 @@ class OpenStackBaseConnection(Connection
 
     def _get_default_region(self, arr):
         if len(arr):
-            for i in arr:
-                if i.get('v1Default', False):
-                    return i['publicURL']
-            # uber lame
             return arr[0]['publicURL']
         return None
 
@@ -224,7 +243,7 @@ class OpenStackBaseConnection(Connection
         request yet, do it here. Otherwise, just return the management host.
         """
         if not self.auth_token:
-            aurl = self.auth_url 
+            aurl = self.auth_url
 
             if self._ex_force_auth_url != None:
                 aurl = self._ex_force_auth_url
@@ -240,13 +259,20 @@ class OpenStackBaseConnection(Connection
             self.auth_token = osa.auth_token
 
             # TODO: Multi-region support
-            self.server_url = self._get_default_region(osa.urls.get('cloudServers', []))
-            self.cdn_management_url = self._get_default_region(osa.urls.get('cloudFilesCDN',
[]))
-            self.storage_url = self._get_default_region(osa.urls.get('cloudFiles', []))
-            # TODO: this is even more broken, the service catalog does NOT show load
-            # balanacers :(  You must hard code in the Rackspace Load balancer URLs...
-            self.lb_url = self.server_url.replace("servers", "ord.loadbalancers")
-            self.dns_url = self.server_url.replace("servers", "dns")
+            if self._auth_version == '2.0':
+                for service in osa.urls:
+                    if service.get('type') == 'compute':
+                        self.server_url = self._get_default_region(service.get('endpoints',
[]))
+            elif self._auth_version in ['1.1', '1.0']:
+                self.server_url = self._get_default_region(osa.urls.get('cloudServers', []))
+                self.cdn_management_url = self._get_default_region(osa.urls.get('cloudFilesCDN',
[]))
+                self.storage_url = self._get_default_region(osa.urls.get('cloudFiles', []))
+                # TODO: this is even more broken, the service catalog does NOT show load
+                # balanacers :(  You must hard code in the Rackspace Load balancer URLs...
+                self.lb_url = self.server_url.replace("servers", "ord.loadbalancers")
+                self.dns_url = self.server_url.replace("servers", "dns")
+            else:
+                raise LibcloudError('auth version "%s" not supported' % (self._auth_version))
 
             for key in ['server_url', 'storage_url', 'cdn_management_url',
                         'lb_url', 'dns_url']:
@@ -259,7 +285,7 @@ class OpenStackBaseConnection(Connection
                 scheme, server, request_path, param, query, fragment = (
                     urlparse.urlparse(base_url))
                 # Set host to where we want to make further requests to
-                setattr(self, '__%s' % (key), server)
+                setattr(self, '__%s' % (key), server+request_path)
                 setattr(self, '__request_path_%s' % (key), request_path)
 
             (self.host, self.port, self.secure, self.request_path) = self._tuple_from_url(self.base_url)

Added: libcloud/trunk/test/compute/fixtures/openstack/_v2_0__auth.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/_v2_0__auth.json?rev=1199274&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/_v2_0__auth.json (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/_v2_0__auth.json Tue Nov  8 15:02:21 2011
@@ -0,0 +1 @@
+{"access": {"token": {"expires": "2011-11-08T15:57:43.653263", "id": "aaaaaaaaaaaa-bbb-cccccccccccccc",
"tenant": {"id": "45", "name": "testproj-project"}}, "serviceCatalog": [{"endpoints": [{"adminURL":
"http://my.fake.hostname:8774/v1.1/slug", "region": "ORD", "internalURL": ".", "publicURL":
"http://my.fake.hostname:8774/v1.1/slug"}], "type": "compute", "name": "nova"}], "user": {"id":
"45", "roles": [{"tenantId": "45", "id": "2", "name": "Member"}], "name": "testproj"}}}

Modified: libcloud/trunk/test/compute/test_openstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/test_openstack.py?rev=1199274&r1=1199273&r2=1199274&view=diff
==============================================================================
--- libcloud/trunk/test/compute/test_openstack.py (original)
+++ libcloud/trunk/test/compute/test_openstack.py Tue Nov  8 15:02:21 2011
@@ -676,6 +676,10 @@ class OpenStack_1_1_MockHttp(MockHttpTes
     auth_fixtures = OpenStackFixtures()
     json_content_headers = {'content-type': 'application/json; charset=UTF-8'}
 
+    def _v2_0_tokens(self, method, url, body, headers):
+        body = self.auth_fixtures.load('_v2_0__auth.json')
+        return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
+
     def _v1_0_(self, method, url, body, headers):
         headers = {
             'x-auth-token': 'FE011C19-CF86-4F87-BE5D-9229145D7A06',
@@ -683,41 +687,41 @@ class OpenStack_1_1_MockHttp(MockHttpTes
         }
         return (httplib.NO_CONTENT, "", headers, httplib.responses[httplib.NO_CONTENT])
 
-    def _servers_detail(self, method, url, body, headers):
+    def _v1_1_slug_servers_detail(self, method, url, body, headers):
         body = self.fixtures.load('_servers_detail.json')
         return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
 
-    def _flavors_detail(self, method, url, body, headers):
+    def _v1_1_slug_flavors_detail(self, method, url, body, headers):
         body = self.fixtures.load('_flavors_detail.json')
         return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
 
-    def _images_detail(self, method, url, body, headers):
+    def _v1_1_slug_images_detail(self, method, url, body, headers):
         body = self.fixtures.load('_images_detail.json')
         return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
 
-    def _servers(self, method, url, body, headers):
+    def _v1_1_slug_servers(self, method, url, body, headers):
         body = self.fixtures.load('_servers.json')
         return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
 
-    def _servers_12065_action(self, method, url, body, headers):
+    def _v1_1_slug_servers_12065_action(self, method, url, body, headers):
         if method != "POST":
             self.fail('HTTP method other than POST to action URL')
 
         return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED])
 
-    def _servers_12064_action(self, method, url, body, headers):
+    def _v1_1_slug_servers_12064_action(self, method, url, body, headers):
         if method != "POST":
             self.fail('HTTP method other than POST to action URL')
 
         return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED])
 
-    def _servers_12065(self, method, url, body, headers):
+    def _v1_1_slug_servers_12065(self, method, url, body, headers):
         if method == "DELETE":
             return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED])
         else:
             raise NotImplementedError()
 
-    def _servers_12064(self, method, url, body, headers):
+    def _v1_1_slug_servers_12064(self, method, url, body, headers):
         if method == "GET":
             body = self.fixtures.load('_servers_12064.json')
             return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
@@ -729,12 +733,12 @@ class OpenStack_1_1_MockHttp(MockHttpTes
         else:
             raise NotImplementedError()
 
-    def _servers_12062(self, method, url, body, headers):
+    def _v1_1_slug_servers_12062(self, method, url, body, headers):
         if method == "GET":
             body = self.fixtures.load('_servers_12064.json')
             return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
 
-    def _servers_12063_metadata(self, method, url, body, headers):
+    def _v1_1_slug_servers_12063_metadata(self, method, url, body, headers):
         if method == "GET":
             body = self.fixtures.load('_servers_12063_metadata_two_keys.json')
             return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
@@ -742,26 +746,36 @@ class OpenStack_1_1_MockHttp(MockHttpTes
             body = self.fixtures.load('_servers_12063_metadata_two_keys.json')
             return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
 
-    def _flavors_7(self, method, url, body, headers):
+    def _v1_1_slug_flavors_7(self, method, url, body, headers):
         if method == "GET":
             body = self.fixtures.load('_flavors_7.json')
             return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
         else:
             raise NotImplementedError()
 
-    def _images_13(self, method, url, body, headers):
+    def _v1_1_slug_images_13(self, method, url, body, headers):
         if method == "GET":
             body = self.fixtures.load('_images_13.json')
             return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
         else:
             raise NotImplementedError()
 
-    def _images_DELETEUUID(self, method, url, body, headers):
+    def _v1_1_slug_images_DELETEUUID(self, method, url, body, headers):
         if method == "DELETE":
             return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED])
         else:
             raise NotImplementedError()
 
+class OpenStack_1_1_Auth_2_0_Tests(OpenStack_1_1_Tests):
+    driver_kwargs = {'ex_force_auth_version': '2.0'}
+
+    def setUp(self):
+        self.driver_klass.connectionCls.conn_classes = (OpenStack_1_1_MockHttp, OpenStack_1_1_MockHttp)
+        self.driver_klass.connectionCls.auth_url = "https://auth.api.example.com/v2.0/"
+        OpenStack_1_1_MockHttp.type = None
+        self.driver = self.create_driver()
+        clear_pricing_data()
+        self.node = self.driver.list_nodes()[1]
 
 if __name__ == '__main__':
     sys.exit(unittest.main())



Mime
View raw message