Remove Limited XML API Support from Manila

Remove the broken XML support presently available in Manila
in order to comply with the general direction among other
OpenStack projects to support JSON only.

Change-Id: Ibb542fa223f7f7d1bf95f3d1f568987ef839cd12
Closes-Bug: #1440782
This commit is contained in:
Igor Malinovskiy 2015-04-13 15:43:47 +03:00
parent cdcf2e7671
commit a26824818f
39 changed files with 21 additions and 3180 deletions

View File

@ -27,5 +27,5 @@ class ExtensionsTest(base.BaseSharesTest):
# verify response
self.assertIn(int(resp["status"]), self.HTTP_SUCCESS)
keys = ["alias", "updated", "namespace", "name", "description"]
keys = ["alias", "updated", "name", "description"]
[self.assertIn(key, ext.keys()) for ext in extensions for key in keys]

View File

@ -22,10 +22,7 @@ import six
from six.moves.urllib import parse
import webob
from manila.api.openstack import wsgi
from manila.api import xmlutil
from manila.i18n import _
from manila import utils
api_common_opts = [
cfg.IntOpt(
@ -41,7 +38,6 @@ api_common_opts = [
CONF = cfg.CONF
CONF.register_opts(api_common_opts)
LOG = log.getLogger(__name__)
XML_NS_V1 = 'http://docs.openstack.org/volume/api/v1'
# Regex that matches alphanumeric characters, periods, hypens,
@ -267,78 +263,6 @@ class ViewBuilder(object):
return parse.urlunsplit(url_parts)
class MetadataDeserializer(wsgi.MetadataXMLDeserializer):
def deserialize(self, text):
dom = utils.safe_minidom_parse_string(text)
metadata_node = self.find_first_child_named(dom, "metadata")
metadata = self.extract_metadata(metadata_node)
return {'body': {'metadata': metadata}}
class MetaItemDeserializer(wsgi.MetadataXMLDeserializer):
def deserialize(self, text):
dom = utils.safe_minidom_parse_string(text)
metadata_item = self.extract_metadata(dom)
return {'body': {'meta': metadata_item}}
class MetadataXMLDeserializer(wsgi.XMLDeserializer):
def extract_metadata(self, metadata_node):
"""Marshal the metadata attribute of a parsed request."""
if metadata_node is None:
return {}
metadata = {}
for meta_node in self.find_children_named(metadata_node, "meta"):
key = meta_node.getAttribute("key")
metadata[key] = self.extract_text(meta_node)
return metadata
def _extract_metadata_container(self, datastring):
dom = utils.safe_minidom_parse_string(datastring)
metadata_node = self.find_first_child_named(dom, "metadata")
metadata = self.extract_metadata(metadata_node)
return {'body': {'metadata': metadata}}
def create(self, datastring):
return self._extract_metadata_container(datastring)
def update_all(self, datastring):
return self._extract_metadata_container(datastring)
def update(self, datastring):
dom = utils.safe_minidom_parse_string(datastring)
metadata_item = self.extract_metadata(dom)
return {'body': {'meta': metadata_item}}
metadata_nsmap = {None: xmlutil.XMLNS_V11}
class MetaItemTemplate(xmlutil.TemplateBuilder):
def construct(self):
sel = xmlutil.Selector('meta', xmlutil.get_items, 0)
root = xmlutil.TemplateElement('meta', selector=sel)
root.set('key', 0)
root.text = 1
return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
class MetadataTemplateElement(xmlutil.TemplateElement):
def will_render(self, datum):
return True
class MetadataTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = MetadataTemplateElement('metadata', selector='metadata')
elem = xmlutil.SubTemplateElement(root, 'meta',
selector=xmlutil.get_items)
elem.set('key', 0)
elem.text = 1
return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
def remove_invalid_options(context, search_options, allowed_search_options):
"""Remove search options that are not valid for non-admin API/context."""
if context.is_admin:

View File

@ -131,7 +131,6 @@ class Admin_actions(extensions.ExtensionDescriptor):
name = "AdminActions"
alias = "os-admin-actions"
namespace = "http://docs.openstack.org/share/ext/admin-actions/api/v1.1"
updated = "2012-08-25T00:00:00+00:00"
def get_controller_extensions(self):

View File

@ -25,6 +25,4 @@ class Extended_quotas(extensions.ExtensionDescriptor):
name = "ExtendedQuotas"
alias = "os-extended-quotas"
namespace = ("http://docs.openstack.org/compute/ext/extended_quotas"
"/api/v1.1")
updated = "2013-06-09T00:00:00+00:00"

View File

@ -16,32 +16,14 @@
import webob
from manila.api import extensions
from manila.api.openstack import wsgi
from manila.api import xmlutil
from manila import db
from manila import exception
from manila import quota
QUOTAS = quota.QUOTAS
authorize = extensions.extension_authorizer('share', 'quota_classes')
class QuotaClassTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('quota_class_set',
selector='quota_class_set')
root.set('id')
for resource in QUOTAS.resources:
elem = xmlutil.SubTemplateElement(root, resource)
elem.text = resource
return xmlutil.MasterTemplate(root, 1)
class QuotaClassSetsController(object):
def _format_quota_set(self, quota_class, quota_set):
@ -54,7 +36,6 @@ class QuotaClassSetsController(object):
return dict(quota_class_set=result)
@wsgi.serializers(xml=QuotaClassTemplate)
def show(self, req, id):
context = req.environ['manila.context']
authorize(context)
@ -66,7 +47,6 @@ class QuotaClassSetsController(object):
return self._format_quota_set(id,
QUOTAS.get_class_quotas(context, id))
@wsgi.serializers(xml=QuotaClassTemplate)
def update(self, req, id, body):
context = req.environ['manila.context']
authorize(context)
@ -89,8 +69,6 @@ class Quota_classes(extensions.ExtensionDescriptor):
name = "QuotaClasses"
alias = "os-quota-class-sets"
namespace = ("http://docs.openstack.org/volume/ext/"
"quota-classes-sets/api/v1.1")
updated = "2012-03-12T00:00:00+00:00"
def get_resources(self):

View File

@ -19,8 +19,6 @@ from six.moves.urllib import parse
import webob
from manila.api import extensions
from manila.api.openstack import wsgi
from manila.api import xmlutil
from manila import db
from manila.db.sqlalchemy import api as sqlalchemy_api
from manila import exception
@ -38,18 +36,6 @@ authorize_show = extensions.extension_authorizer('compute', 'quotas:show')
authorize_delete = extensions.extension_authorizer('compute', 'quotas:delete')
class QuotaTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('quota_set', selector='quota_set')
root.set('id')
for resource in QUOTAS.resources:
elem = xmlutil.SubTemplateElement(root, resource)
elem.text = resource
return xmlutil.MasterTemplate(root, 1)
class QuotaSetsController(object):
def __init__(self, ext_mgr):
@ -90,7 +76,6 @@ class QuotaSetsController(object):
else:
return dict((k, v['limit']) for k, v in values.items())
@wsgi.serializers(xml=QuotaTemplate)
def show(self, req, id):
context = req.environ['manila.context']
authorize_show(context)
@ -105,7 +90,6 @@ class QuotaSetsController(object):
except exception.NotAuthorized:
raise webob.exc.HTTPForbidden()
@wsgi.serializers(xml=QuotaTemplate)
def update(self, req, id, body):
context = req.environ['manila.context']
authorize_update(context)
@ -209,7 +193,6 @@ class QuotaSetsController(object):
raise webob.exc.HTTPForbidden()
return {'quota_set': self._get_quotas(context, id, user_id=user_id)}
@wsgi.serializers(xml=QuotaTemplate)
def defaults(self, req, id):
context = req.environ['manila.context']
authorize_show(context)
@ -241,7 +224,6 @@ class Quotas(extensions.ExtensionDescriptor):
name = "Quotas"
alias = "os-quota-sets"
namespace = "http://docs.openstack.org/compute/ext/quotas-sets/api/v1.1"
updated = "2011-08-08T00:00:00+00:00"
def get_resources(self):

View File

@ -17,8 +17,6 @@ from oslo_log import log
import webob.exc
from manila.api import extensions
from manila.api.openstack import wsgi
from manila.api import xmlutil
from manila import db
from manila import exception
from manila import utils
@ -27,33 +25,7 @@ LOG = log.getLogger(__name__)
authorize = extensions.extension_authorizer('share', 'services')
class ServicesIndexTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('services')
elem = xmlutil.SubTemplateElement(root, 'service', selector='services')
elem.set('binary')
elem.set('host')
elem.set('zone')
elem.set('status')
elem.set('state')
elem.set('update_at')
return xmlutil.MasterTemplate(root, 1)
class ServicesUpdateTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('host')
root.set('host')
root.set('disabled')
root.set('binary')
root.set('status')
return xmlutil.MasterTemplate(root, 1)
class ServiceController(object):
@wsgi.serializers(xml=ServicesIndexTemplate)
def index(self, req):
"""Return a list of all running services."""
context = req.environ['manila.context']
@ -90,7 +62,6 @@ class ServiceController(object):
return {'services': services}
@wsgi.serializers(xml=ServicesUpdateTemplate)
def update(self, req, id, body):
"""Enable/Disable scheduling for a service."""
context = req.environ['manila.context']
@ -126,7 +97,6 @@ class Services(extensions.ExtensionDescriptor):
name = "Services"
alias = "os-services"
namespace = "http://docs.openstack.org/volume/ext/services/api/v2"
updated = "2012-10-28T00:00:00-00:00"
def get_resources(self):

View File

@ -19,7 +19,6 @@ import webob
from manila.api import extensions
from manila.api.openstack import wsgi
from manila.api import xmlutil
from manila import exception
from manila.i18n import _
from manila import share
@ -28,40 +27,6 @@ from manila import share
authorize = extensions.extension_authorizer('share', 'services')
class ShareAccessTemplate(xmlutil.TemplateBuilder):
"""XML Template for share access management methods."""
def construct(self):
root = xmlutil.TemplateElement('access',
selector='access')
root.set("share_id")
root.set("deleted")
root.set("created_at")
root.set("updated_at")
root.set("access_type")
root.set("access_to")
root.set("state")
root.set("deleted_at")
root.set("id")
return xmlutil.MasterTemplate(root, 1)
class ShareAccessListTemplate(xmlutil.TemplateBuilder):
"""XML Template for share access list."""
def construct(self):
root = xmlutil.TemplateElement('access_list')
elem = xmlutil.SubTemplateElement(root, 'share',
selector='access_list')
elem.set("state")
elem.set("id")
elem.set("access_type")
elem.set("access_to")
return xmlutil.MasterTemplate(root, 1)
class ShareActionsController(wsgi.Controller):
def __init__(self, *args, **kwargs):
super(ShareActionsController, self).__init__(*args, **kwargs)
@ -118,7 +83,6 @@ class ShareActionsController(wsgi.Controller):
raise webob.exc.HTTPBadRequest(explanation=exc_str)
@wsgi.action('os-allow_access')
@wsgi.serializers(xml=ShareAccessTemplate)
def _allow_access(self, req, id, body):
"""Add share access rule."""
context = req.environ['manila.context']
@ -146,7 +110,6 @@ class ShareActionsController(wsgi.Controller):
return {'access': access}
@wsgi.action('os-deny_access')
@wsgi.serializers(xml=ShareAccessTemplate)
def _deny_access(self, req, id, body):
"""Remove access rule."""
context = req.environ['manila.context']
@ -164,7 +127,6 @@ class ShareActionsController(wsgi.Controller):
return webob.Response(status_int=202)
@wsgi.action('os-access_list')
@wsgi.serializers(xml=ShareAccessListTemplate)
def _access_list(self, req, id, body):
"""list access rules."""
context = req.environ['manila.context']
@ -183,7 +145,6 @@ class Share_actions(extensions.ExtensionDescriptor):
name = 'ShareActions'
alias = 'share-actions'
namespace = ''
updated = '2012-08-14T00:00:00+00:00'
def get_controller_extensions(self):

View File

@ -120,8 +120,6 @@ class Share_manage(extensions.ExtensionDescriptor):
name = 'ShareManage'
alias = 'os-share-manage'
namespace = ('http://docs.openstack.org/share/ext/'
'os-share-manage/api/v1')
updated = '2015-02-17T00:00:00+00:00'
def get_resources(self):

View File

@ -20,7 +20,6 @@ import webob
from manila.api import extensions
from manila.api.openstack import wsgi
from manila.api import xmlutil
from manila import exception
from manila.i18n import _
from manila.share import share_types
@ -31,45 +30,6 @@ soft_authorize = extensions.soft_extension_authorizer('share',
authorize = extensions.extension_authorizer('share', 'share_type_access')
def make_share_type(elem):
elem.set('{%s}is_public' % Share_type_access.namespace,
'%s:is_public' % Share_type_access.alias)
def make_share_type_access(elem):
elem.set('share_type_id')
elem.set('project_id')
class ShareTypeTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('share_type', selector='share_type')
make_share_type(root)
alias = Share_type_access.alias
namespace = Share_type_access.namespace
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
class ShareTypesTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('share_types')
elem = xmlutil.SubTemplateElement(
root, 'share_type', selector='share_types')
make_share_type(elem)
alias = Share_type_access.alias
namespace = Share_type_access.namespace
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
class ShareTypeAccessTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('share_type_access')
elem = xmlutil.SubTemplateElement(root, 'access',
selector='share_type_access')
make_share_type_access(elem)
return xmlutil.MasterTemplate(root, 1)
def _marshall_share_type_access(share_type):
rval = []
for project_id in share_type['projects']:
@ -82,7 +42,6 @@ def _marshall_share_type_access(share_type):
class ShareTypeAccessController(object):
"""The share type access API controller for the OpenStack API."""
@wsgi.serializers(xml=ShareTypeAccessTemplate)
def index(self, req, type_id):
context = req.environ['manila.context']
authorize(context)
@ -123,8 +82,6 @@ class ShareTypeActionController(wsgi.Controller):
def show(self, req, resp_obj, id):
context = req.environ['manila.context']
if soft_authorize(context):
# Attach our slave template to the response object
resp_obj.attach(xml=ShareTypeTemplate())
share_type = req.get_db_share_type(id)
self._extend_share_type(resp_obj.obj['share_type'], share_type)
@ -132,8 +89,6 @@ class ShareTypeActionController(wsgi.Controller):
def index(self, req, resp_obj):
context = req.environ['manila.context']
if soft_authorize(context):
# Attach our slave template to the response object
resp_obj.attach(xml=ShareTypesTemplate())
for share_type_rval in list(resp_obj.obj['share_types']):
type_id = share_type_rval['id']
share_type = req.get_db_share_type(type_id)
@ -143,8 +98,6 @@ class ShareTypeActionController(wsgi.Controller):
def create(self, req, body, resp_obj):
context = req.environ['manila.context']
if soft_authorize(context):
# Attach our slave template to the response object
resp_obj.attach(xml=ShareTypeTemplate())
type_id = resp_obj.obj['share_type']['id']
share_type = req.get_db_share_type(type_id)
self._extend_share_type(resp_obj.obj['share_type'], share_type)
@ -193,8 +146,6 @@ class Share_type_access(extensions.ExtensionDescriptor):
name = "ShareTypeAccess"
alias = "os-share-type-access"
namespace = ("http://docs.openstack.org/share/"
"ext/os-share-type-access/api/v1")
updated = "2015-03-02T00:00:00Z"
def get_resources(self):

View File

@ -78,8 +78,6 @@ class Share_unmanage(extensions.ExtensionDescriptor):
name = 'ShareUnmanage'
alias = 'os-share-unmanage'
namespace = ('http://docs.openstack.org/share/ext/'
'os-share-unmanage/api/v1')
updated = '2015-02-17T00:00:00+00:00'
def get_resources(self):

View File

@ -162,7 +162,6 @@ class Types_extra_specs(extensions.ExtensionDescriptor):
name = "TypesExtraSpecs"
alias = "os-types-extra-specs"
namespace = "http://docs.openstack.org/share/ext/types-extra-specs/api/v1"
updated = "2011-08-24T00:00:00+00:00"
def get_resources(self):

View File

@ -124,7 +124,6 @@ class Types_manage(extensions.ExtensionDescriptor):
name = "TypesManage"
alias = "os-types-manage"
namespace = "http://docs.openstack.org/share/ext/types-manage/api/v1"
updated = "2011-08-24T00:00:00+00:00"
def get_controller_extensions(self):

View File

@ -55,7 +55,6 @@ class Used_limits(extensions.ExtensionDescriptor):
name = "UsedLimits"
alias = 'os-used-limits'
namespace = "http://docs.openstack.org/share/ext/used-limits/api/v1.0"
updated = "2014-03-27T00:00:00+00:00"
def get_controller_extensions(self):

View File

@ -22,6 +22,4 @@ class User_quotas(extensions.ExtensionDescriptor):
name = "UserQuotas"
alias = "os-user-quotas"
namespace = ("http://docs.openstack.org/compute/ext/user_quotas"
"/api/v1.1")
updated = "2013-07-18T00:00:00+00:00"

View File

@ -25,7 +25,6 @@ import webob.exc
import manila.api.openstack
from manila.api.openstack import wsgi
from manila.api import xmlutil
from manila import exception
from manila.i18n import _LE
from manila.i18n import _LI
@ -52,10 +51,6 @@ class ExtensionDescriptor(object):
# Description comes from the docstring for the class
# The XML namespace for the extension, e.g.,
# 'http://www.fox.in.socks/api/ext/pie/v1.0'
namespace = None
# The timestamp when the extension was last updated, e.g.,
# '2011-01-22T13:25:27-06:00'
updated = None
@ -83,55 +78,6 @@ class ExtensionDescriptor(object):
controller_exts = []
return controller_exts
@classmethod
def nsmap(cls):
"""Synthesize a namespace map from extension."""
# Start with a base nsmap
nsmap = ext_nsmap.copy()
# Add the namespace for the extension
nsmap[cls.alias] = cls.namespace
return nsmap
@classmethod
def xmlname(cls, name):
"""Synthesize element and attribute names."""
return '{%s}%s' % (cls.namespace, name)
def make_ext(elem):
elem.set('name')
elem.set('namespace')
elem.set('alias')
elem.set('updated')
desc = xmlutil.SubTemplateElement(elem, 'description')
desc.text = 'description'
xmlutil.make_links(elem, 'links')
ext_nsmap = {None: xmlutil.XMLNS_COMMON_V10, 'atom': xmlutil.XMLNS_ATOM}
class ExtensionTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('extension', selector='extension')
make_ext(root)
return xmlutil.MasterTemplate(root, 1, nsmap=ext_nsmap)
class ExtensionsTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('extensions')
elem = xmlutil.SubTemplateElement(root, 'extension',
selector='extensions')
make_ext(elem)
return xmlutil.MasterTemplate(root, 1, nsmap=ext_nsmap)
class ExtensionsResource(wsgi.Resource):
@ -144,19 +90,16 @@ class ExtensionsResource(wsgi.Resource):
ext_data['name'] = ext.name
ext_data['alias'] = ext.alias
ext_data['description'] = ext.__doc__
ext_data['namespace'] = ext.namespace
ext_data['updated'] = ext.updated
ext_data['links'] = [] # TODO(dprince): implement extension links
return ext_data
@wsgi.serializers(xml=ExtensionsTemplate)
def index(self, req):
extensions = []
for _alias, ext in six.iteritems(self.extension_manager.extensions):
extensions.append(self._translate(ext))
return dict(extensions=extensions)
@wsgi.serializers(xml=ExtensionTemplate)
def show(self, req, id):
try:
# NOTE(dprince): the extensions alias is used as the 'id' for show
@ -240,7 +183,6 @@ class ExtensionManager(object):
LOG.debug('Ext alias: %s', extension.alias)
LOG.debug('Ext description: %s',
' '.join(extension.__doc__.strip().split()))
LOG.debug('Ext namespace: %s', extension.namespace)
LOG.debug('Ext updated: %s', extension.updated)
except AttributeError as ex:
LOG.exception(_LE("Exception loading extension: %s"),

View File

@ -28,37 +28,14 @@ from manila.i18n import _LI
from manila import utils
from manila import wsgi
from lxml import etree
from xml.dom import minidom
from xml.parsers import expat
XMLNS_V1 = 'http://docs.openstack.org/volume/api/v1'
XMLNS_ATOM = 'http://www.w3.org/2005/Atom'
LOG = log.getLogger(__name__)
# The vendor content types should serialize identically to the non-vendor
# content types. So to avoid littering the code with both options, we
# map the vendor to the other when looking up the type
_CONTENT_TYPE_MAP = {
'application/vnd.openstack.volume+json': 'application/json',
'application/vnd.openstack.volume+xml': 'application/xml',
}
SUPPORTED_CONTENT_TYPES = (
'application/json',
'application/vnd.openstack.volume+json',
'application/xml',
'application/vnd.openstack.volume+xml',
)
_MEDIA_TYPE_MAP = {
'application/vnd.openstack.volume+json': 'json',
'application/json': 'json',
'application/vnd.openstack.volume+xml': 'xml',
'application/xml': 'xml',
'application/atom+xml': 'atom',
}
@ -252,95 +229,6 @@ class JSONDeserializer(TextDeserializer):
return {'body': self._from_json(datastring)}
class XMLDeserializer(TextDeserializer):
def __init__(self, metadata=None):
"""
:param metadata: information needed to deserialize xml into
a dictionary.
"""
super(XMLDeserializer, self).__init__()
self.metadata = metadata or {}
def _from_xml(self, datastring):
plurals = set(self.metadata.get('plurals', {}))
try:
node = utils.safe_minidom_parse_string(datastring).childNodes[0]
return {node.nodeName: self._from_xml_node(node, plurals)}
except expat.ExpatError:
msg = _("cannot understand XML")
raise exception.MalformedRequestBody(reason=msg)
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.
"""
if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
return node.childNodes[0].nodeValue
elif node.nodeName in listnames:
return [self._from_xml_node(n, listnames) for n in node.childNodes]
else:
result = dict()
for attr in node.attributes.keys():
result[attr] = node.attributes[attr].nodeValue
for child in node.childNodes:
if child.nodeType != node.TEXT_NODE:
result[child.nodeName] = self._from_xml_node(child,
listnames)
return result
def find_first_child_named(self, parent, name):
"""Search a nodes children for the first child with a given name"""
for node in parent.childNodes:
if node.nodeName == name:
return node
return None
def find_children_named(self, parent, name):
"""Return all of a nodes children who have the given name"""
for node in parent.childNodes:
if node.nodeName == name:
yield node
def extract_text(self, node):
"""Get the text field contained by the given node"""
if len(node.childNodes) == 1:
child = node.childNodes[0]
if child.nodeType == child.TEXT_NODE:
return child.nodeValue
return ""
def find_attribute_or_element(self, parent, name):
"""Get an attribute value; fallback to an element if not found"""
if parent.hasAttribute(name):
return parent.getAttribute(name)
node = self.find_first_child_named(parent, name)
if node:
return self.extract_text(node)
return None
def default(self, datastring):
return {'body': self._from_xml(datastring)}
class MetadataXMLDeserializer(XMLDeserializer):
def extract_metadata(self, metadata_node):
"""Marshal the metadata attribute of a parsed request"""
metadata = {}
if metadata_node is not None:
for meta_node in self.find_children_named(metadata_node, "meta"):
key = meta_node.getAttribute("key")
metadata[key] = self.extract_text(meta_node)
return metadata
class DictSerializer(ActionDispatcher):
"""Default request body serialization"""
@ -358,110 +246,6 @@ class JSONDictSerializer(DictSerializer):
return jsonutils.dumps(data)
class XMLDictSerializer(DictSerializer):
def __init__(self, metadata=None, xmlns=None):
"""
: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 {}
self.xmlns = xmlns
def default(self, data):
# We expect data to contain a single key which is the XML root.
root_key = data.keys()[0]
doc = minidom.Document()
node = self._to_xml_node(doc, self.metadata, root_key, data[root_key])
return self.to_xml_string(node)
def to_xml_string(self, node, has_atom=False):
self._add_xmlns(node, has_atom)
return node.toxml('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, has_atom=False):
if self.xmlns is not None:
node.setAttribute('xmlns', self.xmlns)
if has_atom:
node.setAttribute('xmlns:atom', "http://www.w3.org/2005/Atom")
def _to_xml_node(self, doc, metadata, nodename, data):
"""Recursive method to convert data members to XML nodes."""
result = doc.createElement(nodename)
# Set the xml namespace if one is specified
# TODO(justinsb): We could also use prefixes on the keys
xmlns = metadata.get('xmlns', None)
if xmlns:
result.setAttribute('xmlns', xmlns)
# TODO(bcwaldon): accomplish this without a type-check
if isinstance(data, list):
collections = metadata.get('list_collections', {})
if nodename in collections:
metadata = collections[nodename]
for item in data:
node = doc.createElement(metadata['item_name'])
node.setAttribute(metadata['item_key'], str(item))
result.appendChild(node)
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:
node = self._to_xml_node(doc, metadata, singular, item)
result.appendChild(node)
# TODO(bcwaldon): accomplish this without a type-check
elif isinstance(data, dict):
collections = metadata.get('dict_collections', {})
if nodename in collections:
metadata = collections[nodename]
for k, v in data.items():
node = doc.createElement(metadata['item_name'])
node.setAttribute(metadata['item_key'], str(k))
text = doc.createTextNode(str(v))
node.appendChild(text)
result.appendChild(node)
return result
attrs = metadata.get('attributes', {}).get(nodename, {})
for k, v in data.items():
if k in attrs:
result.setAttribute(k, str(v))
else:
node = self._to_xml_node(doc, metadata, k, v)
result.appendChild(node)
else:
# Type is atom
node = doc.createTextNode(str(data))
result.appendChild(node)
return result
def _create_link_nodes(self, xml_doc, links):
link_nodes = []
for link in links:
link_node = xml_doc.createElement('atom:link')
link_node.setAttribute('rel', link['rel'])
link_node.setAttribute('href', link['href'])
if 'type' in link:
link_node.setAttribute('type', link['type'])
link_nodes.append(link_node)
return link_nodes
def _to_xml(self, root):
"""Convert the xml object to an xml string."""
return etree.tostring(root, encoding='UTF-8', xml_declaration=True)
def serializers(**serializers):
"""Attaches serializers to a method.
@ -660,15 +444,6 @@ def action_peek_json(body):
return decoded.keys()[0]
def action_peek_xml(body):
"""Determine action to invoke."""
dom = utils.safe_minidom_parse_string(body)
action_node = dom.childNodes[0]
return action_node.tagName
class ResourceExceptionHandler(object):
"""Context manager to handle Resource exceptions.
@ -731,16 +506,13 @@ class Resource(wsgi.Application):
self.controller = controller
default_deserializers = dict(xml=XMLDeserializer,
json=JSONDeserializer)
default_deserializers = dict(json=JSONDeserializer)
default_deserializers.update(deserializers)
self.default_deserializers = default_deserializers
self.default_serializers = dict(xml=XMLDictSerializer,
json=JSONDictSerializer)
self.default_serializers = dict(json=JSONDictSerializer)
self.action_peek = dict(xml=action_peek_xml,
json=action_peek_json)
self.action_peek = dict(json=action_peek_json)
self.action_peek.update(action_peek or {})
# Copy over the actions dictionary
@ -1186,11 +958,8 @@ class Fault(webob.exc.HTTPException):
# 'code' is an attribute on the fault tag itself
metadata = {'attributes': {fault_name: 'code'}}
xml_serializer = XMLDictSerializer(metadata, XMLNS_V1)
content_type = req.best_match_content_type()
serializer = {
'application/xml': xml_serializer,
'application/json': JSONDictSerializer(),
}[content_type]
@ -1245,9 +1014,7 @@ class OverLimitFault(webob.exc.HTTPException):
content_type = request.best_match_content_type()
metadata = {"attributes": {"overLimitFault": "code"}}
xml_serializer = XMLDictSerializer(metadata, XMLNS_V1)
serializer = {
'application/xml': xml_serializer,
'application/json': JSONDictSerializer(),
}[content_type]

View File

@ -1,141 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
-*- rnc -*-
RELAX NG Compact Syntax Grammar for the
Atom Format Specification Version 11
-->
<grammar xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:s="http://www.ascc.net/xml/schematron" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
<choice>
<ref name="atomLink"/>
</choice>
</start>
<!-- Common attributes -->
<define name="atomCommonAttributes">
<optional>
<attribute name="xml:base">
<ref name="atomUri"/>
</attribute>
</optional>
<optional>
<attribute name="xml:lang">
<ref name="atomLanguageTag"/>
</attribute>
</optional>
<zeroOrMore>
<ref name="undefinedAttribute"/>
</zeroOrMore>
</define>
<!-- atom:link -->
<define name="atomLink">
<element name="atom:link">
<ref name="atomCommonAttributes"/>
<attribute name="href">
<ref name="atomUri"/>
</attribute>
<optional>
<attribute name="rel">
<choice>
<ref name="atomNCName"/>
<ref name="atomUri"/>
</choice>
</attribute>
</optional>
<optional>
<attribute name="type">
<ref name="atomMediaType"/>
</attribute>
</optional>
<optional>
<attribute name="hreflang">
<ref name="atomLanguageTag"/>
</attribute>
</optional>
<optional>
<attribute name="title"/>
</optional>
<optional>
<attribute name="length"/>
</optional>
<ref name="undefinedContent"/>
</element>
</define>
<!-- Low-level simple types -->
<define name="atomNCName">
<data type="string">
<param name="minLength">1</param>
<param name="pattern">[^:]*</param>
</data>
</define>
<!-- Whatever a media type is, it contains at least one slash -->
<define name="atomMediaType">
<data type="string">
<param name="pattern">.+/.+</param>
</data>
</define>
<!-- As defined in RFC 3066 -->
<define name="atomLanguageTag">
<data type="string">
<param name="pattern">[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*</param>
</data>
</define>
<!--
Unconstrained; it's not entirely clear how IRI fit into
xsd:anyURI so let's not try to constrain it here
-->
<define name="atomUri">
<text/>
</define>
<!-- Other Extensibility -->
<define name="undefinedAttribute">
<attribute>
<anyName>
<except>
<name>xml:base</name>
<name>xml:lang</name>
<nsName ns=""/>
</except>
</anyName>
</attribute>
</define>
<define name="undefinedContent">
<zeroOrMore>
<choice>
<text/>
<ref name="anyForeignElement"/>
</choice>
</zeroOrMore>
</define>
<define name="anyElement">
<element>
<anyName/>
<zeroOrMore>
<choice>
<attribute>
<anyName/>
</attribute>
<text/>
<ref name="anyElement"/>
</choice>
</zeroOrMore>
</element>
</define>
<define name="anyForeignElement">
<element>
<anyName>
<except>
<nsName ns="http://www.w3.org/2005/Atom"/>
</except>
</anyName>
<zeroOrMore>
<choice>
<attribute>
<anyName/>
</attribute>
<text/>
<ref name="anyElement"/>
</choice>
</zeroOrMore>
</element>
</define>
</grammar>

View File

@ -1,11 +0,0 @@
<element name="extension" ns="http://docs.openstack.org/common/api/v1.0"
xmlns="http://relaxng.org/ns/structure/1.0">
<attribute name="alias"> <text/> </attribute>
<attribute name="name"> <text/> </attribute>
<attribute name="namespace"> <text/> </attribute>
<attribute name="updated"> <text/> </attribute>
<element name="description"> <text/> </element>
<zeroOrMore>
<externalRef href="../atom-link.rng"/>
</zeroOrMore>
</element>

View File

@ -1,6 +0,0 @@
<element name="extensions" xmlns="http://relaxng.org/ns/structure/1.0"
ns="http://docs.openstack.org/common/api/v1.0">
<zeroOrMore>
<externalRef href="extension.rng"/>
</zeroOrMore>
</element>

View File

@ -1,28 +0,0 @@
<element name="limits" ns="http://docs.openstack.org/common/api/v1.0"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="rates">
<zeroOrMore>
<element name="rate">
<attribute name="uri"> <text/> </attribute>
<attribute name="regex"> <text/> </attribute>
<zeroOrMore>
<element name="limit">
<attribute name="value"> <text/> </attribute>
<attribute name="verb"> <text/> </attribute>
<attribute name="remaining"> <text/> </attribute>
<attribute name="unit"> <text/> </attribute>
<attribute name="next-available"> <text/> </attribute>
</element>
</zeroOrMore>
</element>
</zeroOrMore>
</element>
<element name="absolute">
<zeroOrMore>
<element name="limit">
<attribute name="name"> <text/> </attribute>
<attribute name="value"> <text/> </attribute>
</element>
</zeroOrMore>
</element>
</element>

View File

@ -1,9 +0,0 @@
<element name="metadata" ns="http://docs.openstack.org/compute/api/v1.1"
xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="meta">
<attribute name="key"> <text/> </attribute>
<text/>
</element>
</zeroOrMore>
</element>

View File

@ -253,24 +253,16 @@ class URLMap(paste.urlmap.URLMap):
path_info = environ['PATH_INFO']
path_info = self.normalize_url(path_info, False)[1]
# The MIME type for the response is determined in one of two ways:
# 1) URL path suffix (eg /servers/detail.json)
# 2) Accept header (eg application/json;q=0.8, application/xml;q=0.2)
# The API version is determined in one of three ways:
# 1) URL path prefix (eg /v1.1/tenant/servers/detail)
# 2) Content-Type header (eg application/json;version=1.1)
# 3) Accept header (eg application/json;q=0.8;version=1.1)
# Manila supports only application/json as MIME type for the responses.
supported_content_types = list(wsgi.SUPPORTED_CONTENT_TYPES)
mime_type, app, app_url = self._path_strategy(host, port, path_info)
# Accept application/atom+xml for the index query of each API
# version mount point as well as the root index
if (app_url and app_url + '/' == path_info) or path_info == '/':
supported_content_types.append('application/atom+xml')
if not app:
app = self._content_type_strategy(host, port, environ)

View File

@ -31,7 +31,6 @@ import webob.exc
from manila.api.openstack import wsgi
from manila.api.views import limits as limits_views
from manila.api import xmlutil
from manila.i18n import _
from manila import quota
from manila import wsgi as base_wsgi
@ -46,38 +45,9 @@ PER_HOUR = 60 * 60
PER_DAY = 60 * 60 * 24
limits_nsmap = {None: xmlutil.XMLNS_COMMON_V10, 'atom': xmlutil.XMLNS_ATOM}
class LimitsTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('limits', selector='limits')
rates = xmlutil.SubTemplateElement(root, 'rates')
rate = xmlutil.SubTemplateElement(rates, 'rate', selector='rate')
rate.set('uri', 'uri')
rate.set('regex', 'regex')
limit = xmlutil.SubTemplateElement(rate, 'limit', selector='limit')
limit.set('value', 'value')
limit.set('verb', 'verb')
limit.set('remaining', 'remaining')
limit.set('unit', 'unit')
limit.set('next-available', 'next-available')
absolute = xmlutil.SubTemplateElement(root, 'absolute',
selector='absolute')
limit = xmlutil.SubTemplateElement(absolute, 'limit',
selector=xmlutil.get_items)
limit.set('name', 0)
limit.set('value', 1)
return xmlutil.MasterTemplate(root, 1, nsmap=limits_nsmap)
class LimitsController(object):
"""Controller for accessing limits in the OpenStack API."""
@wsgi.serializers(xml=LimitsTemplate)
def index(self, req):
"""Return all global and rate limit information."""
context = req.environ['manila.context']

View File

@ -23,7 +23,6 @@ from webob import exc
from manila.api import common
from manila.api.openstack import wsgi
from manila.api.views import security_service as security_service_views
from manila.api import xmlutil
from manila.common import constants
from manila import db
from manila import exception
@ -36,36 +35,11 @@ RESOURCE_NAME = 'security_service'
LOG = log.getLogger(__name__)
def make_security_service(elem):
attrs = ['id', 'name', 'description', 'type', 'server', 'domain', 'user',
'password', 'dns_ip', 'status', 'updated_at', 'created_at']
for attr in attrs:
elem.set(attr)
class SecurityServiceTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('security_service',
selector='security_service')
make_security_service(root)
return xmlutil.MasterTemplate(root, 1)
class SecurityServicesTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('security_services')
elem = xmlutil.SubTemplateElement(root, 'security_service',
selector='security_services')
make_security_service(elem)
return xmlutil.MasterTemplate(root, 1)
class SecurityServiceController(wsgi.Controller):
"""The Shares API controller for the OpenStack API."""
_view_builder_class = security_service_views.ViewBuilder
@wsgi.serializers(xml=SecurityServiceTemplate)
def show(self, req, id):
"""Return data about the given security service."""
context = req.environ['manila.context']
@ -102,14 +76,12 @@ class SecurityServiceController(wsgi.Controller):
return webob.Response(status_int=202)
@wsgi.serializers(xml=SecurityServicesTemplate)
def index(self, req):
"""Returns a summary list of security services."""
policy.check_policy(req.environ['manila.context'], RESOURCE_NAME,
'index')
return self._get_security_services(req, is_detail=False)
@wsgi.serializers(xml=SecurityServicesTemplate)
def detail(self, req):
"""Returns a detailed list of security services."""
policy.check_policy(req.environ['manila.context'], RESOURCE_NAME,
@ -181,7 +153,6 @@ class SecurityServiceController(wsgi.Controller):
return True
return False
@wsgi.serializers(xml=SecurityServiceTemplate)
def update(self, req, id, body):
"""Update a security service."""
context = req.environ['manila.context']
@ -221,7 +192,6 @@ class SecurityServiceController(wsgi.Controller):
security_service = db.security_service_update(context, id, update_dict)
return self._view_builder.detail(req, security_service)
@wsgi.serializers(xml=SecurityServiceTemplate)
def create(self, req, body):
"""Creates a new security service."""
context = req.environ['manila.context']

View File

@ -16,7 +16,6 @@
import webob
from webob import exc
from manila.api import common
from manila.api.openstack import wsgi
from manila import exception
from manila.i18n import _
@ -39,14 +38,11 @@ class ShareMetadataController(object):
raise exc.HTTPNotFound(explanation=msg)
return meta
@wsgi.serializers(xml=common.MetadataTemplate)
def index(self, req, share_id):
"""Returns the list of metadata for a given share."""
context = req.environ['manila.context']
return {'metadata': self._get_metadata(context, share_id)}
@wsgi.serializers(xml=common.MetadataTemplate)
@wsgi.deserializers(xml=common.MetadataDeserializer)
def create(self, req, share_id, body):
try:
metadata = body['metadata']
@ -63,8 +59,6 @@ class ShareMetadataController(object):
return {'metadata': new_metadata}
@wsgi.serializers(xml=common.MetaItemTemplate)
@wsgi.deserializers(xml=common.MetaItemDeserializer)
def update(self, req, share_id, id, body):
try:
meta_item = body['meta']
@ -88,8 +82,6 @@ class ShareMetadataController(object):
return {'meta': meta_item}
@wsgi.serializers(xml=common.MetadataTemplate)
@wsgi.deserializers(xml=common.MetadataDeserializer)
def update_all(self, req, share_id, body):
try:
metadata = body['metadata']
@ -125,7 +117,6 @@ class ShareMetadataController(object):
except exception.InvalidShareMetadataSize as error:
raise exc.HTTPBadRequest(explanation=error.msg)
@wsgi.serializers(xml=common.MetaItemTemplate)
def show(self, req, share_id, id):
"""Return a single metadata item."""
context = req.environ['manila.context']

View File

@ -25,7 +25,6 @@ from webob import exc
from manila.api import common
from manila.api.openstack import wsgi
from manila.api.views import share_networks as share_networks_views
from manila.api import xmlutil
from manila.db import api as db_api
from manila import exception
from manila.i18n import _
@ -39,43 +38,6 @@ RESOURCE_NAME = 'share_network'
RESOURCES_NAME = 'share_networks'
LOG = log.getLogger(__name__)
QUOTAS = quota.QUOTAS
SHARE_NETWORK_ATTRS = (
'id',
'project_id',
'user_id',
'created_at',
'updated_at',
'nova_net_id',
'neutron_net_id',
'neutron_subnet_id',
'network_type',
'segmentation_id',
'cidr',
'ip_version',
'name',
'description',
)
def _make_share_network(elem):
for attr in SHARE_NETWORK_ATTRS:
elem.set(attr)
class ShareNetworkTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement(RESOURCE_NAME, selector=RESOURCE_NAME)
_make_share_network(root)
return xmlutil.MasterTemplate(root, 1)
class ShareNetworksTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement(RESOURCES_NAME)
elem = xmlutil.SubTemplateElement(root, RESOURCE_NAME,
selector=RESOURCES_NAME)
_make_share_network(elem)
return xmlutil.MasterTemplate(root, 1)
class ShareNetworkController(wsgi.Controller):
@ -87,7 +49,6 @@ class ShareNetworkController(wsgi.Controller):
super(ShareNetworkController, self).__init__()
self.share_rpcapi = share_rpcapi.ShareAPI()
@wsgi.serializers(xml=ShareNetworkTemplate)
def show(self, req, id):
"""Return data about the requested network info."""
context = req.environ['manila.context']
@ -132,7 +93,6 @@ class ShareNetworkController(wsgi.Controller):
project_id=share_network['project_id'])
return webob.Response(status_int=202)
@wsgi.serializers(xml=ShareNetworksTemplate)
def _get_share_networks(self, req, is_detail=True):
"""Returns a list of share networks."""
context = req.environ['manila.context']
@ -200,14 +160,12 @@ class ShareNetworkController(wsgi.Controller):
limited_list = common.limited(networks, req)
return self._view_builder.build_share_networks(limited_list, is_detail)
@wsgi.serializers(xml=ShareNetworksTemplate)
def index(self, req):
"""Returns a summary list of share networks."""
policy.check_policy(req.environ['manila.context'], RESOURCE_NAME,
'index')
return self._get_share_networks(req, is_detail=False)
@wsgi.serializers(xml=ShareNetworksTemplate)
def detail(self, req):
"""Returns a detailed list of share networks."""
policy.check_policy(req.environ['manila.context'], RESOURCE_NAME,
@ -231,7 +189,6 @@ class ShareNetworkController(wsgi.Controller):
"exclusive. Only one of these are allowed at a time.")
raise exc.HTTPBadRequest(explanation=msg)
@wsgi.serializers(xml=ShareNetworkTemplate)
def update(self, req, id, body):
"""Update specified share network."""
context = req.environ['manila.context']
@ -267,7 +224,6 @@ class ShareNetworkController(wsgi.Controller):
return self._view_builder.build_share_network(share_network)
@wsgi.serializers(xml=ShareNetworkTemplate)
def create(self, req, body):
"""Creates a new share network."""
context = req.environ['manila.context']
@ -309,7 +265,6 @@ class ShareNetworkController(wsgi.Controller):
QUOTAS.commit(context, reservations)
return self._view_builder.build_share_network(share_network)
@wsgi.serializers(xml=ShareNetworkTemplate)
def action(self, req, id, body):
_actions = {
'add_security_service': self._add_security_service,

View File

@ -20,7 +20,6 @@ from webob import exc
from manila.api.openstack import wsgi
from manila.api.views import share_servers as share_servers_views
from manila.api import xmlutil
from manila.common import constants
from manila.db import api as db_api
from manila import exception
@ -31,37 +30,6 @@ from manila import share
RESOURCE_NAME = 'share_server'
RESOURCES_NAME = 'share_servers'
LOG = log.getLogger(__name__)
SHARE_SERVER_ATTRS = (
'id',
'project_id',
'updated_at',
'status',
'host',
'share_network_name',
)
def _make_share_server(elem, details=False):
for attr in SHARE_SERVER_ATTRS:
elem.set(attr)
if details:
elem.set('backend_details')
class ShareServerTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement(RESOURCE_NAME, selector=RESOURCE_NAME)
_make_share_server(root, details=True)
return xmlutil.MasterTemplate(root, 1)
class ShareServersTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement(RESOURCES_NAME)
elem = xmlutil.SubTemplateElement(root, RESOURCE_NAME,
selector=RESOURCES_NAME)
_make_share_server(elem)
return xmlutil.MasterTemplate(root, 1)
class ShareServerController(wsgi.Controller):
@ -72,7 +40,6 @@ class ShareServerController(wsgi.Controller):
self._view_builder_class = share_servers_views.ViewBuilder
super(ShareServerController, self).__init__()
@wsgi.serializers(xml=ShareServersTemplate)
def index(self, req):
"""Returns a list of share servers."""
@ -98,7 +65,6 @@ class ShareServerController(wsgi.Controller):
s.share_network['id']])]
return self._view_builder.build_share_servers(share_servers)
@wsgi.serializers(xml=ShareServerTemplate)
def show(self, req, id):
"""Return data about the requested share server."""
context = req.environ['manila.context']

View File

@ -23,7 +23,6 @@ from webob import exc
from manila.api import common
from manila.api.openstack import wsgi
from manila.api.views import share_snapshots as snapshot_views
from manila.api import xmlutil
from manila import exception
from manila.i18n import _LI
from manila import share
@ -31,30 +30,6 @@ from manila import share
LOG = log.getLogger(__name__)
def make_snapshot(elem):
attrs = ['id', 'size', 'status', 'name', 'description', 'share_proto',
'links', 'share_id', 'created_at', 'share_size']
for attr in attrs:
elem.set(attr)
class SnapshotTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('snapshot',
selector='snapshot')
make_snapshot(root)
return xmlutil.MasterTemplate(root, 1)
class SnapshotsTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('snapshots')
elem = xmlutil.SubTemplateElement(root, 'snapshot',
selector='snapshots')
make_snapshot(elem)
return xmlutil.MasterTemplate(root, 1)
class ShareSnapshotsController(wsgi.Controller):
"""The Share Snapshots API controller for the OpenStack API."""
@ -64,7 +39,6 @@ class ShareSnapshotsController(wsgi.Controller):
super(ShareSnapshotsController, self).__init__()
self.share_api = share.API()
@wsgi.serializers(xml=SnapshotTemplate)
def show(self, req, id):
"""Return data about the given snapshot."""
context = req.environ['manila.context']
@ -89,12 +63,10 @@ class ShareSnapshotsController(wsgi.Controller):
raise exc.HTTPNotFound()
return webob.Response(status_int=202)
@wsgi.serializers(xml=SnapshotsTemplate)
def index(self, req):
"""Returns a summary list of snapshots."""
return self._get_snapshots(req, is_detail=False)
@wsgi.serializers(xml=SnapshotsTemplate)
def detail(self, req):
"""Returns a detailed list of snapshots."""
return self._get_snapshots(req, is_detail=True)
@ -138,7 +110,6 @@ class ShareSnapshotsController(wsgi.Controller):
"""Return share search options allowed by non-admin."""
return ('display_name', 'name', 'status', 'share_id', 'size')
@wsgi.serializers(xml=SnapshotTemplate)
def update(self, req, id, body):
"""Update a snapshot."""
context = req.environ['manila.context']
@ -167,7 +138,6 @@ class ShareSnapshotsController(wsgi.Controller):
return self._view_builder.detail(req, snapshot)
@wsgi.response(202)
@wsgi.serializers(xml=SnapshotTemplate)
def create(self, req, body):
"""Creates a new snapshot."""
context = req.environ['manila.context']
@ -204,18 +174,3 @@ class ShareSnapshotsController(wsgi.Controller):
def create_resource():
return wsgi.Resource(ShareSnapshotsController())
#
# class Share_snapshots(extensions.ExtensionDescriptor):
# """Enable share snapshtos API."""
# name = 'ShareSnapshots'
# alias = 'snapshots'
# namespace = ''
# updated = '2013-03-01T00:00:00+00:00'
#
# def get_resources(self):
# controller = ShareSnapshotsController()
# resource = extensions.ResourceExtension(
# 'snapshots', controller,
# collection_actions={'detail': 'GET'})
# return [resource]

View File

@ -26,7 +26,6 @@ from webob import exc
from manila.api import common
from manila.api.openstack import wsgi
from manila.api.views import shares as share_views
from manila.api import xmlutil
from manila import exception
from manila.i18n import _
from manila.i18n import _LI
@ -36,32 +35,6 @@ from manila.share import share_types
LOG = log.getLogger(__name__)
def make_share(elem):
# NOTE(u_glide):
# export_location is backward-compatibility attribute, which contains first
# export location from export_locations list.
attrs = ['id', 'size', 'availability_zone', 'status', 'name',
'description', 'share_proto', 'export_location', 'links',
'snapshot_id', 'created_at', 'metadata', 'export_locations']
for attr in attrs:
elem.set(attr)
class ShareTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('share', selector='share')
make_share(root)
return xmlutil.MasterTemplate(root, 1)
class SharesTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('shares')
elem = xmlutil.SubTemplateElement(root, 'share', selector='shares')
make_share(elem)
return xmlutil.MasterTemplate(root, 1)
class ShareController(wsgi.Controller):
"""The Shares API controller for the OpenStack API."""
@ -71,7 +44,6 @@ class ShareController(wsgi.Controller):
super(ShareController, self).__init__()
self.share_api = share.API()
@wsgi.serializers(xml=ShareTemplate)
def show(self, req, id):
"""Return data about the given share."""
context = req.environ['manila.context']
@ -99,12 +71,10 @@ class ShareController(wsgi.Controller):
return webob.Response(status_int=202)
@wsgi.serializers(xml=SharesTemplate)
def index(self, req):
"""Returns a summary list of shares."""
return self._get_shares(req, is_detail=False)
@wsgi.serializers(xml=SharesTemplate)
def detail(self, req):
"""Returns a detailed list of shares."""
return self._get_shares(req, is_detail=True)
@ -164,7 +134,6 @@ class ShareController(wsgi.Controller):
'is_public', 'metadata', 'extra_specs', 'sort_key', 'sort_dir',
)
@wsgi.serializers(xml=ShareTemplate)
def update(self, req, id, body):
"""Update a share."""
context = req.environ['manila.context']
@ -192,7 +161,6 @@ class ShareController(wsgi.Controller):
share.update(update_dict)
return self._view_builder.detail(req, share)
@wsgi.serializers(xml=ShareTemplate)
def create(self, req, body):
"""Creates a new share."""
context = req.environ['manila.context']

View File

@ -13,14 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from lxml import etree
from oslo_config import cfg
from manila.api.openstack import wsgi
from manila.api.views import versions as views_versions
from manila.api import xmlutil
CONF = cfg.CONF
@ -46,13 +42,8 @@ _KNOWN_VERSIONS = {
},
],
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.volume+xml;version=1",
},
{
"base": "application/json",
"type": "application/vnd.openstack.volume+json;version=1",
}
],
},
@ -76,13 +67,8 @@ _KNOWN_VERSIONS = {
},
],
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.volume+xml;version=1",
},
{
"base": "application/json",
"type": "application/vnd.openstack.volume+json;version=1",
}
],
}
@ -101,157 +87,16 @@ def get_supported_versions():
return versions
class MediaTypesTemplateElement(xmlutil.TemplateElement):
def will_render(self, datum):
return 'media-types' in datum
def make_version(elem):
elem.set('id')
elem.set('status')
elem.set('updated')
mts = MediaTypesTemplateElement('media-types')
elem.append(mts)
mt = xmlutil.SubTemplateElement(mts, 'media-type', selector='media-types')
mt.set('base')
mt.set('type')
xmlutil.make_links(elem, 'links')
version_nsmap = {None: xmlutil.XMLNS_COMMON_V10, 'atom': xmlutil.XMLNS_ATOM}
class VersionTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('version', selector='version')
make_version(root)
return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap)
class VersionsTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('versions')
elem = xmlutil.SubTemplateElement(root, 'version', selector='versions')
make_version(elem)
return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap)
class ChoicesTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('choices')
elem = xmlutil.SubTemplateElement(root, 'version', selector='choices')
make_version(elem)
return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap)
class AtomSerializer(wsgi.XMLDictSerializer):
NSMAP = {None: xmlutil.XMLNS_ATOM}
def __init__(self, metadata=None, xmlns=None):
self.metadata = metadata or {}
if not xmlns:
self.xmlns = wsgi.XMLNS_ATOM
else:
self.xmlns = xmlns
def _get_most_recent_update(self, versions):
recent = None
for version in versions:
updated = datetime.datetime.strptime(version['updated'],
'%Y-%m-%dT%H:%M:%SZ')
if not recent:
recent = updated
elif updated > recent:
recent = updated
return recent.strftime('%Y-%m-%dT%H:%M:%SZ')
def _get_base_url(self, link_href):
# Make sure no trailing /
link_href = link_href.rstrip('/')
return link_href.rsplit('/', 1)[0] + '/'
def _create_feed(self, versions, feed_title, feed_id):
feed = etree.Element('feed', nsmap=self.NSMAP)
title = etree.SubElement(feed, 'title')
title.set('type', 'text')
title.text = feed_title
# Set this updated to the most recently updated version
recent = self._get_most_recent_update(versions)
etree.SubElement(feed, 'updated').text = recent
etree.SubElement(feed, 'id').text = feed_id
link = etree.SubElement(feed, 'link')
link.set('rel', 'self')
link.set('href', feed_id)
author = etree.SubElement(feed, 'author')
etree.SubElement(author, 'name').text = 'Rackspace'
etree.SubElement(author, 'uri').text = 'http://www.rackspace.com/'
for version in versions:
feed.append(self._create_version_entry(version))
return feed
def _create_version_entry(self, version):
entry = etree.Element('entry')
etree.SubElement(entry, 'id').text = version['links'][0]['href']
title = etree.SubElement(entry, 'title')
title.set('type', 'text')
title.text = 'Version %s' % version['id']
etree.SubElement(entry, 'updated').text = version['updated']
for link in version['links']:
link_elem = etree.SubElement(entry, 'link')
link_elem.set('rel', link['rel'])
link_elem.set('href', link['href'])
if 'type' in link:
link_elem.set('type', link['type'])
content = etree.SubElement(entry, 'content')
content.set('type', 'text')
content.text = 'Version %s %s (%s)' % (version['id'],
version['status'],
version['updated'])
return entry
class VersionsAtomSerializer(AtomSerializer):
def default(self, data):
versions = data['versions']
feed_id = self._get_base_url(versions[0]['links'][0]['href'])
feed = self._create_feed(versions, 'Available API Versions', feed_id)
return self._to_xml(feed)
class VersionAtomSerializer(AtomSerializer):
def default(self, data):
version = data['version']
feed_id = version['links'][0]['href']
feed = self._create_feed([version], 'About This Version', feed_id)
return self._to_xml(feed)
class Versions(wsgi.Resource):
def __init__(self):
super(Versions, self).__init__(None)
@wsgi.serializers(xml=VersionsTemplate,
atom=VersionsAtomSerializer)
def index(self, req):
"""Return all versions."""
builder = views_versions.get_view_builder(req)
return builder.build_versions(get_supported_versions())
@wsgi.serializers(xml=ChoicesTemplate)
@wsgi.response(300)
def multi(self, req):
"""Return multiple choices."""
@ -270,8 +115,6 @@ class Versions(wsgi.Resource):
class ShareVersionV1(object):
@wsgi.serializers(xml=VersionTemplate,
atom=VersionAtomSerializer)
def show(self, req):
builder = views_versions.get_view_builder(req)
return builder.build_version(_KNOWN_VERSIONS['v1.0'])

