Merge "Remove Limited XML API Support from Manila"
This commit is contained in:
commit
61475eb5b6
|
@ -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