209 lines
8.0 KiB
Python
209 lines
8.0 KiB
Python
# Copyright 2010 OpenStack Foundation
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import logging
|
|
|
|
from debtcollector import removals
|
|
from six.moves.urllib import parse as urlparse
|
|
|
|
from keystoneclient import exceptions
|
|
from keystoneclient import httpclient
|
|
from keystoneclient.i18n import _
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
# NOTE(jamielennox): To be removed after Pike.
|
|
@removals.removed_class('keystoneclient.generic.client.Client',
|
|
message='Use keystoneauth discovery',
|
|
version='3.9.0',
|
|
removal_version='4.0.0')
|
|
class Client(httpclient.HTTPClient):
|
|
"""Client for the OpenStack Keystone pre-version calls API.
|
|
|
|
:param string endpoint: A user-supplied endpoint URL for the keystone
|
|
service.
|
|
:param integer timeout: Allows customization of the timeout for client
|
|
http requests. (optional)
|
|
|
|
Example::
|
|
|
|
>>> from keystoneclient.generic import client
|
|
>>> root = client.Client(auth_url=KEYSTONE_URL)
|
|
>>> versions = root.discover()
|
|
...
|
|
>>> from keystoneclient.v2_0 import client as v2client
|
|
>>> keystone = v2client.Client(auth_url=versions['v2.0']['url'])
|
|
...
|
|
>>> user = keystone.users.get(USER_ID)
|
|
>>> user.delete()
|
|
|
|
"""
|
|
|
|
def __init__(self, endpoint=None, **kwargs):
|
|
"""Initialize a new client for the Keystone v2.0 API."""
|
|
super(Client, self).__init__(endpoint=endpoint, **kwargs)
|
|
self.endpoint = endpoint
|
|
|
|
def discover(self, url=None):
|
|
"""Discover Keystone servers and return API versions supported.
|
|
|
|
:param url: optional url to test (without version)
|
|
|
|
Returns::
|
|
|
|
{
|
|
'message': 'Keystone found at http://127.0.0.1:5000/',
|
|
'v2.0': {
|
|
'status': 'beta',
|
|
'url': 'http://127.0.0.1:5000/v2.0/',
|
|
'id': 'v2.0'
|
|
},
|
|
}
|
|
|
|
"""
|
|
if url:
|
|
return self._check_keystone_versions(url)
|
|
else:
|
|
return self._local_keystone_exists()
|
|
|
|
def _local_keystone_exists(self):
|
|
"""Check if Keystone is available on default local port 35357."""
|
|
results = self._check_keystone_versions("http://localhost:35357")
|
|
if results is None:
|
|
results = self._check_keystone_versions("https://localhost:35357")
|
|
return results
|
|
|
|
def _check_keystone_versions(self, url):
|
|
"""Call Keystone URL and detects the available API versions."""
|
|
try:
|
|
resp, body = self._request(url, "GET",
|
|
headers={'Accept':
|
|
'application/json'})
|
|
# Multiple Choices status code is returned by the root
|
|
# identity endpoint, with references to one or more
|
|
# Identity API versions -- v3 spec
|
|
# some cases we get No Content
|
|
if resp.status_code in (200, 204, 300):
|
|
try:
|
|
results = {}
|
|
if 'version' in body:
|
|
results['message'] = _("Keystone found at %s") % url
|
|
version = body['version']
|
|
# Stable/diablo incorrect format
|
|
id, status, version_url = (
|
|
self._get_version_info(version, url))
|
|
results[str(id)] = {"id": id,
|
|
"status": status,
|
|
"url": version_url}
|
|
return results
|
|
elif 'versions' in body:
|
|
# Correct format
|
|
results['message'] = _("Keystone found at %s") % url
|
|
for version in body['versions']['values']:
|
|
id, status, version_url = (
|
|
self._get_version_info(version, url))
|
|
results[str(id)] = {"id": id,
|
|
"status": status,
|
|
"url": version_url}
|
|
return results
|
|
else:
|
|
results['message'] = (
|
|
_("Unrecognized response from %s") % url)
|
|
return results
|
|
except KeyError:
|
|
raise exceptions.AuthorizationFailure()
|
|
elif resp.status_code == 305:
|
|
return self._check_keystone_versions(resp['location'])
|
|
else:
|
|
raise exceptions.from_response(resp, "GET", url)
|
|
except Exception:
|
|
_logger.exception('Failed to detect available versions.')
|
|
|
|
def discover_extensions(self, url=None):
|
|
"""Discover Keystone extensions supported.
|
|
|
|
:param url: optional url to test (should have a version in it)
|
|
|
|
Returns::
|
|
|
|
{
|
|
'message': 'Keystone extensions at http://127.0.0.1:35357/v2',
|
|
'OS-KSEC2': 'OpenStack EC2 Credentials Extension',
|
|
}
|
|
|
|
"""
|
|
if url:
|
|
return self._check_keystone_extensions(url)
|
|
|
|
def _check_keystone_extensions(self, url):
|
|
"""Call Keystone URL and detects the available extensions."""
|
|
try:
|
|
if not url.endswith("/"):
|
|
url += '/'
|
|
resp, body = self._request("%sextensions" % url, "GET",
|
|
headers={'Accept':
|
|
'application/json'})
|
|
if resp.status_code in (200, 204): # some cases we get No Content
|
|
if 'extensions' in body and 'values' in body['extensions']:
|
|
# Parse correct format (per contract)
|
|
extensions = body['extensions']['values']
|
|
elif 'extensions' in body:
|
|
# Support incorrect, but prevalent format
|
|
extensions = body['extensions']
|
|
else:
|
|
return dict(message=(
|
|
_('Unrecognized extensions response from %s') % url))
|
|
|
|
return dict(self._get_extension_info(e) for e in extensions)
|
|
elif resp.status_code == 305:
|
|
return self._check_keystone_extensions(resp['location'])
|
|
else:
|
|
raise exceptions.from_response(
|
|
resp, "GET", "%sextensions" % url)
|
|
except Exception:
|
|
_logger.exception('Failed to check keystone extensions.')
|
|
|
|
@staticmethod
|
|
def _get_version_info(version, root_url):
|
|
"""Parse version information.
|
|
|
|
:param version: a dict of a Keystone version response
|
|
:param root_url: string url used to construct
|
|
the version if no URL is provided.
|
|
:returns: tuple - (verionId, versionStatus, versionUrl)
|
|
"""
|
|
id = version['id']
|
|
status = version['status']
|
|
ref = urlparse.urljoin(root_url, id)
|
|
if 'links' in version:
|
|
for link in version['links']:
|
|
if link['rel'] == 'self':
|
|
ref = link['href']
|
|
break
|
|
return (id, status, ref)
|
|
|
|
@staticmethod
|
|
def _get_extension_info(extension):
|
|
"""Parse extension information.
|
|
|
|
:param extension: a dict of a Keystone extension response
|
|
:returns: tuple - (alias, name)
|
|
"""
|
|
alias = extension['alias']
|
|
name = extension['name']
|
|
return (alias, name)
|