View File

@ -1,913 +0,0 @@
# Copyright 2011 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.
import os.path
from lxml import etree
import six
from manila.i18n import _
from manila import utils
XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
XMLNS_COMMON_V10 = 'http://docs.openstack.org/common/api/v1.0'
XMLNS_ATOM = 'http://www.w3.org/2005/Atom'
XMLNS_VOLUME_V1 = 'http://docs.openstack.org/volume/api/v1'
XMLNS_VOLUME_V2 = ('http://docs.openstack.org/api/openstack-volume/2.0/'
'content')
XMLNS_SHARE_V1 = ''
def validate_schema(xml, schema_name):
if isinstance(xml, str):
xml = etree.fromstring(xml)
base_path = 'manila/api/schemas/v1.1/'
if schema_name in ('atom', 'atom-link'):
base_path = 'manila/api/schemas/'
schema_path = os.path.join(utils.maniladir(),
'%s%s.rng' % (base_path, schema_name))
schema_doc = etree.parse(schema_path)
relaxng = etree.RelaxNG(schema_doc)
relaxng.assertValid(xml)
class Selector(object):
"""Selects datum to operate on from an object."""
def __init__(self, *chain):
"""Initialize the selector.
Each argument is a subsequent index into the object.
"""
self.chain = chain
def __repr__(self):
"""Return a representation of the selector."""
return "Selector" + repr(self.chain)
def __call__(self, obj, do_raise=False):
"""Select a datum to operate on.
Selects the relevant datum within the object.
:param obj: The object from which to select the object.
:param do_raise: If False (the default), return None if the
indexed datum does not exist. Otherwise,
raise a KeyError.
"""
# Walk the selector list
for elem in self.chain:
# If it's callable, call it
if callable(elem):
obj = elem(obj)
else:
# Use indexing
try:
obj = obj[elem]
except (KeyError, IndexError):
# No sense going any further
if do_raise:
# Convert to a KeyError, for consistency
raise KeyError(elem)
return None
# Return the finally-selected object
return obj
def get_items(obj):
"""Get items in obj."""
return list(obj.items())
class EmptyStringSelector(Selector):
"""Returns the empty string if Selector would return None."""
def __call__(self, obj, do_raise=False):
"""Returns empty string if the selected value does not exist."""
try:
return super(EmptyStringSelector, self).__call__(obj, True)
except KeyError:
return ""
class ConstantSelector(object):
"""Returns a constant."""
def __init__(self, value):
"""Initialize the selector.
:param value: The value to return.
"""
self.value = value
def __repr__(self):
"""Return a representation of the selector."""
return repr(self.value)
def __call__(self, _obj, _do_raise=False):
"""Select a datum to operate on.
Returns a constant value. Compatible with
Selector.__call__().
"""
return self.value
class TemplateElement(object):
"""Represent an element in the template."""
def __init__(self, tag, attrib=None, selector=None, subselector=None,
**extra):
"""Initialize an element.
Initializes an element in the template. Keyword arguments
specify attributes to be set on the element; values must be
callables. See TemplateElement.set() for more information.
:param tag: The name of the tag to create.
:param attrib: An optional dictionary of element attributes.
:param selector: An optional callable taking an object and
optional boolean do_raise indicator and
returning the object bound to the element.
:param subselector: An optional callable taking an object and
optional boolean do_raise indicator and
returning the object bound to the element.
This is used to further refine the datum
object returned by selector in the event
that it is a list of objects.
"""
# Convert selector into a Selector
if selector is None:
selector = Selector()
elif not callable(selector):
selector = Selector(selector)
# Convert subselector into a Selector
if subselector is not None and not callable(subselector):
subselector = Selector(subselector)
self.tag = tag
self.selector = selector
self.subselector = subselector
self.attrib = {}
self._text = None
self._children = []
self._childmap = {}
# Run the incoming attributes through set() so that they
# become selectorized
if not attrib:
attrib = {}
attrib.update(extra)
for k, v in attrib.items():
self.set(k, v)
def __repr__(self):
"""Return a representation of the template element."""
return ('<%s.%s %r at %#x>' %
(self.__class__.__module__, self.__class__.__name__,
self.tag, id(self)))
def __len__(self):
"""Return the number of child elements."""
return len(self._children)
def __contains__(self, key):
"""Determine whether a child node named by key exists."""
return key in self._childmap
def __getitem__(self, idx):
"""Retrieve a child node by index or name."""
if isinstance(idx, six.string_types):
# Allow access by node name
return self._childmap[idx]
else:
return self._children[idx]
def append(self, elem):
"""Append a child to the element."""
# Unwrap templates...
elem = elem.unwrap()
# Avoid duplications
if elem.tag in self._childmap:
raise KeyError(elem.tag)
self._children.append(elem)
self._childmap[elem.tag] = elem
def extend(self, elems):
"""Append children to the element."""
# Pre-evaluate the elements
elemmap = {}
elemlist = []
for elem in elems:
# Unwrap templates...
elem = elem.unwrap()
# Avoid duplications
if elem.tag in self._childmap or elem.tag in elemmap:
raise KeyError(elem.tag)
elemmap[elem.tag] = elem
elemlist.append(elem)
# Update the children
self._children.extend(elemlist)
self._childmap.update(elemmap)
def insert(self, idx, elem):
"""Insert a child element at the given index."""
# Unwrap templates...
elem = elem.unwrap()
# Avoid duplications
if elem.tag in self._childmap:
raise KeyError(elem.tag)
self._children.insert(idx, elem)
self._childmap[elem.tag] = elem
def remove(self, elem):
"""Remove a child element."""
# Unwrap templates...
elem = elem.unwrap()
# Check if element exists
if elem.tag not in self._childmap or self._childmap[elem.tag] != elem:
raise ValueError(_('element is not a child'))
self._children.remove(elem)
del self._childmap[elem.tag]
def get(self, key):
"""Get an attribute.
Returns a callable which performs datum selection.
:param key: The name of the attribute to get.
"""
return self.attrib[key]
def set(self, key, value=None):
"""Set an attribute.
:param key: The name of the attribute to set.
:param value: A callable taking an object and optional boolean
do_raise indicator and returning the datum bound
to the attribute. If None, a Selector() will be
constructed from the key. If a string, a
Selector() will be constructed from the string.
"""
# Convert value to a selector
if value is None:
value = Selector(key)
elif not callable(value):
value = Selector(value)
self.attrib[key] = value
def keys(self):
"""Return the attribute names."""
return self.attrib.keys()
def items(self):
"""Return the attribute names and values."""
return self.attrib.items()
def unwrap(self):
"""Unwraps a template to return a template element."""
# We are a template element
return self
def wrap(self):
"""Wraps a template element to return a template."""
# Wrap in a basic Template
return Template(self)
def apply(self, elem, obj):
"""Apply text and attributes to an etree.Element.
Applies the text and attribute instructions in the template
element to an etree.Element instance.
:param elem: An etree.Element instance.
:param obj: The base object associated with this template
element.
"""
# Start with the text...
if self.text is not None:
elem.text = six.text_type(self.text(obj))
# Now set up all the attributes...
for key, value in self.attrib.items():
try:
elem.set(key, six.text_type(value(obj, True)))
except KeyError:
# Attribute has no value, so don't include it
pass
def _render(self, parent, datum, patches, nsmap):
"""Internal rendering.
Renders the template node into an etree.Element object.
Returns the etree.Element object.
:param parent: The parent etree.Element instance.
:param datum: The datum associated with this template element.
:param patches: A list of other template elements that must
also be applied.
:param nsmap: An optional namespace dictionary to be
associated with the etree.Element instance.
"""
# Allocate a node
if callable(self.tag):
tagname = self.tag(datum)
else:
tagname = self.tag
elem = etree.Element(tagname, nsmap=nsmap)
# If we have a parent, append the node to the parent
if parent is not None:
parent.append(elem)
# If the datum is None, do nothing else
if datum is None:
return elem
# Apply this template element to the element
self.apply(elem, datum)
# Additionally, apply the patches
for patch in patches:
patch.apply(elem, datum)
# We have fully rendered the element; return it
return elem
def render(self, parent, obj, patches=[], nsmap=None):
"""Render an object.
Renders an object against this template node. Returns a list
of two-item tuples, where the first item is an etree.Element
instance and the second item is the datum associated with that
instance.
:param parent: The parent for the etree.Element instances.
:param obj: The object to render this template element
against.
:param patches: A list of other template elements to apply
when rendering this template element.
:param nsmap: An optional namespace dictionary to attach to
the etree.Element instances.
"""
# First, get the datum we're rendering
data = None if obj is None else self.selector(obj)
# Check if we should render at all
if not self.will_render(data):
return []
elif data is None:
return [(self._render(parent, None, patches, nsmap), None)]
# Make the data into a list if it isn't already
if not isinstance(data, list):
data = [data]
elif parent is None:
raise ValueError(_('root element selecting a list'))
# Render all the elements
elems = []
for datum in data:
if self.subselector is not None:
datum = self.subselector(datum)
elems.append((self._render(parent, datum, patches, nsmap), datum))
# Return all the elements rendered, as well as the
# corresponding datum for the next step down the tree
return elems
def will_render(self, datum):
"""Hook method.
An overridable hook method to determine whether this template
element will be rendered at all. By default, returns False
(inhibiting rendering) if the datum is None.
:param datum: The datum associated with this template element.
"""
# Don't render if datum is None
return datum is not None
def _text_get(self):
"""Template element text.
Either None or a callable taking an object and optional
boolean do_raise indicator and returning the datum bound to
the text of the template element.
"""
return self._text
def _text_set(self, value):
# Convert value to a selector
if value is not None and not callable(value):
value = Selector(value)
self._text = value
def _text_del(self):
self._text = None
text = property(_text_get, _text_set, _text_del)
def tree(self):
"""Return string representation of the template tree.
Returns a representation of the template rooted at this
element as a string, suitable for inclusion in debug logs.
"""
# Build the inner contents of the tag...
contents = [self.tag, '!selector=%r' % self.selector]
# Add the text...
if self.text is not None:
contents.append('!text=%r' % self.text)
# Add all the other attributes
for key, value in self.attrib.items():
contents.append('%s=%r' % (key, value))
# If there are no children, return it as a closed tag
if len(self) == 0:
return '<%s/>' % ' '.join([str(i) for i in contents])
# OK, recurse to our children
children = [c.tree() for c in self]
# Return the result
return ('<%s>%s</%s>' %
(' '.join(contents), ''.join(children), self.tag))
def SubTemplateElement(parent, tag, attrib=None, selector=None,
subselector=None, **extra):
"""Create a template element as a child of another.
Corresponds to the etree.SubElement interface. Parameters are as
for TemplateElement, with the addition of the parent.
"""
# Convert attributes
attrib = attrib or {}
attrib.update(extra)
# Get a TemplateElement
elem = TemplateElement(tag, attrib=attrib, selector=selector,
subselector=subselector)
# Append the parent safely
if parent is not None:
parent.append(elem)
return elem
class Template(object):
"""Represent a template."""
def __init__(self, root, nsmap=None):
"""Initialize a template.
:param root: The root element of the template.
:param nsmap: An optional namespace dictionary to be
associated with the root element of the
template.
"""
self.root = root.unwrap() if root is not None else None
self.nsmap = nsmap or {}
self.serialize_options = dict(encoding='UTF-8', xml_declaration=True)
def _serialize(self, parent, obj, siblings, nsmap=None):
"""Internal serialization.
Recursive routine to build a tree of etree.Element instances
from an object based on the template. Returns the first
etree.Element instance rendered, or None.
:param parent: The parent etree.Element instance. Can be
None.
:param obj: The object to render.
:param siblings: The TemplateElement instances against which
to render the object.
:param nsmap: An optional namespace dictionary to be
associated with the etree.Element instance
rendered.
"""
# First step, render the element
elems = siblings[0].render(parent, obj, siblings[1:], nsmap)
# Now, recurse to all child elements
seen = set()
for idx, sibling in enumerate(siblings):
for child in sibling:
# Have we handled this child already?
if child.tag in seen:
continue
seen.add(child.tag)
# Determine the child's siblings
nieces = [child]
for sib in siblings[idx + 1:]:
if child.tag in sib:
nieces.append(sib[child.tag])
# Now we recurse for every data element
for elem, datum in elems:
self._serialize(elem, datum, nieces)
# Return the first element; at the top level, this will be the
# root element
if elems:
return elems[0][0]
def serialize(self, obj, *args, **kwargs):
"""Serialize an object.
Serializes an object against the template. Returns a string
with the serialized XML. Positional and keyword arguments are
passed to etree.tostring().
:param obj: The object to serialize.
"""
elem = self.make_tree(obj)
if elem is None:
return ''
for k, v in self.serialize_options.items():
kwargs.setdefault(k, v)
# Serialize it into XML
return etree.tostring(elem, *args, **kwargs)
def make_tree(self, obj):
"""Create a tree.
Serializes an object against the template. Returns an Element
node with appropriate children.
:param obj: The object to serialize.
"""
# If the template is empty, return the empty string
if self.root is None:
return None
# Get the siblings and nsmap of the root element
siblings = self._siblings()
nsmap = self._nsmap()
# Form the element tree
return self._serialize(None, obj, siblings, nsmap)
def _siblings(self):
"""Hook method for computing root siblings.
An overridable hook method to return the siblings of the root
element. By default, this is the root element itself.
"""
return [self.root]
def _nsmap(self):
"""Hook method for computing the namespace dictionary.
An overridable hook method to return the namespace dictionary.
"""
return self.nsmap.copy()
def unwrap(self):
"""Unwraps a template to return a template element."""
# Return the root element
return self.root
def wrap(self):
"""Wraps a template element to return a template."""
# We are a template
return self
def apply(self, master):
"""Hook method for determining slave applicability.
An overridable hook method used to determine if this template
is applicable as a slave to a given master template.
:param master: The master template to test.
"""
return True
def tree(self):
"""Return string representation of the template tree.
Returns a representation of the template as a string, suitable
for inclusion in debug logs.
"""
return "%r: %s" % (self, self.root.tree())
class MasterTemplate(Template):
"""Represent a master template.
Master templates are versioned derivatives of templates that
additionally allow slave templates to be attached. Slave
templates allow modification of the serialized result without
directly changing the master.
"""
def __init__(self, root, version, nsmap=None):
"""Initialize a master template.
:param root: The root element of the template.
:param version: The version number of the template.
:param nsmap: An optional namespace dictionary to be
associated with the root element of the
template.
"""
super(MasterTemplate, self).__init__(root, nsmap)
self.version = version
self.slaves = []
def __repr__(self):
"""Return string representation of the template."""
return ("<%s.%s object version %s at %#x>" %
(self.__class__.__module__, self.__class__.__name__,
self.version, id(self)))
def _siblings(self):
"""Hook method for computing root siblings.
An overridable hook method to return the siblings of the root
element. This is the root element plus the root elements of
all the slave templates.
"""
return [self.root] + [slave.root for slave in self.slaves]
def _nsmap(self):
"""Hook method for computing the namespace dictionary.
An overridable hook method to return the namespace dictionary.
The namespace dictionary is computed by taking the master
template's namespace dictionary and updating it from all the
slave templates.
"""
nsmap = self.nsmap.copy()
for slave in self.slaves:
nsmap.update(slave._nsmap())
return nsmap
def attach(self, *slaves):
"""Attach one or more slave templates.
Attaches one or more slave templates to the master template.
Slave templates must have a root element with the same tag as
the master template. The slave template's apply() method will
be called to determine if the slave should be applied to this
master; if it returns False, that slave will be skipped.
(This allows filtering of slaves based on the version of the
master template.)
"""
slave_list = []
for slave in slaves:
slave = slave.wrap()
# Make sure we have a tree match
if slave.root.tag != self.root.tag:
slavetag = slave.root.tag
mastertag = self.root.tag
msg = _("Template tree mismatch; adding slave %(slavetag)s "
"to master %(mastertag)s") % {
"slavetag": slavetag,
"mastertag": mastertag
}
raise ValueError(msg)
# Make sure slave applies to this template
if not slave.apply(self):
continue
slave_list.append(slave)
# Add the slaves
self.slaves.extend(slave_list)
def copy(self):
"""Return a copy of this master template."""
# Return a copy of the MasterTemplate
tmp = self.__class__(self.root, self.version, self.nsmap)
tmp.slaves = self.slaves[:]
return tmp
class SlaveTemplate(Template):
"""Represent a slave template.
Slave templates are versioned derivatives of templates. Each
slave has a minimum version and optional maximum version of the
master template to which they can be attached.
"""
def __init__(self, root, min_vers, max_vers=None, nsmap=None):
"""Initialize a slave template.
:param root: The root element of the template.
:param min_vers: The minimum permissible version of the master
template for this slave template to apply.
:param max_vers: An optional upper bound for the master
template version.
:param nsmap: An optional namespace dictionary to be
associated with the root element of the
template.
"""
super(SlaveTemplate, self).__init__(root, nsmap)
self.min_vers = min_vers
self.max_vers = max_vers
def __repr__(self):
"""Return string representation of the template."""
return ("<%s.%s object versions %s-%s at %#x>" %
(self.__class__.__module__, self.__class__.__name__,
self.min_vers, self.max_vers, id(self)))
def apply(self, master):
"""Hook method for determining slave applicability.
An overridable hook method used to determine if this template
is applicable as a slave to a given master template. This
version requires the master template to have a version number
between min_vers and max_vers.
:param master: The master template to test.
"""
# Does the master meet our minimum version requirement?
if master.version < self.min_vers:
return False
# How about our maximum version requirement?
if self.max_vers is not None and master.version > self.max_vers:
return False
return True
class TemplateBuilder(object):
"""Template builder.
This class exists to allow templates to be lazily built without
having to build them each time they are needed. It must be
subclassed, and the subclass must implement the construct()
method, which must return a Template (or subclass) instance. The
constructor will always return the template returned by
construct(), or, if it has a copy() method, a copy of that
template.
"""
_tmpl = None
def __new__(cls, copy=True):
"""Construct and return a template.
:param copy: If True (the default), a copy of the template
will be constructed and returned, if possible.
"""
# Do we need to construct the template?
if cls._tmpl is None:
tmp = super(TemplateBuilder, cls).__new__(cls)
# Construct the template
cls._tmpl = tmp.construct()
# If the template has a copy attribute, return the result of
# calling it
if copy and hasattr(cls._tmpl, 'copy'):
return cls._tmpl.copy()
# Return the template
return cls._tmpl
def construct(self):
"""Construct a template.
Called to construct a template instance, which it must return.
Only called once.
"""
raise NotImplementedError(_("subclasses must implement construct()!"))
def make_links(parent, selector=None):
"""Attach an Atom <links> element to the parent."""
elem = SubTemplateElement(parent, '{%s}link' % XMLNS_ATOM,
selector=selector)
elem.set('rel')
elem.set('type')
elem.set('href')
# Just for completeness...
return elem
def make_flat_dict(name, selector=None, subselector=None, ns=None):
"""Utility for simple XML templates.
Utility for simple XML templates that traditionally used
XMLDictSerializer with no metadata. Returns a template element
where the top-level element has the given tag name, and where
sub-elements have tag names derived from the object's keys and
text derived from the object's values. This only works for flat
dictionary objects, not dictionaries containing nested lists or
dictionaries.
"""
# Set up the names we need...
if ns is None:
elemname = name
tagname = Selector(0)
else:
elemname = '{%s}%s' % (ns, name)
tagname = lambda obj, do_raise=False: '{%s}%s' % (ns, obj[0])
if selector is None:
selector = name
# Build the root element
root = TemplateElement(elemname, selector=selector,
subselector=subselector)
# Build an element to represent all the keys and values
elem = SubTemplateElement(root, tagname, selector=get_items)
elem.text = 1
# Return the template
return root

