diff options
Diffstat (limited to 'quantumclient/common')
| -rw-r--r-- | quantumclient/common/__init__.py | 8 | ||||
| -rw-r--r-- | quantumclient/common/clientmanager.py | 87 | ||||
| -rw-r--r-- | quantumclient/common/command.py | 41 | ||||
| -rw-r--r-- | quantumclient/common/constants.py | 43 | ||||
| -rw-r--r-- | quantumclient/common/exceptions.py | 153 | ||||
| -rw-r--r-- | quantumclient/common/serializer.py | 410 | ||||
| -rw-r--r-- | quantumclient/common/utils.py | 188 |
7 files changed, 5 insertions, 925 deletions
diff --git a/quantumclient/common/__init__.py b/quantumclient/common/__init__.py index 1415c50..7e695ff 100644 --- a/quantumclient/common/__init__.py +++ b/quantumclient/common/__init__.py @@ -14,11 +14,3 @@ # License for the specific language governing permissions and limitations # under the License. # @author: Somik Behera, Nicira Networks, Inc. - -import gettext - -t = gettext.translation('quantumclient', fallback=True) - - -def _(msg): - return t.ugettext(msg) diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py deleted file mode 100644 index 4d219e4..0000000 --- a/quantumclient/common/clientmanager.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2012 OpenStack LLC. -# 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. -# -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -"""Manage access to the clients, including authenticating when needed. -""" - -import logging - -from quantumclient import client -from quantumclient.quantum import client as quantum_client - - -LOG = logging.getLogger(__name__) - - -class ClientCache(object): - """Descriptor class for caching created client handles. - """ - - def __init__(self, factory): - self.factory = factory - self._handle = None - - def __get__(self, instance, owner): - # Tell the ClientManager to login to keystone - if self._handle is None: - self._handle = self.factory(instance) - return self._handle - - -class ClientManager(object): - """Manages access to API clients, including authentication. - """ - quantum = ClientCache(quantum_client.make_client) - - def __init__(self, token=None, url=None, - auth_url=None, - endpoint_type=None, - tenant_name=None, tenant_id=None, - username=None, password=None, - region_name=None, - api_version=None, - auth_strategy=None, - insecure=False - ): - self._token = token - self._url = url - self._auth_url = auth_url - self._endpoint_type = endpoint_type - self._tenant_name = tenant_name - self._tenant_id = tenant_id - self._username = username - self._password = password - self._region_name = region_name - self._api_version = api_version - self._service_catalog = None - self._auth_strategy = auth_strategy - self._insecure = insecure - return - - def initialize(self): - if not self._url: - httpclient = client.HTTPClient(username=self._username, - tenant_name=self._tenant_name, - password=self._password, - region_name=self._region_name, - auth_url=self._auth_url, - endpoint_type=self._endpoint_type, - insecure=self._insecure) - httpclient.authenticate() - # Populate other password flow attributes - self._token = httpclient.auth_token - self._url = httpclient.endpoint_url diff --git a/quantumclient/common/command.py b/quantumclient/common/command.py deleted file mode 100644 index 7191436..0000000 --- a/quantumclient/common/command.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2012 OpenStack LLC. -# 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. -# -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -""" -OpenStack base command -""" - -from cliff import command - - -class OpenStackCommand(command.Command): - """Base class for OpenStack commands - """ - - api = None - - def run(self, parsed_args): - if not self.api: - return - else: - return super(OpenStackCommand, self).run(parsed_args) - - def get_data(self, parsed_args): - pass - - def take_action(self, parsed_args): - return self.get_data(parsed_args) diff --git a/quantumclient/common/constants.py b/quantumclient/common/constants.py deleted file mode 100644 index a8e8276..0000000 --- a/quantumclient/common/constants.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2012 OpenStack, LLC. -# -# 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. - - -EXT_NS = '_extension_ns' -XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0' -XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance" -XSI_ATTR = "xsi:nil" -XSI_NIL_ATTR = "xmlns:xsi" -TYPE_XMLNS = "xmlns:quantum" -TYPE_ATTR = "quantum:type" -VIRTUAL_ROOT_KEY = "_v_root" -ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" -ATOM_XMLNS = "xmlns:atom" -ATOM_LINK_NOTATION = "{%s}link" % ATOM_NAMESPACE - -TYPE_BOOL = "bool" -TYPE_INT = "int" -TYPE_LONG = "long" -TYPE_FLOAT = "float" -TYPE_LIST = "list" -TYPE_DICT = "dict" - -PLURALS = {'networks': 'network', - 'ports': 'port', - 'subnets': 'subnet', - 'dns_nameservers': 'dns_nameserver', - 'host_routes': 'host_route', - 'allocation_pools': 'allocation_pool', - 'fixed_ips': 'fixed_ip', - 'extensions': 'extension'} diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index ee33ef7..ab22951 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -15,155 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -from quantumclient.common import _ +from neutronclient.common.exceptions import * # noqa -""" -Quantum base exception handling. -""" - - -class QuantumException(Exception): - """Base Quantum Exception - - Taken from nova.exception.NovaException - To correctly use this class, inherit from it and define - a 'message' property. That message will get printf'd - with the keyword arguments provided to the constructor. - - """ - message = _("An unknown exception occurred.") - - def __init__(self, **kwargs): - try: - self._error_string = self.message % kwargs - - except Exception: - # at least get the core message out if something happened - self._error_string = self.message - - def __str__(self): - return self._error_string - - -class NotFound(QuantumException): - pass - - -class QuantumClientException(QuantumException): - - def __init__(self, **kwargs): - message = kwargs.get('message') - self.status_code = kwargs.get('status_code', 0) - if message: - self.message = message - super(QuantumClientException, self).__init__(**kwargs) - - -# NOTE: on the client side, we use different exception types in order -# to allow client library users to handle server exceptions in try...except -# blocks. The actual error message is the one generated on the server side -class NetworkNotFoundClient(QuantumClientException): - pass - - -class PortNotFoundClient(QuantumClientException): - pass - - -class MalformedResponseBody(QuantumException): - message = _("Malformed response body: %(reason)s") - - -class StateInvalidClient(QuantumClientException): - pass - - -class NetworkInUseClient(QuantumClientException): - pass - - -class PortInUseClient(QuantumClientException): - pass - - -class AlreadyAttachedClient(QuantumClientException): - pass - - -class Unauthorized(QuantumClientException): - message = _("Unauthorized: bad credentials.") - - -class Forbidden(QuantumClientException): - message = _("Forbidden: your credentials don't give you access to this " - "resource.") - - -class EndpointNotFound(QuantumClientException): - """Could not find Service or Region in Service Catalog.""" - message = _("Could not find Service or Region in Service Catalog.") - - -class EndpointTypeNotFound(QuantumClientException): - """Could not find endpoint type in Service Catalog.""" - - def __str__(self): - msg = "Could not find endpoint type %s in Service Catalog." - return msg % repr(self.message) - - -class AmbiguousEndpoints(QuantumClientException): - """Found more than one matching endpoint in Service Catalog.""" - - def __str__(self): - return "AmbiguousEndpoints: %s" % repr(self.message) - - -class QuantumCLIError(QuantumClientException): - """Exception raised when command line parsing fails.""" - pass - - -class RequestURITooLong(QuantumClientException): - """Raised when a request fails with HTTP error 414.""" - - def __init__(self, **kwargs): - self.excess = kwargs.get('excess', 0) - super(RequestURITooLong, self).__init__(**kwargs) - - -class ConnectionFailed(QuantumClientException): - message = _("Connection to quantum failed: %(reason)s") - - -class BadInputError(Exception): - """Error resulting from a client sending bad input to a server.""" - pass - - -class Error(Exception): - def __init__(self, message=None): - super(Error, self).__init__(message) - - -class MalformedRequestBody(QuantumException): - message = _("Malformed request body: %(reason)s") - - -class Invalid(Error): - pass - - -class InvalidContentType(Invalid): - message = _("Invalid content type %(content_type)s.") - - -class UnsupportedVersion(Exception): - """Indicates that the user is trying to use an unsupported - version of the API - """ - pass - - -class CommandError(Exception): - pass +QuantumException = NeutronException diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py deleted file mode 100644 index 9c595e4..0000000 --- a/quantumclient/common/serializer.py +++ /dev/null @@ -1,410 +0,0 @@ -# Copyright 2013 OpenStack LLC. -# 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. -# -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -### -### Codes from quantum wsgi -### - -import logging - -from xml.etree import ElementTree as etree -from xml.parsers import expat - -from quantumclient.common import constants -from quantumclient.common import exceptions as exception -from quantumclient.openstack.common.gettextutils import _ -from quantumclient.openstack.common import jsonutils - -LOG = logging.getLogger(__name__) - - -class ActionDispatcher(object): - """Maps method name to local methods through action name.""" - - def dispatch(self, *args, **kwargs): - """Find and call local method.""" - action = kwargs.pop('action', 'default') - action_method = getattr(self, str(action), self.default) - return action_method(*args, **kwargs) - - def default(self, data): - raise NotImplementedError() - - -class DictSerializer(ActionDispatcher): - """Default request body serialization.""" - - def serialize(self, data, action='default'): - return self.dispatch(data, action=action) - - def default(self, data): - return "" - - -class JSONDictSerializer(DictSerializer): - """Default JSON request body serialization.""" - - def default(self, data): - def sanitizer(obj): - return unicode(obj) - return jsonutils.dumps(data, default=sanitizer) - - -class XMLDictSerializer(DictSerializer): - - def __init__(self, metadata=None, xmlns=None): - """XMLDictSerializer constructor. - - :param metadata: information needed to deserialize xml into - a dictionary. - :param xmlns: XML namespace to include with serialized xml - """ - super(XMLDictSerializer, self).__init__() - self.metadata = metadata or {} - if not xmlns: - xmlns = self.metadata.get('xmlns') - if not xmlns: - xmlns = constants.XML_NS_V20 - self.xmlns = xmlns - - def default(self, data): - """Default serializer of XMLDictSerializer. - - :param data: expect data to contain a single key as XML root, or - contain another '*_links' key as atom links. Other - case will use 'VIRTUAL_ROOT_KEY' as XML root. - """ - try: - links = None - has_atom = False - if data is None: - root_key = constants.VIRTUAL_ROOT_KEY - root_value = None - else: - link_keys = [k for k in data.iterkeys() or [] - if k.endswith('_links')] - if link_keys: - links = data.pop(link_keys[0], None) - has_atom = True - root_key = (len(data) == 1 and - data.keys()[0] or constants.VIRTUAL_ROOT_KEY) - root_value = data.get(root_key, data) - doc = etree.Element("_temp_root") - used_prefixes = [] - self._to_xml_node(doc, self.metadata, root_key, - root_value, used_prefixes) - if links: - self._create_link_nodes(list(doc)[0], links) - return self.to_xml_string(list(doc)[0], used_prefixes, has_atom) - except AttributeError as e: - LOG.exception(str(e)) - return '' - - def __call__(self, data): - # Provides a migration path to a cleaner WSGI layer, this - # "default" stuff and extreme extensibility isn't being used - # like originally intended - return self.default(data) - - def to_xml_string(self, node, used_prefixes, has_atom=False): - self._add_xmlns(node, used_prefixes, has_atom) - return etree.tostring(node, encoding='UTF-8') - - #NOTE (ameade): the has_atom should be removed after all of the - # xml serializers and view builders have been updated to the current - # spec that required all responses include the xmlns:atom, the has_atom - # flag is to prevent current tests from breaking - def _add_xmlns(self, node, used_prefixes, has_atom=False): - node.set('xmlns', self.xmlns) - node.set(constants.TYPE_XMLNS, self.xmlns) - if has_atom: - node.set(constants.ATOM_XMLNS, constants.ATOM_NAMESPACE) - node.set(constants.XSI_NIL_ATTR, constants.XSI_NAMESPACE) - ext_ns = self.metadata.get(constants.EXT_NS, {}) - for prefix in used_prefixes: - if prefix in ext_ns: - node.set('xmlns:' + prefix, ext_ns[prefix]) - - def _to_xml_node(self, parent, metadata, nodename, data, used_prefixes): - """Recursive method to convert data members to XML nodes.""" - result = etree.SubElement(parent, nodename) - if ":" in nodename: - used_prefixes.append(nodename.split(":", 1)[0]) - #TODO(bcwaldon): accomplish this without a type-check - if isinstance(data, list): - if not data: - result.set( - constants.TYPE_ATTR, - constants.TYPE_LIST) - return result - singular = metadata.get('plurals', {}).get(nodename, None) - if singular is None: - if nodename.endswith('s'): - singular = nodename[:-1] - else: - singular = 'item' - for item in data: - self._to_xml_node(result, metadata, singular, item, - used_prefixes) - #TODO(bcwaldon): accomplish this without a type-check - elif isinstance(data, dict): - if not data: - result.set( - constants.TYPE_ATTR, - constants.TYPE_DICT) - return result - attrs = metadata.get('attributes', {}).get(nodename, {}) - for k, v in data.items(): - if k in attrs: - result.set(k, str(v)) - else: - self._to_xml_node(result, metadata, k, v, - used_prefixes) - elif data is None: - result.set(constants.XSI_ATTR, 'true') - else: - if isinstance(data, bool): - result.set( - constants.TYPE_ATTR, - constants.TYPE_BOOL) - elif isinstance(data, int): - result.set( - constants.TYPE_ATTR, - constants.TYPE_INT) - elif isinstance(data, long): - result.set( - constants.TYPE_ATTR, - constants.TYPE_LONG) - elif isinstance(data, float): - result.set( - constants.TYPE_ATTR, - constants.TYPE_FLOAT) - LOG.debug(_("Data %(data)s type is %(type)s"), - {'data': data, - 'type': type(data)}) - if isinstance(data, str): - result.text = unicode(data, 'utf-8') - else: - result.text = unicode(data) - return result - - def _create_link_nodes(self, xml_doc, links): - for link in links: - link_node = etree.SubElement(xml_doc, 'atom:link') - link_node.set('rel', link['rel']) - link_node.set('href', link['href']) - - -class TextDeserializer(ActionDispatcher): - """Default request body deserialization.""" - - def deserialize(self, datastring, action='default'): - return self.dispatch(datastring, action=action) - - def default(self, datastring): - return {} - - -class JSONDeserializer(TextDeserializer): - - def _from_json(self, datastring): - try: - return jsonutils.loads(datastring) - except ValueError: - msg = _("Cannot understand JSON") - raise exception.MalformedRequestBody(reason=msg) - - def default(self, datastring): - return {'body': self._from_json(datastring)} - - -class XMLDeserializer(TextDeserializer): - - def __init__(self, metadata=None): - """XMLDeserializer constructor. - - :param metadata: information needed to deserialize xml into - a dictionary. - """ - super(XMLDeserializer, self).__init__() - self.metadata = metadata or {} - xmlns = self.metadata.get('xmlns') - if not xmlns: - xmlns = constants.XML_NS_V20 - self.xmlns = xmlns - - def _get_key(self, tag): - tags = tag.split("}", 1) - if len(tags) == 2: - ns = tags[0][1:] - bare_tag = tags[1] - ext_ns = self.metadata.get(constants.EXT_NS, {}) - if ns == self.xmlns: - return bare_tag - for prefix, _ns in ext_ns.items(): - if ns == _ns: - return prefix + ":" + bare_tag - else: - return tag - - def _get_links(self, root_tag, node): - link_nodes = node.findall(constants.ATOM_LINK_NOTATION) - root_tag = self._get_key(node.tag) - link_key = "%s_links" % root_tag - link_list = [] - for link in link_nodes: - link_list.append({'rel': link.get('rel'), - 'href': link.get('href')}) - # Remove link node in order to avoid link node being - # processed as an item in _from_xml_node - node.remove(link) - return link_list and {link_key: link_list} or {} - - def _from_xml(self, datastring): - if datastring is None: - return None - plurals = set(self.metadata.get('plurals', {})) - try: - node = etree.fromstring(datastring) - root_tag = self._get_key(node.tag) - links = self._get_links(root_tag, node) - result = self._from_xml_node(node, plurals) - # There is no case where root_tag = constants.VIRTUAL_ROOT_KEY - # and links is not None because of the way data are serialized - if root_tag == constants.VIRTUAL_ROOT_KEY: - return result - return dict({root_tag: result}, **links) - except Exception as e: - parseError = False - # Python2.7 - if (hasattr(etree, 'ParseError') and - isinstance(e, getattr(etree, 'ParseError'))): - parseError = True - # Python2.6 - elif isinstance(e, expat.ExpatError): - parseError = True - if parseError: - msg = _("Cannot understand XML") - raise exception.MalformedRequestBody(reason=msg) - else: - raise - - def _from_xml_node(self, node, listnames): - """Convert a minidom node to a simple Python type. - - :param listnames: list of XML node names whose subnodes should - be considered list items. - - """ - attrNil = node.get(str(etree.QName(constants.XSI_NAMESPACE, "nil"))) - attrType = node.get(str(etree.QName( - self.metadata.get('xmlns'), "type"))) - if (attrNil and attrNil.lower() == 'true'): - return None - elif not len(node) and not node.text: - if (attrType and attrType == constants.TYPE_DICT): - return {} - elif (attrType and attrType == constants.TYPE_LIST): - return [] - else: - return '' - elif (len(node) == 0 and node.text): - converters = {constants.TYPE_BOOL: - lambda x: x.lower() == 'true', - constants.TYPE_INT: - lambda x: int(x), - constants.TYPE_LONG: - lambda x: long(x), - constants.TYPE_FLOAT: - lambda x: float(x)} - if attrType and attrType in converters: - return converters[attrType](node.text) - else: - return node.text - elif self._get_key(node.tag) in listnames: - return [self._from_xml_node(n, listnames) for n in node] - else: - result = dict() - for attr in node.keys(): - if (attr == 'xmlns' or - attr.startswith('xmlns:') or - attr == constants.XSI_ATTR or - attr == constants.TYPE_ATTR): - continue - result[self._get_key(attr)] = node.get(attr) - children = list(node) - for child in children: - result[self._get_key(child.tag)] = self._from_xml_node( - child, listnames) - return result - - def default(self, datastring): - return {'body': self._from_xml(datastring)} - - def __call__(self, datastring): - # Adding a migration path to allow us to remove unncessary classes - return self.default(datastring) - - -# NOTE(maru): this class is duplicated from quantum.wsgi -class Serializer(object): - """Serializes and deserializes dictionaries to certain MIME types.""" - - def __init__(self, metadata=None, default_xmlns=None): - """Create a serializer based on the given WSGI environment. - - 'metadata' is an optional dict mapping MIME types to information - needed to serialize a dictionary to that type. - - """ - self.metadata = metadata or {} - self.default_xmlns = default_xmlns - - def _get_serialize_handler(self, content_type): - handlers = { - 'application/json': JSONDictSerializer(), - 'application/xml': XMLDictSerializer(self.metadata), - } - - try: - return handlers[content_type] - except Exception: - raise exception.InvalidContentType(content_type=content_type) - - def serialize(self, data, content_type): - """Serialize a dictionary into the specified content type.""" - return self._get_serialize_handler(content_type).serialize(data) - - def deserialize(self, datastring, content_type): - """Deserialize a string to a dictionary. - - The string must be in the format of a supported MIME type. - - """ - return self.get_deserialize_handler(content_type).deserialize( - datastring) - - def get_deserialize_handler(self, content_type): - handlers = { - 'application/json': JSONDeserializer(), - 'application/xml': XMLDeserializer(self.metadata), - } - - try: - return handlers[content_type] - except Exception: - raise exception.InvalidContentType(content_type=content_type) diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py index 192b46f..01a0442 100644 --- a/quantumclient/common/utils.py +++ b/quantumclient/common/utils.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011, Nicira Networks, Inc. +# Copyright 2011 Nicira Networks, Inc +# 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 @@ -13,188 +14,5 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# -# Borrowed from nova code base, more utilities will be added/borrowed as and -# when needed. -# @author: Somik Behera, Nicira Networks, Inc. - -"""Utilities and helper functions.""" - -import datetime -import json -import logging -import os -import sys - -from quantumclient.common import exceptions -from quantumclient.openstack.common import strutils - - -def env(*vars, **kwargs): - """Returns the first environment variable set. - - if none are non-empty, defaults to '' or keyword arg default. - """ - for v in vars: - value = os.environ.get(v) - if value: - return value - return kwargs.get('default', '') - - -def to_primitive(value): - if isinstance(value, list) or isinstance(value, tuple): - o = [] - for v in value: - o.append(to_primitive(v)) - return o - elif isinstance(value, dict): - o = {} - for k, v in value.iteritems(): - o[k] = to_primitive(v) - return o - elif isinstance(value, datetime.datetime): - return str(value) - elif hasattr(value, 'iteritems'): - return to_primitive(dict(value.iteritems())) - elif hasattr(value, '__iter__'): - return to_primitive(list(value)) - else: - return value - - -def dumps(value, indent=None): - try: - return json.dumps(value, indent=indent) - except TypeError: - pass - return json.dumps(to_primitive(value)) - - -def loads(s): - return json.loads(s) - - -def import_class(import_str): - """Returns a class from a string including module and class. - - :param import_str: a string representation of the class name - :rtype: the requested class - """ - mod_str, _sep, class_str = import_str.rpartition('.') - __import__(mod_str) - return getattr(sys.modules[mod_str], class_str) - - -def get_client_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = "Invalid %s client version '%s'. must be one of: %s" % ( - (api_name, version, ', '.join(version_map.keys()))) - raise exceptions.UnsupportedVersion(msg) - - return import_class(client_path) - - -def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): - """Return a tuple containing the item properties. - - :param item: a single item resource (e.g. Server, Tenant, etc) - :param fields: tuple of strings with the desired field names - :param mixed_case_fields: tuple of field names to preserve case - :param formatters: dictionary mapping field names to callables - to format the values - """ - row = [] - - for field in fields: - if field in formatters: - row.append(formatters[field](item)) - else: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - if not hasattr(item, field_name) and isinstance(item, dict): - data = item[field_name] - else: - data = getattr(item, field_name, '') - if data is None: - data = '' - row.append(data) - return tuple(row) - - -def str2bool(strbool): - if strbool is None: - return None - else: - return strbool.lower() == 'true' - - -def str2dict(strdict): - '''Convert key1=value1,key2=value2,... string into dictionary. - - :param strdict: key1=value1,key2=value2 - ''' - _info = {} - for kv_str in strdict.split(","): - k, v = kv_str.split("=", 1) - _info.update({k: v}) - return _info - - -def http_log_req(_logger, args, kwargs): - if not _logger.isEnabledFor(logging.DEBUG): - return - - string_parts = ['curl -i'] - for element in args: - if element in ('GET', 'POST', 'DELETE', 'PUT'): - string_parts.append(' -X %s' % element) - else: - string_parts.append(' %s' % element) - - for element in kwargs['headers']: - header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) - string_parts.append(header) - - if 'body' in kwargs and kwargs['body']: - string_parts.append(" -d '%s'" % (kwargs['body'])) - string_parts = safe_encode_list(string_parts) - _logger.debug("\nREQ: %s\n" % "".join(string_parts)) - - -def http_log_resp(_logger, resp, body): - if not _logger.isEnabledFor(logging.DEBUG): - return - _logger.debug("RESP:%s %s\n", resp, body) - - -def _safe_encode_without_obj(data): - if isinstance(data, basestring): - return strutils.safe_encode(data) - return data - - -def safe_encode_list(data): - return map(_safe_encode_without_obj, data) - - -def safe_encode_dict(data): - def _encode_item((k, v)): - if isinstance(v, list): - return (k, safe_encode_list(v)) - elif isinstance(v, dict): - return (k, safe_encode_dict(v)) - return (k, _safe_encode_without_obj(v)) - return dict(map(_encode_item, data.items())) +from neutronclient.common.utils import * # noqa |
