diff --git a/troveclient/auth.py b/troveclient/auth.py deleted file mode 100644 index d2f69994..00000000 --- a/troveclient/auth.py +++ /dev/null @@ -1,255 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# -# 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. - -from __future__ import print_function -from troveclient import exceptions - -import six - - -def get_authenticator_cls(cls_or_name): - """Factory method to retrieve Authenticator class.""" - if isinstance(cls_or_name, type): - return cls_or_name - elif isinstance(cls_or_name, six.string_types): - if cls_or_name == "keystone": - return KeyStoneV2Authenticator - elif cls_or_name == "rax": - return RaxAuthenticator - elif cls_or_name == "auth1.1": - return Auth1_1 - elif cls_or_name == "fake": - return FakeAuth - - raise ValueError("Could not determine authenticator class from the given " - "value %r." % cls_or_name) - - -class Authenticator(object): - """ - Helper class to perform Keystone or other miscellaneous authentication. - - The "authenticate" method returns a ServiceCatalog, which can be used - to obtain a token. - - """ - - URL_REQUIRED = True - - def __init__(self, client, type, url, username, password, tenant, - region=None, service_type=None, service_name=None, - service_url=None): - self.client = client - self.type = type - self.url = url - self.username = username - self.password = password - self.tenant = tenant - self.region = region - self.service_type = service_type - self.service_name = service_name - self.service_url = service_url - - def _authenticate(self, url, body, root_key='access'): - """Authenticate and extract the service catalog.""" - # Make sure we follow redirects when trying to reach Keystone - tmp_follow_all_redirects = self.client.follow_all_redirects - self.client.follow_all_redirects = True - - try: - resp, body = self.client._time_request(url, "POST", body=body) - finally: - self.client.follow_all_redirects = tmp_follow_all_redirects - - if resp.status == 200: # content must always present - try: - return ServiceCatalog(body, region=self.region, - service_type=self.service_type, - service_name=self.service_name, - service_url=self.service_url, - root_key=root_key) - except exceptions.AmbiguousEndpoints: - print("Found more than one valid endpoint. Use a more " - "restrictive filter") - raise - except KeyError: - raise exceptions.AuthorizationFailure() - except exceptions.EndpointNotFound: - print("Could not find any suitable endpoint. Correct region?") - raise - - elif resp.status == 305: - return resp['location'] - else: - raise exceptions.from_response(resp, body) - - def authenticate(self): - raise NotImplementedError("Missing authenticate method.") - - -class KeyStoneV2Authenticator(Authenticator): - def authenticate(self): - if self.url is None: - raise exceptions.AuthUrlNotGiven() - return self._v2_auth(self.url) - - def _v2_auth(self, url): - """Authenticate against a v2.0 auth service.""" - body = {"auth": { - "passwordCredentials": { - "username": self.username, - "password": self.password} - } - } - - if self.tenant: - body['auth']['tenantName'] = self.tenant - - return self._authenticate(url, body) - - -class Auth1_1(Authenticator): - def authenticate(self): - """Authenticate against a v2.0 auth service.""" - if self.url is None: - raise exceptions.AuthUrlNotGiven() - auth_url = self.url - body = { - "credentials": { - "username": self.username, - "key": self.password - }} - return self._authenticate(auth_url, body, root_key='auth') - - -class RaxAuthenticator(Authenticator): - def authenticate(self): - if self.url is None: - raise exceptions.AuthUrlNotGiven() - return self._rax_auth(self.url) - - def _rax_auth(self, url): - """Authenticate against the Rackspace auth service.""" - body = {'auth': { - 'RAX-KSKEY:apiKeyCredentials': { - 'username': self.username, - 'apiKey': self.password, - 'tenantName': self.tenant} - } - } - - return self._authenticate(self.url, body) - - -class FakeAuth(Authenticator): - """Useful for faking auth.""" - - def authenticate(self): - class FakeCatalog(object): - def __init__(self, auth): - self.auth = auth - - def get_public_url(self): - return "%s/%s" % ('http://localhost:8779/v1.0', - self.auth.tenant) - - def get_token(self): - return self.auth.tenant - - return FakeCatalog(self) - - -class ServiceCatalog(object): - """Represents a Keystone Service Catalog which describes a service. - - This class has methods to obtain a valid token as well as a public service - url and a management url. - - """ - - def __init__(self, resource_dict, region=None, service_type=None, - service_name=None, service_url=None, root_key='access'): - self.catalog = resource_dict - self.region = region - self.service_type = service_type - self.service_name = service_name - self.service_url = service_url - self.management_url = None - self.public_url = None - self.root_key = root_key - self._load() - - def _load(self): - if not self.service_url: - self.public_url = self._url_for(attr='region', - filter_value=self.region, - endpoint_type="publicURL") - self.management_url = self._url_for(attr='region', - filter_value=self.region, - endpoint_type="adminURL") - else: - self.public_url = self.service_url - self.management_url = self.service_url - - def get_token(self): - return self.catalog[self.root_key]['token']['id'] - - def get_management_url(self): - return self.management_url - - def get_public_url(self): - return self.public_url - - def _url_for(self, attr=None, filter_value=None, - endpoint_type='publicURL'): - """ - Fetch the public URL from the Trove service for a particular - endpoint attribute. If none given, return the first. - """ - matching_endpoints = [] - if 'endpoints' in self.catalog: - # We have a bastardized service catalog. Treat it special. :/ - for endpoint in self.catalog['endpoints']: - if not filter_value or endpoint[attr] == filter_value: - matching_endpoints.append(endpoint) - if not matching_endpoints: - raise exceptions.EndpointNotFound() - - # We don't always get a service catalog back ... - if 'serviceCatalog' not in self.catalog[self.root_key]: - raise exceptions.EndpointNotFound() - - # Full catalog ... - catalog = self.catalog[self.root_key]['serviceCatalog'] - - for service in catalog: - if service.get("type") != self.service_type: - continue - - if (self.service_name and self.service_type == 'database' and - service.get('name') != self.service_name): - continue - - endpoints = service['endpoints'] - for endpoint in endpoints: - if not filter_value or endpoint.get(attr) == filter_value: - endpoint["serviceName"] = service.get("name") - matching_endpoints.append(endpoint) - - if not matching_endpoints: - raise exceptions.EndpointNotFound() - elif len(matching_endpoints) > 1: - raise exceptions.AmbiguousEndpoints(endpoints=matching_endpoints) - else: - return matching_endpoints[0].get(endpoint_type, None) diff --git a/troveclient/compat/auth.py b/troveclient/compat/auth.py index f5bfdbdb..7884d5dc 100644 --- a/troveclient/compat/auth.py +++ b/troveclient/compat/auth.py @@ -13,6 +13,7 @@ # under the License. from __future__ import print_function +from six import string_types from troveclient.compat import exceptions @@ -20,7 +21,7 @@ def get_authenticator_cls(cls_or_name): """Factory method to retrieve Authenticator class.""" if isinstance(cls_or_name, type): return cls_or_name - elif isinstance(cls_or_name, basestring): + elif isinstance(cls_or_name, string_types): if cls_or_name == "keystone": return KeyStoneV2Authenticator elif cls_or_name == "rax": diff --git a/troveclient/tests/test_auth.py b/troveclient/compat/tests/test_auth.py similarity index 99% rename from troveclient/tests/test_auth.py rename to troveclient/compat/tests/test_auth.py index f57f8ea9..cb3b6bc2 100644 --- a/troveclient/tests/test_auth.py +++ b/troveclient/compat/tests/test_auth.py @@ -18,10 +18,10 @@ # under the License. from testtools import TestCase -from troveclient import auth +from troveclient.compat import auth from mock import Mock -from troveclient import exceptions +from troveclient.compat import exceptions """ Unit tests for the classes and functions in auth.py. diff --git a/troveclient/xml.py b/troveclient/xml.py deleted file mode 100644 index 85e30a1a..00000000 --- a/troveclient/xml.py +++ /dev/null @@ -1,292 +0,0 @@ -from lxml import etree -from numbers import Number - -from troveclient import exceptions -from troveclient.client import TroveHTTPClient - -XML_NS = {None: "http://docs.openstack.org/database/api/v1.0"} - -# If XML element is listed here then this searches through the ancestors. -LISTIFY = { - "accounts": [[]], - "databases": [[]], - "flavors": [[]], - "instances": [[]], - "links": [[]], - "hosts": [[]], - "devices": [[]], - "users": [[]], - "versions": [[]], - "attachments": [[]], - "limits": [[]], - "security_groups": [[]], - "backups": [[]] -} - - -class IntDict(object): - pass - - -TYPE_MAP = { - "instance": { - "volume": { - "used": float, - "size": int, - }, - "deleted": bool, - "server": { - "local_id": int, - "deleted": bool, - }, - }, - "instances": { - "deleted": bool, - }, - "deleted": bool, - "flavor": { - "ram": int, - }, - "diagnostics": { - "vmHwm": int, - "vmPeak": int, - "vmSize": int, - "threads": int, - "vmRss": int, - "fdSize": int, - }, - "security_group_rule": { - "from_port": int, - "to_port": int, - }, - "quotas": IntDict, -} -TYPE_MAP["flavors"] = TYPE_MAP["flavor"] - -REQUEST_AS_LIST = set(['databases', 'users']) - - -def element_ancestors_match_list(element, list): - """ - For element root at matches against - list ["blah", "foo"]. - """ - itr_elem = element.getparent() - for name in list: - if itr_elem is None: - break - if name != normalize_tag(itr_elem): - return False - itr_elem = itr_elem.getparent() - return True - - -def element_must_be_list(parent_element, name): - """Determines if an element to be created should be a dict or list.""" - if name in LISTIFY: - list_of_lists = LISTIFY[name] - for tag_list in list_of_lists: - if element_ancestors_match_list(parent_element, tag_list): - return True - return False - - -def element_to_json(name, element): - if element_must_be_list(element, name): - return element_to_list(element) - else: - return element_to_dict(element) - - -def root_element_to_json(name, element): - """Returns a tuple of the root JSON value, plus the links if found.""" - if name == "rootEnabled": # Why oh why were we inconsistent here? :'( - if element.text.strip() == "False": - return False, None - elif element.text.strip() == "True": - return True, None - if element_must_be_list(element, name): - return element_to_list(element, True) - else: - return element_to_dict(element), None - - -def element_to_list(element, check_for_links=False): - """ - For element "foo" in - Returns [{}, {}] - """ - links = None - result = [] - for child_element in element: - # The "links" element gets jammed into the root element. - if check_for_links and normalize_tag(child_element) == "links": - links = element_to_list(child_element) - else: - result.append(element_to_dict(child_element)) - if check_for_links: - return result, links - else: - return result - - -def element_to_dict(element): - result = {} - for name, value in element.items(): - result[name] = value - for child_element in element: - name = normalize_tag(child_element) - result[name] = element_to_json(name, child_element) - if len(result) == 0 and element.text: - string_value = element.text.strip() - if len(string_value): - if string_value == 'None': - return None - return string_value - return result - - -def standardize_json_lists(json_dict): - """ - In XML, we might see something like {'instances':{'instances':[...]}}, - which we must change to just {'instances':[...]} to be compatable with - the true JSON format. - - If any items are dictionaries with only one item which is a list, - simply remove the dictionary and insert its list directly. - """ - found_items = [] - for key, value in json_dict.items(): - value = json_dict[key] - if isinstance(value, dict): - if len(value) == 1 and isinstance(value.values()[0], list): - found_items.append(key) - else: - standardize_json_lists(value) - for key in found_items: - json_dict[key] = json_dict[key].values()[0] - - -def normalize_tag(elem): - """Given an element, returns the tag minus the XMLNS junk. - - IOW, .tag may sometimes return the XML namespace at the start of the - string. This gets rids of that. - """ - try: - prefix = "{" + elem.nsmap[None] + "}" - if elem.tag.startswith(prefix): - return elem.tag[len(prefix):] - except KeyError: - pass - return elem.tag - - -def create_root_xml_element(name, value): - """Create the first element using a name and a dictionary.""" - element = etree.Element(name, nsmap=XML_NS) - if name in REQUEST_AS_LIST: - add_subelements_from_list(element, name, value) - else: - populate_element_from_dict(element, value) - return element - - -def create_subelement(parent_element, name, value): - """Attaches a new element onto the parent element.""" - if isinstance(value, dict): - create_subelement_from_dict(parent_element, name, value) - elif isinstance(value, list): - create_subelement_from_list(parent_element, name, value) - else: - raise TypeError("Can't handle type %s." % type(value)) - - -def create_subelement_from_dict(parent_element, name, dict): - element = etree.SubElement(parent_element, name) - populate_element_from_dict(element, dict) - - -def create_subelement_from_list(parent_element, name, list): - element = etree.SubElement(parent_element, name) - add_subelements_from_list(element, name, list) - - -def add_subelements_from_list(element, name, list): - if name.endswith("s"): - item_name = name[:len(name) - 1] - else: - item_name = name - for item in list: - create_subelement(element, item_name, item) - - -def populate_element_from_dict(element, dict): - for key, value in dict.items(): - if isinstance(value, basestring): - element.set(key, value) - elif isinstance(value, Number): - element.set(key, str(value)) - elif isinstance(value, None.__class__): - element.set(key, '') - else: - create_subelement(element, key, value) - - -def modify_response_types(value, type_translator): - """ - This will convert some string in response dictionary to ints or bool - so that our respose is compatiable with code expecting JSON style responses - """ - if isinstance(value, str): - if value == 'True': - return True - elif value == 'False': - return False - else: - return type_translator(value) - elif isinstance(value, dict): - for k, v in value.iteritems(): - if type_translator is not IntDict: - if v.__class__ is dict and v.__len__() == 0: - value[k] = None - elif k in type_translator: - value[k] = modify_response_types(value[k], - type_translator[k]) - else: - value[k] = int(value[k]) - return value - elif isinstance(value, list): - return [modify_response_types(element, type_translator) - for element in value] - - -class TroveXmlClient(TroveHTTPClient): - - @classmethod - def morph_request(self, kwargs): - kwargs['headers']['Accept'] = 'application/xml' - kwargs['headers']['Content-Type'] = 'application/xml' - if 'body' in kwargs: - body = kwargs['body'] - root_name = body.keys()[0] - xml = create_root_xml_element(root_name, body[root_name]) - xml_string = etree.tostring(xml, pretty_print=True) - kwargs['body'] = xml_string - - @classmethod - def morph_response_body(self, body_string): - # The root XML element always becomes a dictionary with a single - # field, which has the same key as the elements name. - result = {} - try: - root_element = etree.XML(body_string) - except etree.XMLSyntaxError: - raise exceptions.ResponseFormatError() - root_name = normalize_tag(root_element) - root_value, links = root_element_to_json(root_name, root_element) - result = {root_name: root_value} - if links: - result['links'] = links - modify_response_types(result, TYPE_MAP) - return result