View File

@ -13,14 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from xml.dom import minidom
from oslo_serialization import jsonutils
import webob
import webob.dec
import webob.exc
from manila.api import common
from manila.api.openstack import wsgi
from manila import test
@ -88,9 +85,9 @@ class TestFaults(test.TestCase):
def raiser(req):
raise wsgi.Fault(webob.exc.HTTPNotFound(explanation='whut?'))
req = webob.Request.blank('/.xml')
req = webob.Request.blank('/.json')
resp = req.get_response(raiser)
self.assertEqual(resp.content_type, "application/xml")
self.assertEqual(resp.content_type, "application/json")
self.assertEqual(resp.status_int, 404)
self.assertTrue('whut?' in resp.body)
@ -100,9 +97,9 @@ class TestFaults(test.TestCase):
def raiser(req):
raise wsgi.Fault(webob.exc.HTTPForbidden(explanation='whut?'))
req = webob.Request.blank('/.xml')
req = webob.Request.blank('/.json')
resp = req.get_response(raiser)
self.assertEqual(resp.content_type, "application/xml")
self.assertEqual(resp.content_type, "application/json")
self.assertEqual(resp.status_int, 403)
self.assertTrue('resizeNotAllowed' not in resp.body)
self.assertTrue('forbidden' in resp.body)
@ -111,104 +108,3 @@ class TestFaults(test.TestCase):
"""Ensure the status_int is set correctly on faults."""
fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='what?'))
self.assertEqual(fault.status_int, 400)
def test_xml_serializer(self):
"""Ensure that a v1.1 request responds with a v1 xmlns."""
request = webob.Request.blank('/v1',
headers={"Accept": "application/xml"})
fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='scram'))
response = request.get_response(fault)
self.assertTrue(common.XML_NS_V1 in response.body)
self.assertEqual(response.content_type, "application/xml")
self.assertEqual(response.status_int, 400)
class FaultsXMLSerializationTestV11(test.TestCase):
"""Tests covering `manila.api.openstack.faults:Fault` class."""
def _prepare_xml(self, xml_string):
xml_string = xml_string.replace(" ", "")
xml_string = xml_string.replace("\n", "")
xml_string = xml_string.replace("\t", "")
return xml_string
def test_400_fault(self):
metadata = {'attributes': {"badRequest": 'code'}}
serializer = wsgi.XMLDictSerializer(metadata=metadata,
xmlns=common.XML_NS_V1)
fixture = {
"badRequest": {
"message": "scram",
"code": 400,
},
}
output = serializer.serialize(fixture)
actual = minidom.parseString(self._prepare_xml(output))
expected = minidom.parseString(self._prepare_xml("""
<badRequest code="400" xmlns="%s">
<message>scram</message>
</badRequest>
""") % common.XML_NS_V1)
self.assertEqual(expected.toxml(), actual.toxml())
def test_413_fault(self):
metadata = {'attributes': {"overLimit": 'code'}}
serializer = wsgi.XMLDictSerializer(metadata=metadata,
xmlns=common.XML_NS_V1)
fixture = {
"overLimit": {
"message": "sorry",
"code": 413,
"retryAfter": 4,
},
}
output = serializer.serialize(fixture)
result = minidom.parseString(self._prepare_xml(output))
# result has 1 child - overLimit
self.assertEqual(result.firstChild, result.lastChild)
self.assertEqual(result.firstChild.tagName, 'overLimit')
# overLimit has attrs code = '413' and xmlns = common.XML_NS_V1
self.assertEqual(result.firstChild.getAttribute('code'), '413')
self.assertEqual(result.firstChild.getAttribute('xmlns'),
common.XML_NS_V1)
# overLimit has childs message = 'sorry' and retryAfter = '4'
message = result.firstChild.getElementsByTagName('message')
retry_after = result.firstChild.getElementsByTagName('retryAfter')
self.assertEqual(len(message), 1)
self.assertEqual(len(retry_after), 1)
self.assertEqual(message[0].toxml(), '<message>sorry</message>')
self.assertEqual(retry_after[0].toxml(), '<retryAfter>4</retryAfter>')
def test_404_fault(self):
metadata = {'attributes': {"itemNotFound": 'code'}}
serializer = wsgi.XMLDictSerializer(metadata=metadata,
xmlns=common.XML_NS_V1)
fixture = {
"itemNotFound": {
"message": "sorry",
"code": 404,
},
}
output = serializer.serialize(fixture)
actual = minidom.parseString(self._prepare_xml(output))
expected = minidom.parseString(self._prepare_xml("""
<itemNotFound code="404" xmlns="%s">
<message>sorry</message>
</itemNotFound>
""") % common.XML_NS_V1)
self.assertEqual(expected.toxml(), actual.toxml())

