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())