Removing unused files with new client
fixes bug 1243452 Change-Id: Ia51e6d48e486baa818ebe3108d37bb1de537702d
This commit is contained in:
@@ -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)
|
@@ -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":
|
||||
|
@@ -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.
|
@@ -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 <foo><blah><root></blah></foo> 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 <foos><foo/><foo/></foos>
|
||||
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
|
Reference in New Issue
Block a user