View File

@ -29,14 +29,13 @@ class RequestTest(test.TestCase):
self.assertEqual(result, "application/json")
def test_content_type_from_accept(self):
for content_type in ('application/xml',
'application/vnd.openstack.volume+xml',
'application/json',
'application/vnd.openstack.volume+json'):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = content_type
result = request.best_match_content_type()
self.assertEqual(result, content_type)
content_type = 'application/json'
request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = content_type
result = request.best_match_content_type()
self.assertEqual(result, content_type)
def test_content_type_from_accept_best(self):
request = wsgi.Request.blank('/tests/123')
@ -48,13 +47,9 @@ class RequestTest(test.TestCase):
request.headers["Accept"] = ("application/json; q=0.3, "
"application/xml; q=0.9")
result = request.best_match_content_type()
self.assertEqual(result, "application/xml")
self.assertEqual(result, "application/json")
def test_content_type_from_query_extension(self):
request = wsgi.Request.blank('/tests/123.xml')
result = request.best_match_content_type()
self.assertEqual(result, "application/xml")
request = wsgi.Request.blank('/tests/123.json')
result = request.best_match_content_type()
self.assertEqual(result, "application/json")
@ -63,12 +58,6 @@ class RequestTest(test.TestCase):
result = request.best_match_content_type()
self.assertEqual(result, "application/json")
def test_content_type_accept_and_query_extension(self):
request = wsgi.Request.blank('/tests/123.xml')
request.headers["Accept"] = "application/json"
result = request.best_match_content_type()
self.assertEqual(result, "application/xml")
def test_content_type_accept_default(self):
request = wsgi.Request.blank('/tests/123.unsupported')
request.headers["Accept"] = "application/unsupported1"
@ -162,16 +151,6 @@ class DictSerializerTest(test.TestCase):
self.assertEqual(serializer.serialize({}, 'update'), '')
class XMLDictSerializerTest(test.TestCase):
def test_xml(self):
input_dict = dict(servers=dict(a=(2, 3)))
expected_xml = '<serversxmlns="asdf"><a>(2,3)</a></servers>'
serializer = wsgi.XMLDictSerializer(xmlns="asdf")
result = serializer.serialize(input_dict)
result = result.replace('\n', '').replace(' ', '')
self.assertEqual(result, expected_xml)
class JSONDictSerializerTest(test.TestCase):
def test_json(self):
input_dict = dict(servers=dict(a=(2, 3)))
@ -211,37 +190,6 @@ class JSONDeserializerTest(test.TestCase):
self.assertEqual(deserializer.deserialize(data), as_dict)
class XMLDeserializerTest(test.TestCase):
def test_xml(self):
xml = """
<a a1="1" a2="2">
<bs><b>1</b><b>2</b><b>3</b><b><c c1="1"/></b></bs>
<d><e>1</e></d>
<f>1</f>
</a>
""".strip()
as_dict = {
'body': {
'a': {
'a1': '1',
'a2': '2',
'bs': ['1', '2', '3', {'c': {'c1': '1'}}],
'd': {'e': '1'},
'f': '1',
},
},
}
metadata = {'plurals': {'bs': 'b', 'ts': 't'}}
deserializer = wsgi.XMLDeserializer(metadata=metadata)
self.assertEqual(deserializer.deserialize(xml), as_dict)
def test_xml_empty(self):
xml = """<a></a>"""
as_dict = {"body": {"a": {}}}
deserializer = wsgi.XMLDeserializer()
self.assertEqual(deserializer.deserialize(xml), as_dict)
class ResourceTest(test.TestCase):
def test_resource_call(self):
class Controller(object):
@ -299,19 +247,6 @@ class ResourceTest(test.TestCase):
'{"fooAction": true}')
self.assertEqual(controller._action_foo, method)
def test_get_method_action_xml(self):
class Controller(wsgi.Controller):
@wsgi.action('fooAction')
def _action_foo(self, req, id, body):
return body
controller = Controller()
resource = wsgi.Resource(controller)
method, extensions = resource.get_method(None, 'action',
'application/xml',
'<fooAction>true</fooAction>')
self.assertEqual(controller._action_foo, method)
def test_get_method_action_bad_body(self):
class Controller(wsgi.Controller):
@wsgi.action('fooAction')
@ -467,20 +402,15 @@ class ResourceTest(test.TestCase):
def deserialize(self, body):
return 'json'
class XMLDeserializer(object):
def deserialize(self, body):
return 'xml'
class Controller(object):
@wsgi.deserializers(xml=XMLDeserializer)
def index(self, req, pants=None):
return pants
controller = Controller()
resource = wsgi.Resource(controller, json=JSONDeserializer)
obj = resource.deserialize(controller.index, 'application/xml', 'foo')
self.assertEqual(obj, 'xml')
obj = resource.deserialize(controller.index, 'application/json', 'foo')
self.assertEqual(obj, 'json')
def test_register_actions(self):
class Controller(object):

