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:
parent
cdcf2e7671
commit
a26824818f
@ -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]
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
|
@ -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"),
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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)
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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']
|
||||
|
@ -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']
|
||||
|
@ -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,
|
||||
|
@ -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']
|
||||
|
@ -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]
|
||||
|
@ -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']
|
||||
|
@ -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'])
|
||||
|
@ -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
|
@ -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())
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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())
|
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -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."""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user