summaryrefslogtreecommitdiff
path: root/neutronclient/common
diff options
context:
space:
mode:
Diffstat (limited to 'neutronclient/common')
-rw-r--r--neutronclient/common/__init__.py24
-rw-r--r--neutronclient/common/clientmanager.py87
-rw-r--r--neutronclient/common/command.py41
-rw-r--r--neutronclient/common/constants.py43
-rw-r--r--neutronclient/common/exceptions.py169
-rw-r--r--neutronclient/common/serializer.py410
-rw-r--r--neutronclient/common/utils.py200
7 files changed, 974 insertions, 0 deletions
diff --git a/neutronclient/common/__init__.py b/neutronclient/common/__init__.py
new file mode 100644
index 0000000..47ca708
--- /dev/null
+++ b/neutronclient/common/__init__.py
@@ -0,0 +1,24 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# 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
+# 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.
+# @author: Somik Behera, Nicira Networks, Inc.
+
+import gettext
+
+t = gettext.translation('neutronclient', fallback=True)
+
+
+def _(msg):
+ return t.ugettext(msg)
diff --git a/neutronclient/common/clientmanager.py b/neutronclient/common/clientmanager.py
new file mode 100644
index 0000000..8e0614d
--- /dev/null
+++ b/neutronclient/common/clientmanager.py
@@ -0,0 +1,87 @@
+# 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 neutronclient import client
+from neutronclient.neutron import client as neutron_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.
+ """
+ neutron = ClientCache(neutron_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/neutronclient/common/command.py b/neutronclient/common/command.py
new file mode 100644
index 0000000..7191436
--- /dev/null
+++ b/neutronclient/common/command.py
@@ -0,0 +1,41 @@
+# 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/neutronclient/common/constants.py b/neutronclient/common/constants.py
new file mode 100644
index 0000000..a8e8276
--- /dev/null
+++ b/neutronclient/common/constants.py
@@ -0,0 +1,43 @@
+# 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/neutronclient/common/exceptions.py b/neutronclient/common/exceptions.py
new file mode 100644
index 0000000..e02be30
--- /dev/null
+++ b/neutronclient/common/exceptions.py
@@ -0,0 +1,169 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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
+# 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 neutronclient.common import _
+
+"""
+Neutron base exception handling.
+"""
+
+
+class NeutronException(Exception):
+ """Base Neutron 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(NeutronException):
+ pass
+
+
+class NeutronClientException(NeutronException):
+
+ def __init__(self, **kwargs):
+ message = kwargs.get('message')
+ self.status_code = kwargs.get('status_code', 0)
+ if message:
+ self.message = message
+ super(NeutronClientException, 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(NeutronClientException):
+ pass
+
+
+class PortNotFoundClient(NeutronClientException):
+ pass
+
+
+class MalformedResponseBody(NeutronException):
+ message = _("Malformed response body: %(reason)s")
+
+
+class StateInvalidClient(NeutronClientException):
+ pass
+
+
+class NetworkInUseClient(NeutronClientException):
+ pass
+
+
+class PortInUseClient(NeutronClientException):
+ pass
+
+
+class AlreadyAttachedClient(NeutronClientException):
+ pass
+
+
+class Unauthorized(NeutronClientException):
+ message = _("Unauthorized: bad credentials.")
+
+
+class Forbidden(NeutronClientException):
+ message = _("Forbidden: your credentials don't give you access to this "
+ "resource.")
+
+
+class EndpointNotFound(NeutronClientException):
+ """Could not find Service or Region in Service Catalog."""
+ message = _("Could not find Service or Region in Service Catalog.")
+
+
+class EndpointTypeNotFound(NeutronClientException):
+ """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(NeutronClientException):
+ """Found more than one matching endpoint in Service Catalog."""
+
+ def __str__(self):
+ return "AmbiguousEndpoints: %s" % repr(self.message)
+
+
+class NeutronCLIError(NeutronClientException):
+ """Exception raised when command line parsing fails."""
+ pass
+
+
+class RequestURITooLong(NeutronClientException):
+ """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(NeutronClientException):
+ message = _("Connection to neutron 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(NeutronException):
+ 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
diff --git a/neutronclient/common/serializer.py b/neutronclient/common/serializer.py
new file mode 100644
index 0000000..b8f1c19
--- /dev/null
+++ b/neutronclient/common/serializer.py
@@ -0,0 +1,410 @@
+# 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 neutron wsgi
+###
+
+import logging
+
+from xml.etree import ElementTree as etree
+from xml.parsers import expat
+
+from neutronclient.common import constants
+from neutronclient.common import exceptions as exception
+from neutronclient.openstack.common.gettextutils import _
+from neutronclient.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 neutron.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/neutronclient/common/utils.py b/neutronclient/common/utils.py
new file mode 100644
index 0000000..ca2e6ec
--- /dev/null
+++ b/neutronclient/common/utils.py
@@ -0,0 +1,200 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011, Nicira Networks, Inc.
+#
+# 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.
+#
+# 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 neutronclient.common import exceptions
+from neutronclient.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()))