From cc6ba2ff411e8d2fcbffcdab7958f9885a7841ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Thu, 3 Mar 2016 20:25:12 +0100 Subject: [PATCH] Updated to start with identity api v3 instead of v2, and advanced a lot --- .../OpenStack/openStack/UDSOpenStackClient.py | 189 ++++++++++++++---- 1 file changed, 152 insertions(+), 37 deletions(-) diff --git a/server/src/uds/services/OpenStack/openStack/UDSOpenStackClient.py b/server/src/uds/services/OpenStack/openStack/UDSOpenStackClient.py index 62b79aecc..52f10e2f7 100644 --- a/server/src/uds/services/OpenStack/openStack/UDSOpenStackClient.py +++ b/server/src/uds/services/OpenStack/openStack/UDSOpenStackClient.py @@ -48,6 +48,9 @@ __updated__ = '2016-03-03' logger = logging.getLogger(__name__) +# Required: Authentication v3 + + # This is a vary basic implementation for what we need from openstack # This does not includes (nor it is intention) full API implementation, just the parts we need # Theese are related to auth, compute & network basically @@ -57,13 +60,31 @@ logger = logging.getLogger(__name__) # Helpers def ensureResponseIsValid(response, errMsg=None): - if response.reason != 'OK': + if response.ok is False: if errMsg is None: errMsg = 'Error checking response' logger.error('{}: {}'.format(errMsg, response.content)) + print response.content + print response raise Exception(errMsg) +# Decorators +def authRequired(func): + def ensurer(obj, *args, **kwargs): + obj.ensureAuthenticated() + return func(obj, *args, **kwargs) + return ensurer + +def authProjectRequired(func): + def ensurer(obj, *args, **kwargs): + if obj._projectId is None: + raise Exception('Need a project for method {}'.format(func)) + obj.ensureAuthenticated() + return func(obj, *args, **kwargs) + return ensurer + + class UDSOpenStackClient(object): cache = Cache('uds-openstack') @@ -71,16 +92,18 @@ class UDSOpenStackClient(object): PRIVATE = 'private' INTERNAL = 'url' - def __init__(self, host, port, username, password, useSSL=False, tenant=None, access=None): + def __init__(self, host, port, domain, username, password, useSSL=False, projectId=None, region=None, access=None): self._authenticated = False self._tokenId = None - self._serviceCatalog = None + self._catalog = None self._access = UDSOpenStackClient.PUBLIC if access is None else access self._host, self._port = host, int(port) - self._username, self._password = username, password - self._tenant = tenant - self._tenantId = None + self._domain, self._username, self._password = domain, username, password + self._userId = None + self._projectId = projectId + self._project = None + self._region = region self._timeout = 10 self._authUrl = 'http{}://{}:{}/'.format('s' if useSSL else '', host, port) @@ -95,12 +118,12 @@ class UDSOpenStackClient(object): # self._cacheKey = h.hexdigest() - def _getEndpointFor(self, type_, region=None): # If no region is indicatad, first endpoint is returned - for i in self._serviceCatalog: + def _getEndpointFor(self, type_): # If no region is indicatad, first endpoint is returned + for i in self._catalog: if i['type'] == type_: for j in i['endpoints']: - if region is None or j['region'] == region: - return j[self._access + 'URL'] + if j['interface'] == self._access and (self._region is None or j['region'] == self._region): + return j['url'] def _requestHeaders(self): headers = {'content-type': 'application/json'} @@ -109,63 +132,155 @@ class UDSOpenStackClient(object): return headers - def authPassword(self): data = { 'auth': { - 'passwordCredentials': { - 'username': self._username, - 'password': self._password - + 'identity': { + 'methods': [ + 'password' + ], + 'password': { + 'user': { + 'name': self._username, + 'domain': { + 'name': 'Default' if self._domain is None else self._domain + }, + 'password': self._password + } + } } } } - if self._tenant is not None: - data['auth']['tenantName'] = self._tenant + if self._projectId is None: + data['auth']['scope'] = 'unscoped' + else: + data['auth']['scope'] = { + 'project': { + 'id': self._projectId + } + } - r = requests.post(self._authUrl + 'v2.0/tokens', - data=json.dumps(data), - headers={'content-type': 'application/json'}, - verify=False, - timeout=self._timeout) + r = requests.post(self._authUrl + 'v3/auth/tokens', + data=json.dumps(data), + headers={'content-type': 'application/json'}, + verify=False, + timeout=self._timeout) ensureResponseIsValid(r, 'Invalid Credentials') - self._authtenticated = True + self._authenticated = True + self._tokenId = r.headers['X-Subject-Token'] # Extract the token id - r = json.loads(r.content) - token = r['access']['token'] - self._tokenId = token['id'] - validity = (dateutil.parser.parse(token['expires']).replace(tzinfo=None) - dateutil.parser.parse(token['issued_at']).replace(tzinfo=None)).seconds - 60 + token = r.json()['token'] + self._userId = token['user']['id'] + validity = (dateutil.parser.parse(token['expires_at']).replace(tzinfo=None) - dateutil.parser.parse(token['issued_at']).replace(tzinfo=None)).seconds - 60 + logger.debug('The token {} will be valid for {}'.format(self._tokenId, validity)) # Now, if endpoints are present (only if tenant was specified), store & cache them - if self._tenant is not None: - self._serviceCatalog = r['access']['serviceCatalog'] - - print self._serviceCatalog + if self._projectId is not None: + self._catalog = token['catalog'] def ensureAuthenticated(self): if self._authenticated is False: self.authPassword() - def listTenants(self): - self.ensureAuthenticated() - r = requests.get(self._authUrl + 'v2.0/tenants/', + + @authRequired + def listProjects(self): + r = requests.get(self._authUrl + 'v3/users/{user_id}/projects'.format(user_id=self._userId), headers=self._requestHeaders()) ensureResponseIsValid(r, 'List Tenants') - return json.loads(r.content)['tenants'] + for p in json.loads(r.content)['projects']: + yield p + + @authRequired def listRegions(self): - self.ensureAuthenticated() r = requests.get(self._authUrl + 'v3/regions/', headers=self._requestHeaders()) ensureResponseIsValid(r, 'List Regions') - return json.loads(r.content)['regions'] + for r in json.loads(r.content)['regions']: + yield r + + + @authProjectRequired + def listVms(self): + url = self._getEndpointFor('compute') + '/servers' + while True: + r = requests.get(url, headers=self._requestHeaders()) + + ensureResponseIsValid(r, 'List Vms') + + json = r.json() + + for v in json['servers']: + yield { 'name': v['name'], 'id': v['id'] } + + if 'next' not in json: + break + + url = json['next'] + + @authProjectRequired + def listImages(self): + url = self._getEndpointFor('image') + '/v2/images?status=active' + while True: + r = requests.get(url, headers=self._requestHeaders()) + + ensureResponseIsValid(r, 'List Images') + + json = r.json() + + for i in json['images']: + yield { 'name': i['name'], 'size': i['size'], 'visibility': i['visibility'], 'format': i['disk_format'] } + + if 'next' not in json: + break + + url = json['next'] + + @authProjectRequired + def listVolumes(self): + url = self._getEndpointFor('volumev2') + '/volumes' + + while True: + r = requests.get(url, headers=self._requestHeaders()) + + ensureResponseIsValid(r, 'List Volumes') + + json = r.json() + + for i in json['volumes']: + yield { 'id': i['id'], 'name': i['name'] } + + if 'next' not in json: + break + + url = json['next'] + + + def testConection(self): + # First, ensure requested api is supported + # We need api version 3.2 or greater + + r = requests.get(self._authUrl, + headers=self._requestHeaders()) + + for v in r.json()['versions']['values']: + if v['id'] >= 'v3.2': + # Tries to authenticate + try: + self.authPassword() + return True + except Exception: + return False + + return False