View File

@ -16,7 +16,6 @@
import ddt
import iso8601
from lxml import etree
import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
@ -24,7 +23,6 @@ import webob
from manila.api import extensions
from manila.api.v1 import router
from manila.api import xmlutil
from manila import policy
from manila import test
@ -70,8 +68,7 @@ class ExtensionControllerTest(ExtensionTestCase):
(fox_ext, ) = [
x for x in data['extensions'] if x['alias'] == 'FOXNSOX']
self.assertEqual(
fox_ext, {'namespace': 'http://www.fox.in.socks/api/ext/pie/v1.0',
'name': 'Fox In Socks',
fox_ext, {'name': 'Fox In Socks',
'updated': '2011-01-22T13:25:27-06:00',
'description': 'The Fox In Socks Extension.',
'alias': 'FOXNSOX',
@ -93,8 +90,7 @@ class ExtensionControllerTest(ExtensionTestCase):
data = jsonutils.loads(response.body)
self.assertEqual(
data['extension'],
{"namespace": "http://www.fox.in.socks/api/ext/pie/v1.0",
"name": "Fox In Socks",
{"name": "Fox In Socks",
"updated": "2011-01-22T13:25:27-06:00",
"description": "The Fox In Socks Extension.",
"alias": "FOXNSOX",
@ -106,55 +102,6 @@ class ExtensionControllerTest(ExtensionTestCase):
response = request.get_response(app)
self.assertEqual(404, response.status_int)
def test_list_extensions_xml(self):
app = router.APIRouter()
request = webob.Request.blank("/fake/extensions")
request.accept = "application/xml"
response = request.get_response(app)
self.assertEqual(200, response.status_int)
root = etree.XML(response.body)
self.assertEqual(root.tag.split('extensions')[0], NS)
# Make sure we have all the extensions, extras extensions being OK.
exts = root.findall('{0}extension'.format(NS))
self.assertTrue(len(exts) >= len(self.ext_list))
# Make sure that at least Fox in Sox is correct.
(fox_ext, ) = [x for x in exts if x.get('alias') == 'FOXNSOX']
self.assertEqual(fox_ext.get('name'), 'Fox In Socks')
self.assertEqual(
fox_ext.get('namespace'),
'http://www.fox.in.socks/api/ext/pie/v1.0')
self.assertEqual(fox_ext.get('updated'), '2011-01-22T13:25:27-06:00')
self.assertEqual(
fox_ext.findtext('{0}description'.format(NS)),
'The Fox In Socks Extension.')
xmlutil.validate_schema(root, 'extensions')
def test_get_extension_xml(self):
app = router.APIRouter()
request = webob.Request.blank("/fake/extensions/FOXNSOX")
request.accept = "application/xml"
response = request.get_response(app)
self.assertEqual(200, response.status_int)
xml = response.body
root = etree.XML(xml)
self.assertEqual(root.tag.split('extension')[0], NS)
self.assertEqual(root.get('alias'), 'FOXNSOX')
self.assertEqual(root.get('name'), 'Fox In Socks')
self.assertEqual(
root.get('namespace'),
'http://www.fox.in.socks/api/ext/pie/v1.0')
self.assertEqual(root.get('updated'), '2011-01-22T13:25:27-06:00')
self.assertEqual(
root.findtext('{0}description'.format(NS)),
'The Fox In Socks Extension.')
xmlutil.validate_schema(root, 'extension')
@ddt.ddt
class ExtensionAuthorizeTestCase(test.TestCase):

View File

@ -1,714 +0,0 @@
# Copyright 2011 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.
from xml.dom import minidom
from lxml import etree
from manila.api import xmlutil
from manila import test
class SelectorTest(test.TestCase):
obj_for_test = {'test': {'name': 'test',
'values': [1, 2, 3],
'attrs': {'foo': 1,
'bar': 2,
'baz': 3, }, }, }
def test_empty_selector(self):
sel = xmlutil.Selector()
self.assertEqual(len(sel.chain), 0)
self.assertEqual(sel(self.obj_for_test), self.obj_for_test)
def test_dict_selector(self):
sel = xmlutil.Selector('test')
self.assertEqual(len(sel.chain), 1)
self.assertEqual(sel.chain[0], 'test')
self.assertEqual(sel(self.obj_for_test),
self.obj_for_test['test'])
def test_datum_selector(self):
sel = xmlutil.Selector('test', 'name')
self.assertEqual(len(sel.chain), 2)
self.assertEqual(sel.chain[0], 'test')
self.assertEqual(sel.chain[1], 'name')
self.assertEqual(sel(self.obj_for_test), 'test')
def test_list_selector(self):
sel = xmlutil.Selector('test', 'values', 0)
self.assertEqual(len(sel.chain), 3)
self.assertEqual(sel.chain[0], 'test')
self.assertEqual(sel.chain[1], 'values')
self.assertEqual(sel.chain[2], 0)
self.assertEqual(sel(self.obj_for_test), 1)
def test_items_selector(self):
sel = xmlutil.Selector('test', 'attrs', xmlutil.get_items)
self.assertEqual(len(sel.chain), 3)
self.assertEqual(sel.chain[2], xmlutil.get_items)
for key, val in sel(self.obj_for_test):
self.assertEqual(self.obj_for_test['test']['attrs'][key], val)
def test_missing_key_selector(self):
sel = xmlutil.Selector('test2', 'attrs')
self.assertEqual(sel(self.obj_for_test), None)
self.assertRaises(KeyError, sel, self.obj_for_test, True)
def test_constant_selector(self):
sel = xmlutil.ConstantSelector('Foobar')
self.assertEqual(sel.value, 'Foobar')
self.assertEqual(sel(self.obj_for_test), 'Foobar')
class TemplateElementTest(test.TestCase):
def test_element_initial_attributes(self):
# Create a template element with some attributes
elem = xmlutil.TemplateElement('test', attrib=dict(a=1, b=2, c=3),
c=4, d=5, e=6)
# Verify all the attributes are as expected
expected = dict(a=1, b=2, c=4, d=5, e=6)
for k, v in expected.items():
self.assertEqual(elem.attrib[k].chain[0], v)
def test_element_get_attributes(self):
expected = dict(a=1, b=2, c=3)
# Create a template element with some attributes
elem = xmlutil.TemplateElement('test', attrib=expected)
# Verify that get() retrieves the attributes
for k, v in expected.items():
self.assertEqual(elem.get(k).chain[0], v)
def test_element_set_attributes(self):
attrs = dict(a=None, b='foo', c=xmlutil.Selector('foo', 'bar'))
# Create a bare template element with no attributes
elem = xmlutil.TemplateElement('test')
# Set the attribute values
for k, v in attrs.items():
elem.set(k, v)
# Now verify what got set
self.assertEqual(len(elem.attrib['a'].chain), 1)
self.assertEqual(elem.attrib['a'].chain[0], 'a')
self.assertEqual(len(elem.attrib['b'].chain), 1)
self.assertEqual(elem.attrib['b'].chain[0], 'foo')
self.assertEqual(elem.attrib['c'], attrs['c'])
def test_element_attribute_keys(self):
attrs = dict(a=1, b=2, c=3, d=4)
expected = set(attrs.keys())
# Create a template element with some attributes
elem = xmlutil.TemplateElement('test', attrib=attrs)
# Now verify keys
self.assertEqual(set(elem.keys()), expected)
def test_element_attribute_items(self):
expected = dict(a=xmlutil.Selector(1),
b=xmlutil.Selector(2),
c=xmlutil.Selector(3))
keys = set(expected.keys())
# Create a template element with some attributes
elem = xmlutil.TemplateElement('test', attrib=expected)
# Now verify items
for k, v in elem.items():
self.assertEqual(expected[k], v)
keys.remove(k)
# Did we visit all keys?
self.assertEqual(len(keys), 0)
def test_element_selector_none(self):
# Create a template element with no selector
elem = xmlutil.TemplateElement('test')
self.assertEqual(len(elem.selector.chain), 0)
def test_element_selector_string(self):
# Create a template element with a string selector
elem = xmlutil.TemplateElement('test', selector='test')
self.assertEqual(len(elem.selector.chain), 1)
self.assertEqual(elem.selector.chain[0], 'test')
def test_element_selector(self):
sel = xmlutil.Selector('a', 'b')
# Create a template element with an explicit selector
elem = xmlutil.TemplateElement('test', selector=sel)
self.assertEqual(elem.selector, sel)
def test_element_subselector_none(self):
# Create a template element with no subselector
elem = xmlutil.TemplateElement('test')
self.assertEqual(elem.subselector, None)
def test_element_subselector_string(self):
# Create a template element with a string subselector
elem = xmlutil.TemplateElement('test', subselector='test')
self.assertEqual(len(elem.subselector.chain), 1)
self.assertEqual(elem.subselector.chain[0], 'test')
def test_element_subselector(self):
sel = xmlutil.Selector('a', 'b')
# Create a template element with an explicit subselector
elem = xmlutil.TemplateElement('test', subselector=sel)
self.assertEqual(elem.subselector, sel)
def test_element_append_child(self):
# Create an element
elem = xmlutil.TemplateElement('test')
# Make sure the element starts off empty
self.assertEqual(len(elem), 0)
# Create a child element
child = xmlutil.TemplateElement('child')
# Append the child to the parent
elem.append(child)
# Verify that the child was added
self.assertEqual(len(elem), 1)
self.assertEqual(elem[0], child)
self.assertEqual('child' in elem, True)
self.assertEqual(elem['child'], child)
# Ensure that multiple children of the same name are rejected
child2 = xmlutil.TemplateElement('child')
self.assertRaises(KeyError, elem.append, child2)
def test_element_extend_children(self):
# Create an element
elem = xmlutil.TemplateElement('test')
# Make sure the element starts off empty
self.assertEqual(len(elem), 0)
# Create a few children
children = [xmlutil.TemplateElement('child1'),
xmlutil.TemplateElement('child2'),
xmlutil.TemplateElement('child3'), ]
# Extend the parent by those children
elem.extend(children)
# Verify that the children were added
self.assertEqual(len(elem), 3)
for idx in range(len(elem)):
self.assertEqual(children[idx], elem[idx])
self.assertEqual(children[idx].tag in elem, True)
self.assertEqual(elem[children[idx].tag], children[idx])
# Ensure that multiple children of the same name are rejected
children2 = [xmlutil.TemplateElement('child4'),
xmlutil.TemplateElement('child1'), ]
self.assertRaises(KeyError, elem.extend, children2)
# Also ensure that child4 was not added
self.assertEqual(len(elem), 3)
self.assertEqual(elem[-1].tag, 'child3')
def test_element_insert_child(self):
# Create an element
elem = xmlutil.TemplateElement('test')
# Make sure the element starts off empty
self.assertEqual(len(elem), 0)
# Create a few children
children = [xmlutil.TemplateElement('child1'),
xmlutil.TemplateElement('child2'),
xmlutil.TemplateElement('child3'), ]
# Extend the parent by those children
elem.extend(children)
# Create a child to insert
child = xmlutil.TemplateElement('child4')
# Insert it
elem.insert(1, child)
# Ensure the child was inserted in the right place
self.assertEqual(len(elem), 4)
children.insert(1, child)
for idx in range(len(elem)):
self.assertEqual(children[idx], elem[idx])
self.assertEqual(children[idx].tag in elem, True)
self.assertEqual(elem[children[idx].tag], children[idx])
# Ensure that multiple children of the same name are rejected
child2 = xmlutil.TemplateElement('child2')
self.assertRaises(KeyError, elem.insert, 2, child2)
def test_element_remove_child(self):
# Create an element
elem = xmlutil.TemplateElement('test')
# Make sure the element starts off empty
self.assertEqual(len(elem), 0)
# Create a few children
children = [xmlutil.TemplateElement('child1'),
xmlutil.TemplateElement('child2'),
xmlutil.TemplateElement('child3'), ]
# Extend the parent by those children
elem.extend(children)
# Create a test child to remove
child = xmlutil.TemplateElement('child2')
# Try to remove it
self.assertRaises(ValueError, elem.remove, child)
# Ensure that no child was removed
self.assertEqual(len(elem), 3)
# Now remove a legitimate child
elem.remove(children[1])
# Ensure that the child was removed
self.assertEqual(len(elem), 2)
self.assertEqual(elem[0], children[0])
self.assertEqual(elem[1], children[2])
self.assertEqual('child2' in elem, False)
# Ensure the child cannot be retrieved by name
def get_key(elem, key):
return elem[key]
self.assertRaises(KeyError, get_key, elem, 'child2')
def test_element_text(self):
# Create an element
elem = xmlutil.TemplateElement('test')
# Ensure that it has no text
self.assertEqual(elem.text, None)
# Try setting it to a string and ensure it becomes a selector
elem.text = 'test'
self.assertEqual(hasattr(elem.text, 'chain'), True)
self.assertEqual(len(elem.text.chain), 1)
self.assertEqual(elem.text.chain[0], 'test')
# Try resetting the text to None
elem.text = None
self.assertEqual(elem.text, None)
# Now make up a selector and try setting the text to that
sel = xmlutil.Selector()
elem.text = sel
self.assertEqual(elem.text, sel)
# Finally, try deleting the text and see what happens
del elem.text
self.assertEqual(elem.text, None)
def test_apply_attrs(self):
# Create a template element
attrs = dict(attr1=xmlutil.ConstantSelector(1),
attr2=xmlutil.ConstantSelector(2))
tmpl_elem = xmlutil.TemplateElement('test', attrib=attrs)
# Create an etree element
elem = etree.Element('test')
# Apply the template to the element
tmpl_elem.apply(elem, None)
# Now, verify the correct attributes were set
for k, v in elem.items():
self.assertEqual(str(attrs[k].value), v)
def test_apply_text(self):
# Create a template element
tmpl_elem = xmlutil.TemplateElement('test')
tmpl_elem.text = xmlutil.ConstantSelector(1)
# Create an etree element
elem = etree.Element('test')
# Apply the template to the element
tmpl_elem.apply(elem, None)
# Now, verify the text was set
self.assertEqual(str(tmpl_elem.text.value), elem.text)
def test__render(self):
attrs = dict(attr1=xmlutil.ConstantSelector(1),
attr2=xmlutil.ConstantSelector(2),
attr3=xmlutil.ConstantSelector(3))
# Create a master template element
master_elem = xmlutil.TemplateElement('test', attr1=attrs['attr1'])
# Create a couple of slave template element
slave_elems = [xmlutil.TemplateElement('test', attr2=attrs['attr2']),
xmlutil.TemplateElement('test', attr3=attrs['attr3']), ]
# Try the render
elem = master_elem._render(None, None, slave_elems, None)
# Verify the particulars of the render
self.assertEqual(elem.tag, 'test')
self.assertEqual(len(elem.nsmap), 0)
for k, v in elem.items():
self.assertEqual(str(attrs[k].value), v)
# Create a parent for the element to be rendered
parent = etree.Element('parent')
# Try the render again...
elem = master_elem._render(parent, None, slave_elems, dict(a='foo'))
# Verify the particulars of the render
self.assertEqual(len(parent), 1)
self.assertEqual(parent[0], elem)
self.assertEqual(len(elem.nsmap), 1)
self.assertEqual(elem.nsmap['a'], 'foo')
def test_render(self):
# Create a template element
tmpl_elem = xmlutil.TemplateElement('test')
tmpl_elem.text = xmlutil.Selector()
# Create the object we're going to render
obj = ['elem1', 'elem2', 'elem3', 'elem4']
# Try a render with no object
elems = tmpl_elem.render(None, None)
self.assertEqual(len(elems), 0)
# Try a render with one object
elems = tmpl_elem.render(None, 'foo')
self.assertEqual(len(elems), 1)
self.assertEqual(elems[0][0].text, 'foo')
self.assertEqual(elems[0][1], 'foo')
# Now, try rendering an object with multiple entries
parent = etree.Element('parent')
elems = tmpl_elem.render(parent, obj)
self.assertEqual(len(elems), 4)
# Check the results
for idx in range(len(obj)):
self.assertEqual(elems[idx][0].text, obj[idx])
self.assertEqual(elems[idx][1], obj[idx])
def test_subelement(self):
# Try the SubTemplateElement constructor
parent = xmlutil.SubTemplateElement(None, 'parent')
self.assertEqual(parent.tag, 'parent')
self.assertEqual(len(parent), 0)
# Now try it with a parent element
child = xmlutil.SubTemplateElement(parent, 'child')
self.assertEqual(child.tag, 'child')
self.assertEqual(len(parent), 1)
self.assertEqual(parent[0], child)
def test_wrap(self):
# These are strange methods, but they make things easier
elem = xmlutil.TemplateElement('test')
self.assertEqual(elem.unwrap(), elem)
self.assertEqual(elem.wrap().root, elem)
def test_dyntag(self):
obj = ['a', 'b', 'c']
# Create a template element with a dynamic tag
tmpl_elem = xmlutil.TemplateElement(xmlutil.Selector())
# Try the render
parent = etree.Element('parent')
elems = tmpl_elem.render(parent, obj)
# Verify the particulars of the render
self.assertEqual(len(elems), len(obj))
for idx in range(len(obj)):
self.assertEqual(elems[idx][0].tag, obj[idx])
class TemplateTest(test.TestCase):
def test_wrap(self):
# These are strange methods, but they make things easier
elem = xmlutil.TemplateElement('test')
tmpl = xmlutil.Template(elem)
self.assertEqual(tmpl.unwrap(), elem)
self.assertEqual(tmpl.wrap(), tmpl)
def test__siblings(self):
# Set up a basic template
elem = xmlutil.TemplateElement('test')
tmpl = xmlutil.Template(elem)
# Check that we get the right siblings
siblings = tmpl._siblings()
self.assertEqual(len(siblings), 1)
self.assertEqual(siblings[0], elem)
def test__nsmap(self):
# Set up a basic template
elem = xmlutil.TemplateElement('test')
tmpl = xmlutil.Template(elem, nsmap=dict(a="foo"))
# Check out that we get the right namespace dictionary
nsmap = tmpl._nsmap()
self.assertNotEqual(id(nsmap), id(tmpl.nsmap))
self.assertEqual(len(nsmap), 1)
self.assertEqual(nsmap['a'], 'foo')
def test_master_attach(self):
# Set up a master template
elem = xmlutil.TemplateElement('test')
tmpl = xmlutil.MasterTemplate(elem, 1)
# Make sure it has a root but no slaves
self.assertEqual(tmpl.root, elem)
self.assertEqual(len(tmpl.slaves), 0)
# Try to attach an invalid slave
bad_elem = xmlutil.TemplateElement('test2')
self.assertRaises(ValueError, tmpl.attach, bad_elem)
self.assertEqual(len(tmpl.slaves), 0)
# Try to attach an invalid and a valid slave
good_elem = xmlutil.TemplateElement('test')
self.assertRaises(ValueError, tmpl.attach, good_elem, bad_elem)
self.assertEqual(len(tmpl.slaves), 0)
# Try to attach an inapplicable template
class InapplicableTemplate(xmlutil.Template):
def apply(self, master):
return False
inapp_tmpl = InapplicableTemplate(good_elem)
tmpl.attach(inapp_tmpl)
self.assertEqual(len(tmpl.slaves), 0)
# Now try attaching an applicable template
tmpl.attach(good_elem)
self.assertEqual(len(tmpl.slaves), 1)
self.assertEqual(tmpl.slaves[0].root, good_elem)
def test_master_copy(self):
# Construct a master template
elem = xmlutil.TemplateElement('test')
tmpl = xmlutil.MasterTemplate(elem, 1, nsmap=dict(a='foo'))
# Give it a slave
slave = xmlutil.TemplateElement('test')
tmpl.attach(slave)
# Construct a copy
copy = tmpl.copy()
# Check to see if we actually managed a copy
self.assertNotEqual(tmpl, copy)
self.assertEqual(tmpl.root, copy.root)
self.assertEqual(tmpl.version, copy.version)
self.assertEqual(id(tmpl.nsmap), id(copy.nsmap))
self.assertNotEqual(id(tmpl.slaves), id(copy.slaves))
self.assertEqual(len(tmpl.slaves), len(copy.slaves))
self.assertEqual(tmpl.slaves[0], copy.slaves[0])
def test_slave_apply(self):
# Construct a master template
elem = xmlutil.TemplateElement('test')
master = xmlutil.MasterTemplate(elem, 3)
# Construct a slave template with applicable minimum version
slave = xmlutil.SlaveTemplate(elem, 2)
self.assertEqual(slave.apply(master), True)
# Construct a slave template with equal minimum version
slave = xmlutil.SlaveTemplate(elem, 3)
self.assertEqual(slave.apply(master), True)
# Construct a slave template with inapplicable minimum version
slave = xmlutil.SlaveTemplate(elem, 4)
self.assertEqual(slave.apply(master), False)
# Construct a slave template with applicable version range
slave = xmlutil.SlaveTemplate(elem, 2, 4)
self.assertEqual(slave.apply(master), True)
# Construct a slave template with low version range
slave = xmlutil.SlaveTemplate(elem, 1, 2)
self.assertEqual(slave.apply(master), False)
# Construct a slave template with high version range
slave = xmlutil.SlaveTemplate(elem, 4, 5)
self.assertEqual(slave.apply(master), False)
# Construct a slave template with matching version range
slave = xmlutil.SlaveTemplate(elem, 3, 3)
self.assertEqual(slave.apply(master), True)
def test__serialize(self):
# Our test object to serialize
obj = {'test': {'name': 'foobar',
'values': [1, 2, 3, 4],
'attrs': {'a': 1,
'b': 2,
'c': 3,
'd': 4, },
'image': {'name': 'image_foobar', 'id': 42, }, }, }
# Set up our master template
root = xmlutil.TemplateElement('test', selector='test',
name='name')
value = xmlutil.SubTemplateElement(root, 'value', selector='values')
value.text = xmlutil.Selector()
attrs = xmlutil.SubTemplateElement(root, 'attrs', selector='attrs')
xmlutil.SubTemplateElement(attrs, 'attr', selector=xmlutil.get_items,
key=0, value=1)
master = xmlutil.MasterTemplate(root, 1, nsmap=dict(f='foo'))
# Set up our slave template
root_slave = xmlutil.TemplateElement('test', selector='test')
image = xmlutil.SubTemplateElement(root_slave, 'image',
selector='image', id='id')
image.text = xmlutil.Selector('name')
slave = xmlutil.SlaveTemplate(root_slave, 1, nsmap=dict(b='bar'))
# Attach the slave to the master...
master.attach(slave)
# Try serializing our object
siblings = master._siblings()
nsmap = master._nsmap()
result = master._serialize(None, obj, siblings, nsmap)
# Now we get to manually walk the element tree...
self.assertEqual(result.tag, 'test')
self.assertEqual(len(result.nsmap), 2)
self.assertEqual(result.nsmap['f'], 'foo')
self.assertEqual(result.nsmap['b'], 'bar')
self.assertEqual(result.get('name'), obj['test']['name'])
for idx, val in enumerate(obj['test']['values']):
self.assertEqual(result[idx].tag, 'value')
self.assertEqual(result[idx].text, str(val))
idx += 1
self.assertEqual(result[idx].tag, 'attrs')
for attr in result[idx]:
self.assertEqual(attr.tag, 'attr')
self.assertEqual(attr.get('value'),
str(obj['test']['attrs'][attr.get('key')]))
idx += 1
self.assertEqual(result[idx].tag, 'image')
self.assertEqual(result[idx].get('id'),
str(obj['test']['image']['id']))
self.assertEqual(result[idx].text, obj['test']['image']['name'])
class MasterTemplateBuilder(xmlutil.TemplateBuilder):
def construct(self):
elem = xmlutil.TemplateElement('test')
return xmlutil.MasterTemplate(elem, 1)
class SlaveTemplateBuilder(xmlutil.TemplateBuilder):
def construct(self):
elem = xmlutil.TemplateElement('test')
return xmlutil.SlaveTemplate(elem, 1)
class TemplateBuilderTest(test.TestCase):
def test_master_template_builder(self):
# Make sure the template hasn't been built yet
self.assertEqual(MasterTemplateBuilder._tmpl, None)
# Now, construct the template
tmpl1 = MasterTemplateBuilder()
# Make sure that there is a template cached...
self.assertNotEqual(MasterTemplateBuilder._tmpl, None)
# Make sure it wasn't what was returned...
self.assertNotEqual(MasterTemplateBuilder._tmpl, tmpl1)
# Make sure it doesn't get rebuilt
cached = MasterTemplateBuilder._tmpl
tmpl2 = MasterTemplateBuilder()
self.assertEqual(MasterTemplateBuilder._tmpl, cached)
# Make sure we're always getting fresh copies
self.assertNotEqual(tmpl1, tmpl2)
# Make sure we can override the copying behavior
tmpl3 = MasterTemplateBuilder(False)
self.assertEqual(MasterTemplateBuilder._tmpl, tmpl3)
def test_slave_template_builder(self):
# Make sure the template hasn't been built yet
self.assertEqual(SlaveTemplateBuilder._tmpl, None)
# Now, construct the template
tmpl1 = SlaveTemplateBuilder()
# Make sure there is a template cached...
self.assertNotEqual(SlaveTemplateBuilder._tmpl, None)
# Make sure it was what was returned...
self.assertEqual(SlaveTemplateBuilder._tmpl, tmpl1)
# Make sure it doesn't get rebuilt
tmpl2 = SlaveTemplateBuilder()
self.assertEqual(SlaveTemplateBuilder._tmpl, tmpl1)
# Make sure we're always getting the cached copy
self.assertEqual(tmpl1, tmpl2)
class MiscellaneousXMLUtilTests(test.TestCase):
def test_make_flat_dict(self):
expected = minidom.parseString(
"<?xml version='1.0' encoding='UTF-8'?>"
"<wrapper><a>foo</a><b>bar</b></wrapper>"
)
root = xmlutil.make_flat_dict('wrapper')
tmpl = xmlutil.MasterTemplate(root, 1)
result = tmpl.serialize(dict(wrapper=dict(a='foo', b='bar')))
actual = minidom.parseString(result)
self.assertEqual(expected.firstChild.tagName,
actual.firstChild.tagName)
expected_child_a = expected.firstChild.getElementsByTagName('a')
expected_child_b = expected.firstChild.getElementsByTagName('b')
actual_child_a = actual.firstChild.getElementsByTagName('a')
actual_child_b = actual.firstChild.getElementsByTagName('b')
self.assertEqual(len(expected_child_a), 1)
self.assertEqual(len(expected_child_b), 1)
self.assertEqual(len(actual_child_a), 1)
self.assertEqual(len(actual_child_b), 1)
self.assertEqual(expected_child_a[0].toxml(),
actual_child_a[0].toxml())
self.assertEqual(expected_child_b[0].toxml(),
actual_child_b[0].toxml())

View File

@ -18,9 +18,7 @@ Tests dealing with HTTP rate-limiting.
"""
import httplib
from xml.dom import minidom
from lxml import etree
from oslo_serialization import jsonutils
import six
from six import moves
@ -28,7 +26,6 @@ import webob
from manila.api.v1 import limits
from manila.api import views
from manila.api import xmlutil
import manila.context
from manila import test
@ -279,26 +276,6 @@ class LimitMiddlewareTest(BaseLimitTestSuite):
value = body["overLimitFault"]["details"].strip()
self.assertEqual(value, expected)
def test_limited_request_xml(self):
"""Test a rate-limited (413) response as XML."""
request = webob.Request.blank("/")
response = request.get_response(self.app)
self.assertEqual(200, response.status_int)
request = webob.Request.blank("/")
request.accept = "application/xml"
response = request.get_response(self.app)
self.assertEqual(response.status_int, 413)
root = minidom.parseString(response.body).childNodes[0]
expected = "Only 1 GET request(s) can be made to * every minute."
details = root.getElementsByTagName("details")
self.assertEqual(details.length, 1)
value = details.item(0).firstChild.data.strip()
self.assertEqual(value, expected)
class LimitTest(BaseLimitTestSuite):
"""Tests for the `limits.Limit` class."""
@ -802,89 +779,3 @@ class LimitsViewBuilderTest(test.TestCase):
rate_limits = []
output = self.view_builder.build(rate_limits, abs_limits)
self.assertDictMatch(output, expected_limits)
class LimitsXMLSerializationTest(test.TestCase):
def test_xml_declaration(self):
serializer = limits.LimitsTemplate()
fixture = {"limits": {
"rate": [],
"absolute": {}}}
output = serializer.serialize(fixture)
has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>")
self.assertTrue(has_dec)
def test_index(self):
serializer = limits.LimitsTemplate()
fixture = {
"limits": {
"rate": [{
"uri": "*",
"regex": ".*",
"limit": [{
"value": 10,
"verb": "POST",
"remaining": 2,
"unit": "MINUTE",
"next-available": "2011-12-15T22:42:45Z"}]},
{"uri": "*/servers",
"regex": "^/servers",
"limit": [{
"value": 50,
"verb": "POST",
"remaining": 10,
"unit": "DAY",
"next-available": "2011-12-15T22:42:45Z"}]}],
"absolute": {"maxServerMeta": 1,
"maxImageMeta": 1,
"maxPersonality": 5,
"maxPersonalitySize": 10240}}}
output = serializer.serialize(fixture)
root = etree.XML(output)
xmlutil.validate_schema(root, 'limits')
# verify absolute limits
absolutes = root.xpath('ns:absolute/ns:limit', namespaces=NS)
self.assertEqual(len(absolutes), 4)
for limit in absolutes:
name = limit.get('name')
value = limit.get('value')
self.assertEqual(value, str(fixture['limits']['absolute'][name]))
# verify rate limits
rates = root.xpath('ns:rates/ns:rate', namespaces=NS)
self.assertEqual(len(rates), 2)
for i, rate in enumerate(rates):
for key in ['uri', 'regex']:
self.assertEqual(rate.get(key),
str(fixture['limits']['rate'][i][key]))
rate_limits = rate.xpath('ns:limit', namespaces=NS)
self.assertEqual(len(rate_limits), 1)
for j, limit in enumerate(rate_limits):
for key in ['verb', 'value', 'remaining', 'unit',
'next-available']:
self.assertEqual(
limit.get(key),
str(fixture['limits']['rate'][i]['limit'][j][key]))
def test_index_no_limits(self):
serializer = limits.LimitsTemplate()
fixture = {"limits": {
"rate": [],
"absolute": {}}}
output = serializer.serialize(fixture)
root = etree.XML(output)
xmlutil.validate_schema(root, 'limits')
# verify absolute limits
absolutes = root.xpath('ns:absolute/ns:limit', namespaces=NS)
self.assertEqual(len(absolutes), 0)
# verify rate limits
rates = root.xpath('ns:rates/ns:rate', namespaces=NS)
self.assertEqual(len(rates), 0)

View File

@ -284,36 +284,6 @@ class GenericUtilsTestCase(test.TestCase):
self.assertFalse(result)
timeutils.utcnow.assert_called_once_with()
def test_safe_parse_xml(self):
normal_body = ('<?xml version="1.0" ?>'
'<foo><bar><v1>hey</v1><v2>there</v2></bar></foo>')
def killer_body():
return (("""<!DOCTYPE x [
<!ENTITY a "%(a)s">
<!ENTITY b "%(b)s">
<!ENTITY c "%(c)s">]>
<foo>
<bar>
<v1>%(d)s</v1>
</bar>
</foo>""") % {
'a': 'A' * 10,
'b': '&a;' * 10,
'c': '&b;' * 10,
'd': '&c;' * 9999,
}).strip()
dom = utils.safe_minidom_parse_string(normal_body)
# Some versions of minidom inject extra newlines so we ignore them
result = str(dom.toxml()).replace('\n', '')
self.assertEqual(normal_body, result)
self.assertRaises(ValueError,
utils.safe_minidom_parse_string,
killer_body())
def test_is_ipv6_configured0(self):
fake_fd = mock.Mock()
fake_fd.read.return_value = 'test'

View File

@ -26,10 +26,6 @@ import shutil
import socket
import sys
import tempfile
from xml.dom import minidom
from xml.parsers import expat
from xml import sax
from xml.sax import expatreader
from eventlet import pools
import netaddr
@ -191,46 +187,6 @@ class LazyPluggable(object):
return getattr(backend, key)
class ProtectedExpatParser(expatreader.ExpatParser):
"""An expat parser which disables DTD's and entities by default."""
def __init__(self, forbid_dtd=True, forbid_entities=True,
*args, **kwargs):
# Python 2.x old style class
expatreader.ExpatParser.__init__(self, *args, **kwargs)
self.forbid_dtd = forbid_dtd
self.forbid_entities = forbid_entities
def start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
raise ValueError("Inline DTD forbidden")
def entity_decl(self, entityName, is_parameter_entity, value, base,
systemId, publicId, notationName):
raise ValueError("<!ENTITY> forbidden")
def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
# expat 1.2
raise ValueError("<!ENTITY> forbidden")
def reset(self):
expatreader.ExpatParser.reset(self)
if self.forbid_dtd:
self._parser.StartDoctypeDeclHandler = self.start_doctype_decl
if self.forbid_entities:
self._parser.EntityDeclHandler = self.entity_decl
self._parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl
def safe_minidom_parse_string(xml_string):
"""Parse an XML string using minidom safely.
"""
try:
return minidom.parseString(xml_string, parser=ProtectedExpatParser())
except sax.SAXParseException:
raise expat.ExpatError()
def delete_if_exists(pathname):
"""Delete a file, but ignore file not found error."""