Merge "Remove XML API"
This commit is contained in:
commit
3ec6a6b791
@ -23,8 +23,6 @@ from oslo_log import log as logging
|
||||
from six.moves import urllib
|
||||
import webob
|
||||
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder.common import constants
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -59,9 +57,6 @@ CONF.register_opts(api_common_opts)
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
XML_NS_V1 = 'http://docs.openstack.org/api/openstack-block-storage/1.0/content'
|
||||
XML_NS_V2 = 'http://docs.openstack.org/api/openstack-block-storage/2.0/content'
|
||||
|
||||
METADATA_TYPES = enum.Enum('METADATA_TYPES', 'user image')
|
||||
|
||||
|
||||
@ -370,75 +365,3 @@ class ViewBuilder(object):
|
||||
url_parts[2] = prefix_parts[2] + url_parts[2]
|
||||
|
||||
return urllib.parse.urlunsplit(url_parts).rstrip('/')
|
||||
|
||||
|
||||
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)
|
||||
|
@ -334,7 +334,6 @@ class Admin_actions(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "AdminActions"
|
||||
alias = "os-admin-actions"
|
||||
namespace = "http://docs.openstack.org/volume/ext/admin-actions/api/v1.1"
|
||||
updated = "2012-08-25T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
|
@ -16,29 +16,10 @@
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
import cinder.api.views.availability_zones
|
||||
from cinder.api import xmlutil
|
||||
import cinder.exception
|
||||
import cinder.volume.api
|
||||
|
||||
|
||||
def make_availability_zone(elem):
|
||||
elem.set('name', 'zoneName')
|
||||
zoneStateElem = xmlutil.SubTemplateElement(elem, 'zoneState',
|
||||
selector='zoneState')
|
||||
zoneStateElem.set('available')
|
||||
|
||||
|
||||
class ListTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('availabilityZones')
|
||||
elem = xmlutil.SubTemplateElement(root, 'availabilityZone',
|
||||
selector='availabilityZoneInfo')
|
||||
make_availability_zone(elem)
|
||||
alias = Availability_zones.alias
|
||||
namespace = Availability_zones.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class Controller(wsgi.Controller):
|
||||
|
||||
_view_builder_class = cinder.api.views.availability_zones.ViewBuilder
|
||||
@ -47,7 +28,6 @@ class Controller(wsgi.Controller):
|
||||
super(Controller, self).__init__(*args, **kwargs)
|
||||
self.volume_api = cinder.volume.api.API()
|
||||
|
||||
@wsgi.serializers(xml=ListTemplate)
|
||||
def index(self, req):
|
||||
"""Describe all known availability zones."""
|
||||
azs = self.volume_api.list_availability_zones()
|
||||
@ -59,8 +39,6 @@ class Availability_zones(extensions.ExtensionDescriptor):
|
||||
|
||||
name = 'AvailabilityZones'
|
||||
alias = 'os-availability-zone'
|
||||
namespace = ('http://docs.openstack.org/volume/ext/'
|
||||
'os-availability-zone/api/v1')
|
||||
updated = '2013-06-27T00:00:00+00:00'
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -25,7 +25,6 @@ from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import backups as backup_views
|
||||
from cinder.api import xmlutil
|
||||
from cinder import backup as backupAPI
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
@ -34,123 +33,6 @@ from cinder import utils
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_backup(elem):
|
||||
elem.set('id')
|
||||
elem.set('status')
|
||||
elem.set('size')
|
||||
elem.set('container')
|
||||
elem.set('parent_id')
|
||||
elem.set('volume_id')
|
||||
elem.set('object_count')
|
||||
elem.set('availability_zone')
|
||||
elem.set('created_at')
|
||||
elem.set('name')
|
||||
elem.set('description')
|
||||
elem.set('fail_reason')
|
||||
|
||||
|
||||
def make_backup_restore(elem):
|
||||
elem.set('backup_id')
|
||||
elem.set('volume_id')
|
||||
elem.set('volume_name')
|
||||
|
||||
|
||||
def make_backup_export_import_record(elem):
|
||||
elem.set('backup_service')
|
||||
elem.set('backup_url')
|
||||
|
||||
|
||||
class BackupTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('backup', selector='backup')
|
||||
make_backup(root)
|
||||
alias = Backups.alias
|
||||
namespace = Backups.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class BackupsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('backups')
|
||||
elem = xmlutil.SubTemplateElement(root, 'backup', selector='backups')
|
||||
make_backup(elem)
|
||||
alias = Backups.alias
|
||||
namespace = Backups.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class BackupRestoreTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('restore', selector='restore')
|
||||
make_backup_restore(root)
|
||||
alias = Backups.alias
|
||||
namespace = Backups.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class BackupExportImportTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('backup-record',
|
||||
selector='backup-record')
|
||||
make_backup_export_import_record(root)
|
||||
alias = Backups.alias
|
||||
namespace = Backups.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class CreateDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
backup = self._extract_backup(dom)
|
||||
return {'body': {'backup': backup}}
|
||||
|
||||
def _extract_backup(self, node):
|
||||
backup = {}
|
||||
backup_node = self.find_first_child_named(node, 'backup')
|
||||
|
||||
attributes = ['container', 'display_name',
|
||||
'display_description', 'volume_id',
|
||||
'parent_id']
|
||||
|
||||
for attr in attributes:
|
||||
if backup_node.getAttribute(attr):
|
||||
backup[attr] = backup_node.getAttribute(attr)
|
||||
return backup
|
||||
|
||||
|
||||
class RestoreDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
restore = self._extract_restore(dom)
|
||||
return {'body': {'restore': restore}}
|
||||
|
||||
def _extract_restore(self, node):
|
||||
restore = {}
|
||||
restore_node = self.find_first_child_named(node, 'restore')
|
||||
if restore_node.getAttribute('volume_id'):
|
||||
restore['volume_id'] = restore_node.getAttribute('volume_id')
|
||||
return restore
|
||||
|
||||
|
||||
class BackupImportDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
backup = self._extract_backup(dom)
|
||||
retval = {'body': {'backup-record': backup}}
|
||||
return retval
|
||||
|
||||
def _extract_backup(self, node):
|
||||
backup = {}
|
||||
backup_node = self.find_first_child_named(node, 'backup-record')
|
||||
|
||||
attributes = ['backup_service', 'backup_url']
|
||||
|
||||
for attr in attributes:
|
||||
if backup_node.getAttribute(attr):
|
||||
backup[attr] = backup_node.getAttribute(attr)
|
||||
return backup
|
||||
|
||||
|
||||
class BackupsController(wsgi.Controller):
|
||||
"""The Backups API controller for the OpenStack API."""
|
||||
|
||||
@ -160,7 +42,6 @@ class BackupsController(wsgi.Controller):
|
||||
self.backup_api = backupAPI.API()
|
||||
super(BackupsController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=BackupTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given backup."""
|
||||
LOG.debug('show called for member %s', id)
|
||||
@ -191,12 +72,10 @@ class BackupsController(wsgi.Controller):
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.serializers(xml=BackupsTemplate)
|
||||
def index(self, req):
|
||||
"""Returns a summary list of backups."""
|
||||
return self._get_backups(req, is_detail=False)
|
||||
|
||||
@wsgi.serializers(xml=BackupsTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of backups."""
|
||||
return self._get_backups(req, is_detail=True)
|
||||
@ -242,8 +121,6 @@ class BackupsController(wsgi.Controller):
|
||||
# immediately
|
||||
# - maybe also do validation of swift container name
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=BackupTemplate)
|
||||
@wsgi.deserializers(xml=CreateDeserializer)
|
||||
def create(self, req, body):
|
||||
"""Create a new backup."""
|
||||
LOG.debug('Creating new backup %s', body)
|
||||
@ -287,8 +164,6 @@ class BackupsController(wsgi.Controller):
|
||||
return retval
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=BackupRestoreTemplate)
|
||||
@wsgi.deserializers(xml=RestoreDeserializer)
|
||||
def restore(self, req, id, body):
|
||||
"""Restore an existing backup to a volume."""
|
||||
LOG.debug('Restoring backup %(backup_id)s (%(body)s)',
|
||||
@ -331,7 +206,6 @@ class BackupsController(wsgi.Controller):
|
||||
return retval
|
||||
|
||||
@wsgi.response(200)
|
||||
@wsgi.serializers(xml=BackupExportImportTemplate)
|
||||
def export_record(self, req, id):
|
||||
"""Export a backup."""
|
||||
LOG.debug('export record called for member %s.', id)
|
||||
@ -350,8 +224,6 @@ class BackupsController(wsgi.Controller):
|
||||
return retval
|
||||
|
||||
@wsgi.response(201)
|
||||
@wsgi.serializers(xml=BackupTemplate)
|
||||
@wsgi.deserializers(xml=BackupImportDeserializer)
|
||||
def import_record(self, req, body):
|
||||
"""Import a backup."""
|
||||
LOG.debug('Importing record from %s.', body)
|
||||
@ -389,7 +261,6 @@ class Backups(extensions.ExtensionDescriptor):
|
||||
|
||||
name = 'Backups'
|
||||
alias = 'backups'
|
||||
namespace = 'http://docs.openstack.org/volume/ext/backups/api/v1'
|
||||
updated = '2012-12-12T00:00:00+00:00'
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -65,7 +65,6 @@ class Capabilities(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "Capabilities"
|
||||
alias = "capabilities"
|
||||
namespace = "http://docs.openstack.org/volume/ext/capabilities/api/v2"
|
||||
updated = "2015-08-31T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -23,63 +23,13 @@ from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import cgsnapshots as cgsnapshot_views
|
||||
from cinder.api import xmlutil
|
||||
from cinder import consistencygroup as consistencygroupAPI
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_cgsnapshot(elem):
|
||||
elem.set('id')
|
||||
elem.set('consistencygroup_id')
|
||||
elem.set('status')
|
||||
elem.set('created_at')
|
||||
elem.set('name')
|
||||
elem.set('description')
|
||||
|
||||
|
||||
class CgsnapshotTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('cgsnapshot', selector='cgsnapshot')
|
||||
make_cgsnapshot(root)
|
||||
alias = Cgsnapshots.alias
|
||||
namespace = Cgsnapshots.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class CgsnapshotsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('cgsnapshots')
|
||||
elem = xmlutil.SubTemplateElement(root, 'cgsnapshot',
|
||||
selector='cgsnapshots')
|
||||
make_cgsnapshot(elem)
|
||||
alias = Cgsnapshots.alias
|
||||
namespace = Cgsnapshots.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class CreateDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
cgsnapshot = self._extract_cgsnapshot(dom)
|
||||
return {'body': {'cgsnapshot': cgsnapshot}}
|
||||
|
||||
def _extract_cgsnapshot(self, node):
|
||||
cgsnapshot = {}
|
||||
cgsnapshot_node = self.find_first_child_named(node, 'cgsnapshot')
|
||||
|
||||
attributes = ['name',
|
||||
'description']
|
||||
|
||||
for attr in attributes:
|
||||
if cgsnapshot_node.getAttribute(attr):
|
||||
cgsnapshot[attr] = cgsnapshot_node.getAttribute(attr)
|
||||
return cgsnapshot
|
||||
|
||||
|
||||
class CgsnapshotsController(wsgi.Controller):
|
||||
"""The cgsnapshots API controller for the OpenStack API."""
|
||||
|
||||
@ -89,7 +39,6 @@ class CgsnapshotsController(wsgi.Controller):
|
||||
self.cgsnapshot_api = consistencygroupAPI.API()
|
||||
super(CgsnapshotsController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=CgsnapshotTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given cgsnapshot."""
|
||||
LOG.debug('show called for member %s', id)
|
||||
@ -127,12 +76,10 @@ class CgsnapshotsController(wsgi.Controller):
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.serializers(xml=CgsnapshotsTemplate)
|
||||
def index(self, req):
|
||||
"""Returns a summary list of cgsnapshots."""
|
||||
return self._get_cgsnapshots(req, is_detail=False)
|
||||
|
||||
@wsgi.serializers(xml=CgsnapshotsTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of cgsnapshots."""
|
||||
return self._get_cgsnapshots(req, is_detail=True)
|
||||
@ -150,8 +97,6 @@ class CgsnapshotsController(wsgi.Controller):
|
||||
return cgsnapshots
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=CgsnapshotTemplate)
|
||||
@wsgi.deserializers(xml=CreateDeserializer)
|
||||
def create(self, req, body):
|
||||
"""Create a new cgsnapshot."""
|
||||
LOG.debug('Creating new cgsnapshot %s', body)
|
||||
@ -197,7 +142,6 @@ class Cgsnapshots(extensions.ExtensionDescriptor):
|
||||
|
||||
name = 'Cgsnapshots'
|
||||
alias = 'cgsnapshots'
|
||||
namespace = 'http://docs.openstack.org/volume/ext/cgsnapshots/api/v1'
|
||||
updated = '2014-08-18T00:00:00+00:00'
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -24,108 +24,13 @@ from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import consistencygroups as consistencygroup_views
|
||||
from cinder.api import xmlutil
|
||||
from cinder import consistencygroup as consistencygroupAPI
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_consistencygroup(elem):
|
||||
elem.set('id')
|
||||
elem.set('status')
|
||||
elem.set('availability_zone')
|
||||
elem.set('created_at')
|
||||
elem.set('name')
|
||||
elem.set('description')
|
||||
|
||||
|
||||
def make_consistencygroup_from_src(elem):
|
||||
elem.set('id')
|
||||
elem.set('status')
|
||||
elem.set('created_at')
|
||||
elem.set('name')
|
||||
elem.set('description')
|
||||
elem.set('cgsnapshot_id')
|
||||
elem.set('source_cgid')
|
||||
|
||||
|
||||
class ConsistencyGroupTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('consistencygroup',
|
||||
selector='consistencygroup')
|
||||
make_consistencygroup(root)
|
||||
alias = Consistencygroups.alias
|
||||
namespace = Consistencygroups.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class ConsistencyGroupsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('consistencygroups')
|
||||
elem = xmlutil.SubTemplateElement(root, 'consistencygroup',
|
||||
selector='consistencygroups')
|
||||
make_consistencygroup(elem)
|
||||
alias = Consistencygroups.alias
|
||||
namespace = Consistencygroups.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class ConsistencyGroupFromSrcTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('consistencygroup-from-src',
|
||||
selector='consistencygroup-from-src')
|
||||
make_consistencygroup_from_src(root)
|
||||
alias = Consistencygroups.alias
|
||||
namespace = Consistencygroups.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class CreateDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
consistencygroup = self._extract_consistencygroup(dom)
|
||||
return {'body': {'consistencygroup': consistencygroup}}
|
||||
|
||||
def _extract_consistencygroup(self, node):
|
||||
consistencygroup = {}
|
||||
consistencygroup_node = self.find_first_child_named(
|
||||
node,
|
||||
'consistencygroup')
|
||||
|
||||
attributes = ['name',
|
||||
'description']
|
||||
|
||||
for attr in attributes:
|
||||
if consistencygroup_node.getAttribute(attr):
|
||||
consistencygroup[attr] = consistencygroup_node.\
|
||||
getAttribute(attr)
|
||||
return consistencygroup
|
||||
|
||||
|
||||
class CreateFromSrcDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
consistencygroup = self._extract_consistencygroup(dom)
|
||||
retval = {'body': {'consistencygroup-from-src': consistencygroup}}
|
||||
return retval
|
||||
|
||||
def _extract_consistencygroup(self, node):
|
||||
consistencygroup = {}
|
||||
consistencygroup_node = self.find_first_child_named(
|
||||
node, 'consistencygroup-from-src')
|
||||
|
||||
attributes = ['cgsnapshot', 'source_cgid', 'name', 'description']
|
||||
|
||||
for attr in attributes:
|
||||
if consistencygroup_node.getAttribute(attr):
|
||||
consistencygroup[attr] = (
|
||||
consistencygroup_node.getAttribute(attr))
|
||||
return consistencygroup
|
||||
|
||||
|
||||
class ConsistencyGroupsController(wsgi.Controller):
|
||||
"""The ConsistencyGroups API controller for the OpenStack API."""
|
||||
|
||||
@ -135,7 +40,6 @@ class ConsistencyGroupsController(wsgi.Controller):
|
||||
self.consistencygroup_api = consistencygroupAPI.API()
|
||||
super(ConsistencyGroupsController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=ConsistencyGroupTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given consistency group."""
|
||||
LOG.debug('show called for member %s', id)
|
||||
@ -182,12 +86,10 @@ class ConsistencyGroupsController(wsgi.Controller):
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.serializers(xml=ConsistencyGroupsTemplate)
|
||||
def index(self, req):
|
||||
"""Returns a summary list of consistency groups."""
|
||||
return self._get_consistencygroups(req, is_detail=False)
|
||||
|
||||
@wsgi.serializers(xml=ConsistencyGroupsTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of consistency groups."""
|
||||
return self._get_consistencygroups(req, is_detail=True)
|
||||
@ -212,8 +114,6 @@ class ConsistencyGroupsController(wsgi.Controller):
|
||||
return consistencygroups
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=ConsistencyGroupTemplate)
|
||||
@wsgi.deserializers(xml=CreateDeserializer)
|
||||
def create(self, req, body):
|
||||
"""Create a new consistency group."""
|
||||
LOG.debug('Creating new consistency group %s', body)
|
||||
@ -250,8 +150,6 @@ class ConsistencyGroupsController(wsgi.Controller):
|
||||
return retval
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=ConsistencyGroupFromSrcTemplate)
|
||||
@wsgi.deserializers(xml=CreateFromSrcDeserializer)
|
||||
def create_from_src(self, req, body):
|
||||
"""Create a new consistency group from a source.
|
||||
|
||||
@ -307,7 +205,6 @@ class ConsistencyGroupsController(wsgi.Controller):
|
||||
retval = self._view_builder.summary(req, new_consistencygroup)
|
||||
return retval
|
||||
|
||||
@wsgi.serializers(xml=ConsistencyGroupTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update the consistency group.
|
||||
|
||||
@ -375,7 +272,6 @@ class Consistencygroups(extensions.ExtensionDescriptor):
|
||||
|
||||
name = 'Consistencygroups'
|
||||
alias = 'consistencygroups'
|
||||
namespace = 'http://docs.openstack.org/volume/ext/consistencygroups/api/v1'
|
||||
updated = '2014-08-18T00:00:00+00:00'
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -20,6 +20,4 @@ class Extended_services(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "ExtendedServices"
|
||||
alias = "os-extended-services"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"extended_services/api/v2")
|
||||
updated = "2014-01-10T00:00:00-00:00"
|
||||
|
@ -18,7 +18,6 @@ from oslo_log import log as logging
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -39,7 +38,6 @@ class ExtendedSnapshotAttributesController(wsgi.Controller):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
# Attach our slave template to the response object
|
||||
resp_obj.attach(xml=ExtendedSnapshotAttributeTemplate())
|
||||
snapshot = resp_obj.obj['snapshot']
|
||||
self._extend_snapshot(req, snapshot)
|
||||
|
||||
@ -48,7 +46,6 @@ class ExtendedSnapshotAttributesController(wsgi.Controller):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
# Attach our slave template to the response object
|
||||
resp_obj.attach(xml=ExtendedSnapshotAttributesTemplate())
|
||||
for snapshot in list(resp_obj.obj['snapshots']):
|
||||
self._extend_snapshot(req, snapshot)
|
||||
|
||||
@ -58,8 +55,6 @@ class Extended_snapshot_attributes(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "ExtendedSnapshotAttributes"
|
||||
alias = "os-extended-snapshot-attributes"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"extended_snapshot_attributes/api/v1")
|
||||
updated = "2012-06-19T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
@ -67,30 +62,3 @@ class Extended_snapshot_attributes(extensions.ExtensionDescriptor):
|
||||
extension = extensions.ControllerExtension(self, 'snapshots',
|
||||
controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
def make_snapshot(elem):
|
||||
elem.set('{%s}project_id' % Extended_snapshot_attributes.namespace,
|
||||
'%s:project_id' % Extended_snapshot_attributes.alias)
|
||||
elem.set('{%s}progress' % Extended_snapshot_attributes.namespace,
|
||||
'%s:progress' % Extended_snapshot_attributes.alias)
|
||||
|
||||
|
||||
class ExtendedSnapshotAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('snapshot', selector='snapshot')
|
||||
make_snapshot(root)
|
||||
alias = Extended_snapshot_attributes.alias
|
||||
namespace = Extended_snapshot_attributes.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class ExtendedSnapshotAttributesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('snapshots')
|
||||
elem = xmlutil.SubTemplateElement(root, 'snapshot',
|
||||
selector='snapshots')
|
||||
make_snapshot(elem)
|
||||
alias = Extended_snapshot_attributes.alias
|
||||
namespace = Extended_snapshot_attributes.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
@ -15,8 +15,6 @@
|
||||
|
||||
"""The hosts admin extension."""
|
||||
|
||||
from xml.parsers import expat
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
@ -24,12 +22,10 @@ import webob.exc
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder import objects
|
||||
from cinder import utils
|
||||
from cinder.volume import api as volume_api
|
||||
|
||||
|
||||
@ -39,62 +35,6 @@ LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.extension_authorizer('volume', 'hosts')
|
||||
|
||||
|
||||
class HostIndexTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('hosts')
|
||||
elem = xmlutil.SubTemplateElement(root, 'host', selector='hosts')
|
||||
elem.set('service-status')
|
||||
elem.set('service')
|
||||
elem.set('zone')
|
||||
elem.set('service-state')
|
||||
elem.set('host_name')
|
||||
elem.set('last-update')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class HostUpdateTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('host')
|
||||
root.set('host')
|
||||
root.set('status')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class HostActionTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('host')
|
||||
root.set('host')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class HostShowTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('host')
|
||||
elem = xmlutil.make_flat_dict('resource', selector='host',
|
||||
subselector='resource')
|
||||
root.append(elem)
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class HostDeserializer(wsgi.XMLDeserializer):
|
||||
def default(self, string):
|
||||
try:
|
||||
node = utils.safe_minidom_parse_string(string)
|
||||
except expat.ExpatError:
|
||||
msg = _("cannot understand XML")
|
||||
raise exception.MalformedRequestBody(reason=msg)
|
||||
|
||||
updates = {}
|
||||
for child in node.childNodes[0].childNodes:
|
||||
updates[child.tagName] = self.extract_text(child)
|
||||
|
||||
return dict(body=updates)
|
||||
|
||||
|
||||
def _list_hosts(req, service=None):
|
||||
"""Returns a summary list of hosts."""
|
||||
curr_time = timeutils.utcnow(with_timezone=True)
|
||||
@ -151,13 +91,10 @@ class HostController(wsgi.Controller):
|
||||
self.api = volume_api.HostAPI()
|
||||
super(HostController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=HostIndexTemplate)
|
||||
def index(self, req):
|
||||
authorize(req.environ['cinder.context'])
|
||||
return {'hosts': _list_hosts(req)}
|
||||
|
||||
@wsgi.serializers(xml=HostUpdateTemplate)
|
||||
@wsgi.deserializers(xml=HostDeserializer)
|
||||
@check_host
|
||||
def update(self, req, id, body):
|
||||
authorize(req.environ['cinder.context'])
|
||||
@ -194,7 +131,6 @@ class HostController(wsgi.Controller):
|
||||
raise webob.exc.HTTPBadRequest(explanation=result)
|
||||
return {"host": host, "status": result}
|
||||
|
||||
@wsgi.serializers(xml=HostShowTemplate)
|
||||
def show(self, req, id):
|
||||
"""Shows the volume usage info given by hosts.
|
||||
|
||||
@ -260,7 +196,6 @@ class Hosts(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "Hosts"
|
||||
alias = "os-hosts"
|
||||
namespace = "http://docs.openstack.org/volume/ext/hosts/api/v1.1"
|
||||
updated = "2011-06-29T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -25,5 +25,4 @@ class Image_create(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "CreateVolumeExtension"
|
||||
alias = "os-image-create"
|
||||
namespace = "http://docs.openstack.org/volume/ext/image-create/api/v1"
|
||||
updated = "2012-08-13T00:00:00+00:00"
|
||||
|
@ -24,9 +24,8 @@ from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import qos_specs as view_qos_specs
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder.i18n import _
|
||||
from cinder import rpc
|
||||
from cinder import utils
|
||||
from cinder.volume import qos_specs
|
||||
@ -37,62 +36,6 @@ LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.extension_authorizer('volume', 'qos_specs_manage')
|
||||
|
||||
|
||||
def make_qos_specs(elem):
|
||||
elem.set('id')
|
||||
elem.set('name')
|
||||
elem.set('consumer')
|
||||
elem.append(SpecsTemplate())
|
||||
|
||||
|
||||
def make_associations(elem):
|
||||
elem.set('association_type')
|
||||
elem.set('name')
|
||||
elem.set('id')
|
||||
|
||||
|
||||
class SpecsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
return xmlutil.MasterTemplate(xmlutil.make_flat_dict('specs'), 1)
|
||||
|
||||
|
||||
class QoSSpecsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('qos_specs')
|
||||
elem = xmlutil.SubTemplateElement(root, 'qos_spec',
|
||||
selector='qos_specs')
|
||||
make_qos_specs(elem)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class QoSSpecsKeyDeserializer(wsgi.XMLDeserializer):
|
||||
def _extract_keys(self, key_node):
|
||||
keys = []
|
||||
for key in key_node.childNodes:
|
||||
key_name = key.tagName
|
||||
keys.append(key_name)
|
||||
|
||||
return keys
|
||||
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
key_node = self.find_first_child_named(dom, 'keys')
|
||||
if not key_node:
|
||||
LOG.info(_LI("Unable to parse XML input."))
|
||||
msg = _("Unable to parse XML request. "
|
||||
"Please provide XML in correct format.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
return {'body': {'keys': self._extract_keys(key_node)}}
|
||||
|
||||
|
||||
class AssociationsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('qos_associations')
|
||||
elem = xmlutil.SubTemplateElement(root, 'associations',
|
||||
selector='qos_associations')
|
||||
make_associations(elem)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
def _check_specs(context, specs_id):
|
||||
try:
|
||||
qos_specs.get_qos_specs(context, specs_id)
|
||||
@ -111,7 +54,6 @@ class QoSSpecsController(wsgi.Controller):
|
||||
method,
|
||||
payload)
|
||||
|
||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
||||
def index(self, req):
|
||||
"""Returns the list of qos_specs."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -132,7 +74,6 @@ class QoSSpecsController(wsgi.Controller):
|
||||
sort_dirs=sort_dirs)
|
||||
return self._view_builder.summary_list(req, specs)
|
||||
|
||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
||||
def create(self, req, body=None):
|
||||
context = req.environ['cinder.context']
|
||||
authorize(context)
|
||||
@ -178,7 +119,6 @@ class QoSSpecsController(wsgi.Controller):
|
||||
|
||||
return self._view_builder.detail(req, spec)
|
||||
|
||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
||||
def update(self, req, id, body=None):
|
||||
context = req.environ['cinder.context']
|
||||
authorize(context)
|
||||
@ -213,7 +153,6 @@ class QoSSpecsController(wsgi.Controller):
|
||||
|
||||
return body
|
||||
|
||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return a single qos spec item."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -263,7 +202,6 @@ class QoSSpecsController(wsgi.Controller):
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.deserializers(xml=QoSSpecsKeyDeserializer)
|
||||
def delete_keys(self, req, id, body):
|
||||
"""Deletes specified keys in qos specs."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -297,7 +235,6 @@ class QoSSpecsController(wsgi.Controller):
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.serializers(xml=AssociationsTemplate)
|
||||
def associations(self, req, id):
|
||||
"""List all associations of given qos specs."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -461,7 +398,6 @@ class Qos_specs_manage(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "Qos_specs_manage"
|
||||
alias = "qos-specs"
|
||||
namespace = "http://docs.openstack.org/volume/ext/qos-specs/api/v1"
|
||||
updated = "2013-08-02T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -17,7 +17,6 @@ import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -31,19 +30,6 @@ QUOTAS = quota.QUOTAS
|
||||
authorize = extensions.extension_authorizer('volume', '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(wsgi.Controller):
|
||||
|
||||
def _format_quota_set(self, quota_class, quota_set):
|
||||
@ -53,7 +39,6 @@ class QuotaClassSetsController(wsgi.Controller):
|
||||
|
||||
return dict(quota_class_set=quota_set)
|
||||
|
||||
@wsgi.serializers(xml=QuotaClassTemplate)
|
||||
def show(self, req, id):
|
||||
context = req.environ['cinder.context']
|
||||
authorize(context)
|
||||
@ -65,7 +50,6 @@ class QuotaClassSetsController(wsgi.Controller):
|
||||
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['cinder.context']
|
||||
authorize(context)
|
||||
@ -97,8 +81,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):
|
||||
|
@ -17,7 +17,6 @@ import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import db
|
||||
from cinder.db.sqlalchemy import api as sqlalchemy_api
|
||||
from cinder import exception
|
||||
@ -39,18 +38,6 @@ authorize_show = extensions.extension_authorizer('volume', 'quotas:show')
|
||||
authorize_delete = extensions.extension_authorizer('volume', '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(wsgi.Controller):
|
||||
|
||||
def _format_quota_set(self, project_id, quota_set):
|
||||
@ -155,7 +142,6 @@ class QuotaSetsController(wsgi.Controller):
|
||||
return True
|
||||
return False
|
||||
|
||||
@wsgi.serializers(xml=QuotaTemplate)
|
||||
def show(self, req, id):
|
||||
"""Show quota for a particular tenant
|
||||
|
||||
@ -197,7 +183,6 @@ class QuotaSetsController(wsgi.Controller):
|
||||
quotas = self._get_quotas(context, target_project_id, usage)
|
||||
return self._format_quota_set(target_project_id, quotas)
|
||||
|
||||
@wsgi.serializers(xml=QuotaTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update Quota for a particular tenant
|
||||
|
||||
@ -337,7 +322,6 @@ class QuotaSetsController(wsgi.Controller):
|
||||
|
||||
return reservations
|
||||
|
||||
@wsgi.serializers(xml=QuotaTemplate)
|
||||
def defaults(self, req, id):
|
||||
context = req.environ['cinder.context']
|
||||
authorize_show(context)
|
||||
@ -345,7 +329,6 @@ class QuotaSetsController(wsgi.Controller):
|
||||
return self._format_quota_set(id, QUOTAS.get_defaults(
|
||||
context, project_id=id))
|
||||
|
||||
@wsgi.serializers(xml=QuotaTemplate)
|
||||
def delete(self, req, id):
|
||||
"""Delete Quota for a particular tenant.
|
||||
|
||||
@ -436,7 +419,6 @@ class Quotas(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "Quotas"
|
||||
alias = "os-quota-sets"
|
||||
namespace = "http://docs.openstack.org/volume/ext/quotas-sets/api/v1.1"
|
||||
updated = "2011-08-08T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -17,7 +17,6 @@ import webob.exc
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.v2 import volumes
|
||||
from cinder.i18n import _
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -53,7 +52,6 @@ class Scheduler_hints(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "SchedulerHints"
|
||||
alias = "OS-SCH-HNT"
|
||||
namespace = volumes.SCHEDULER_HINTS_NAMESPACE
|
||||
updated = "2013-04-18T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
|
@ -56,7 +56,6 @@ class Scheduler_stats(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "Scheduler_stats"
|
||||
alias = "scheduler-stats"
|
||||
namespace = "http://docs.openstack.org/volume/ext/scheduler-stats/api/v1"
|
||||
updated = "2014-09-07T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -22,7 +22,6 @@ import webob.exc
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import objects
|
||||
@ -36,51 +35,12 @@ LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.extension_authorizer('volume', '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')
|
||||
elem.set('disabled_reason')
|
||||
elem.set('replication_status')
|
||||
elem.set('active_backend_id')
|
||||
elem.set('frozen')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class ServicesUpdateTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
# TODO(uni): template elements of 'host', 'service' and 'disabled'
|
||||
# should be deprecated to make ServicesUpdateTemplate consistent
|
||||
# with ServicesIndexTemplate. Still keeping it here for API
|
||||
# compatibility sake.
|
||||
root = xmlutil.TemplateElement('host')
|
||||
root.set('host')
|
||||
root.set('service')
|
||||
root.set('disabled')
|
||||
root.set('binary')
|
||||
root.set('status')
|
||||
root.set('disabled_reason')
|
||||
root.set('replication_status')
|
||||
root.set('active_backend_id')
|
||||
root.set('frozen')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class ServiceController(wsgi.Controller):
|
||||
def __init__(self, ext_mgr=None):
|
||||
self.ext_mgr = ext_mgr
|
||||
super(ServiceController, self).__init__()
|
||||
self.volume_api = volume.API()
|
||||
|
||||
@wsgi.serializers(xml=ServicesIndexTemplate)
|
||||
def index(self, req):
|
||||
"""Return a list of all running services.
|
||||
|
||||
@ -154,7 +114,6 @@ class ServiceController(wsgi.Controller):
|
||||
def _failover(self, context, host, backend_id=None):
|
||||
return self.volume_api.failover_host(context, host, backend_id)
|
||||
|
||||
@wsgi.serializers(xml=ServicesUpdateTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Enable/Disable scheduling for a service.
|
||||
|
||||
@ -236,7 +195,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):
|
||||
|
@ -100,8 +100,6 @@ class Snapshot_actions(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "SnapshotActions"
|
||||
alias = "os-snapshot-actions"
|
||||
namespace = \
|
||||
"http://docs.openstack.org/volume/ext/snapshot-actions/api/v1.1"
|
||||
updated = "2013-07-16T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
|
@ -18,7 +18,6 @@ from webob import exc
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.v2 import snapshots
|
||||
from cinder.api.views import snapshots as snapshot_views
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -39,7 +38,6 @@ class SnapshotManageController(wsgi.Controller):
|
||||
self.volume_api = cinder_volume.API()
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=snapshots.SnapshotTemplate)
|
||||
def create(self, req, body):
|
||||
"""Instruct Cinder to manage a storage snapshot object.
|
||||
|
||||
@ -138,8 +136,6 @@ class Snapshot_manage(extensions.ExtensionDescriptor):
|
||||
|
||||
name = 'SnapshotManage'
|
||||
alias = 'os-snapshot-manage'
|
||||
namespace = ('http://docs.openstack.org/volume/ext/'
|
||||
'os-snapshot-manage/api/v1')
|
||||
updated = '2014-12-31T00:00:00+00:00'
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -66,8 +66,6 @@ class Snapshot_unmanage(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "SnapshotUnmanage"
|
||||
alias = "os-snapshot-unmanage"
|
||||
namespace = ('http://docs.openstack.org/snapshot/ext/snapshot-unmanage'
|
||||
'/api/v1')
|
||||
updated = "2014-12-31T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
|
@ -20,7 +20,6 @@ import webob
|
||||
from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -30,26 +29,6 @@ from cinder.volume import volume_types
|
||||
authorize = extensions.extension_authorizer('volume', 'types_extra_specs')
|
||||
|
||||
|
||||
class VolumeTypeExtraSpecsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.make_flat_dict('extra_specs', selector='extra_specs')
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeTypeExtraSpecTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
tagname = xmlutil.Selector('key')
|
||||
|
||||
def extraspec_sel(obj, do_raise=False):
|
||||
# Have to extract the key and value for later use...
|
||||
key, value = list(obj.items())[0]
|
||||
return dict(key=key, value=value)
|
||||
|
||||
root = xmlutil.TemplateElement(tagname, selector=extraspec_sel)
|
||||
root.text = 'value'
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||
"""The volume type extra specs API controller for the OpenStack API."""
|
||||
|
||||
@ -66,7 +45,6 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||
except exception.VolumeTypeNotFound as ex:
|
||||
raise webob.exc.HTTPNotFound(explanation=ex.msg)
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate)
|
||||
def index(self, req, type_id):
|
||||
"""Returns the list of extra specs for a given volume type."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -85,7 +63,6 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||
self.validate_string_length(value, 'Value for key "%s"' % key,
|
||||
min_length=0, max_length=255)
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate)
|
||||
def create(self, req, type_id, body=None):
|
||||
context = req.environ['cinder.context']
|
||||
authorize(context)
|
||||
@ -106,7 +83,6 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||
notifier_info)
|
||||
return body
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeExtraSpecTemplate)
|
||||
def update(self, req, type_id, id, body=None):
|
||||
context = req.environ['cinder.context']
|
||||
authorize(context)
|
||||
@ -133,7 +109,6 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||
notifier_info)
|
||||
return body
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeExtraSpecTemplate)
|
||||
def show(self, req, type_id, id):
|
||||
"""Return a single extra spec item."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -178,7 +153,6 @@ class Types_extra_specs(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "TypesExtraSpecs"
|
||||
alias = "os-types-extra-specs"
|
||||
namespace = "http://docs.openstack.org/volume/ext/types-extra-specs/api/v1"
|
||||
updated = "2011-08-24T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -20,7 +20,6 @@ import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.v1 import types
|
||||
from cinder.api.views import types as views_types
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -48,7 +47,6 @@ class VolumeTypesManageController(wsgi.Controller):
|
||||
rpc.get_notifier('volumeType').info(context, method, payload)
|
||||
|
||||
@wsgi.action("create")
|
||||
@wsgi.serializers(xml=types.VolumeTypeTemplate)
|
||||
def _create(self, req, body):
|
||||
"""Creates a new volume type."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -101,7 +99,6 @@ class VolumeTypesManageController(wsgi.Controller):
|
||||
return self._view_builder.show(req, vol_type)
|
||||
|
||||
@wsgi.action("update")
|
||||
@wsgi.serializers(xml=types.VolumeTypeTemplate)
|
||||
def _update(self, req, id, body):
|
||||
# Update description for a given volume type.
|
||||
context = req.environ['cinder.context']
|
||||
@ -192,7 +189,6 @@ class Types_manage(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "TypesManage"
|
||||
alias = "os-types-manage"
|
||||
namespace = "http://docs.openstack.org/volume/ext/types-manage/api/v1"
|
||||
updated = "2011-08-24T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
|
@ -51,7 +51,6 @@ class Used_limits(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "UsedLimits"
|
||||
alias = 'os-used-limits'
|
||||
namespace = "http://docs.openstack.org/volume/ext/used-limits/api/v1.1"
|
||||
updated = "2013-10-03T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
|
@ -24,7 +24,6 @@ import webob
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import api_version_request
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import utils
|
||||
@ -40,50 +39,6 @@ def authorize(context, action_name):
|
||||
extensions.extension_authorizer('volume', action)(context)
|
||||
|
||||
|
||||
class VolumeToImageSerializer(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('os-volume_upload_image',
|
||||
selector='os-volume_upload_image')
|
||||
root.set('id')
|
||||
root.set('updated_at')
|
||||
root.set('status')
|
||||
root.set('display_description')
|
||||
root.set('size')
|
||||
root.set('volume_type')
|
||||
root.set('image_id')
|
||||
root.set('container_format')
|
||||
root.set('disk_format')
|
||||
root.set('image_name')
|
||||
root.set('protected')
|
||||
if CONF.glance_api_version == 2:
|
||||
root.set('visibility')
|
||||
else:
|
||||
root.set('is_public')
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeToImageDeserializer(wsgi.XMLDeserializer):
|
||||
"""Deserializer to handle xml-formatted requests."""
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
action_node = dom.childNodes[0]
|
||||
action_name = action_node.tagName
|
||||
|
||||
action_data = {}
|
||||
attributes = ["force", "image_name", "container_format", "disk_format",
|
||||
"protected"]
|
||||
if CONF.glance_api_version == 2:
|
||||
attributes.append('visibility')
|
||||
else:
|
||||
attributes.append('is_public')
|
||||
for attr in attributes:
|
||||
if action_node.hasAttribute(attr):
|
||||
action_data[attr] = action_node.getAttribute(attr)
|
||||
if 'force' in action_data and action_data['force'] == 'True':
|
||||
action_data['force'] = True
|
||||
return {'body': {action_name: action_data}}
|
||||
|
||||
|
||||
class VolumeActionsController(wsgi.Controller):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VolumeActionsController, self).__init__(*args, **kwargs)
|
||||
@ -261,8 +216,6 @@ class VolumeActionsController(wsgi.Controller):
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.action('os-volume_upload_image')
|
||||
@wsgi.serializers(xml=VolumeToImageSerializer)
|
||||
@wsgi.deserializers(xml=VolumeToImageDeserializer)
|
||||
def _volume_upload_image(self, req, id, body):
|
||||
"""Uploads the specified volume to image service."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -423,7 +376,6 @@ class Volume_actions(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeActions"
|
||||
alias = "os-volume-actions"
|
||||
namespace = "http://docs.openstack.org/volume/ext/volume-actions/api/v1.1"
|
||||
updated = "2012-05-31T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
|
@ -17,30 +17,21 @@
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import db
|
||||
|
||||
authorize = extensions.extension_authorizer('volume',
|
||||
'volume_encryption_metadata')
|
||||
|
||||
|
||||
class VolumeEncryptionMetadataTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.make_flat_dict('encryption', selector='encryption')
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeEncryptionMetadataController(wsgi.Controller):
|
||||
"""The volume encryption metadata API extension."""
|
||||
|
||||
@wsgi.serializers(xml=VolumeEncryptionMetadataTemplate)
|
||||
def index(self, req, volume_id):
|
||||
"""Returns the encryption metadata for a given volume."""
|
||||
context = req.environ['cinder.context']
|
||||
authorize(context)
|
||||
return db.volume_encryption_metadata_get(context, volume_id)
|
||||
|
||||
@wsgi.serializers(xml=VolumeEncryptionMetadataTemplate)
|
||||
def show(self, req, volume_id, id):
|
||||
"""Return a single encryption item."""
|
||||
encryption_item = self.index(req, volume_id)
|
||||
@ -55,8 +46,6 @@ class Volume_encryption_metadata(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeEncryptionMetadata"
|
||||
alias = "os-volume-encryption-metadata"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"os-volume-encryption-metadata/api/v1")
|
||||
updated = "2013-07-10T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -16,7 +16,6 @@ from oslo_log import log as logging
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -34,7 +33,6 @@ class VolumeHostAttributeController(wsgi.Controller):
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeHostAttributeTemplate())
|
||||
volume = resp_obj.obj['volume']
|
||||
self._add_volume_host_attribute(req, volume)
|
||||
|
||||
@ -42,7 +40,6 @@ class VolumeHostAttributeController(wsgi.Controller):
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeListHostAttributeTemplate())
|
||||
for vol in list(resp_obj.obj['volumes']):
|
||||
self._add_volume_host_attribute(req, vol)
|
||||
|
||||
@ -52,35 +49,9 @@ class Volume_host_attribute(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeHostAttribute"
|
||||
alias = "os-vol-host-attr"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"volume_host_attribute/api/v2")
|
||||
updated = "2011-11-03T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = VolumeHostAttributeController()
|
||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
def make_volume(elem):
|
||||
elem.set('{%s}host' % Volume_host_attribute.namespace,
|
||||
'%s:host' % Volume_host_attribute.alias)
|
||||
|
||||
|
||||
class VolumeHostAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
alias = Volume_host_attribute.alias
|
||||
namespace = Volume_host_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumeListHostAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')
|
||||
make_volume(elem)
|
||||
alias = Volume_host_attribute.alias
|
||||
namespace = Volume_host_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
@ -20,7 +20,6 @@ from oslo_log import log as logging
|
||||
from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import volume
|
||||
@ -75,22 +74,18 @@ class VolumeImageMetadataController(wsgi.Controller):
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeImageMetadataTemplate())
|
||||
self._add_image_metadata(context, [resp_obj.obj['volume']])
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumesImageMetadataTemplate())
|
||||
# Just get the image metadata of those volumes in response.
|
||||
volumes = list(resp_obj.obj.get('volumes', []))
|
||||
if volumes:
|
||||
self._add_image_metadata(context, volumes)
|
||||
|
||||
@wsgi.action("os-set_image_metadata")
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def create(self, req, id, body):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
@ -130,7 +125,6 @@ class VolumeImageMetadataController(wsgi.Controller):
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||
|
||||
@wsgi.action("os-show_image_metadata")
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
def index(self, req, id, body):
|
||||
context = req.environ['cinder.context']
|
||||
return {'metadata': self._get_image_metadata(context, id)[1]}
|
||||
@ -167,46 +161,9 @@ class Volume_image_metadata(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeImageMetadata"
|
||||
alias = "os-vol-image-meta"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"volume_image_metadata/api/v1")
|
||||
updated = "2012-12-07T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = VolumeImageMetadataController()
|
||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
class VolumeImageMetadataMetadataTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_image_metadata',
|
||||
selector='volume_image_metadata')
|
||||
elem = xmlutil.SubTemplateElement(root, 'meta',
|
||||
selector=xmlutil.get_items)
|
||||
elem.set('key', 0)
|
||||
elem.text = 1
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeImageMetadataTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
root.append(VolumeImageMetadataMetadataTemplate())
|
||||
|
||||
alias = Volume_image_metadata.alias
|
||||
namespace = Volume_image_metadata.namespace
|
||||
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumesImageMetadataTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volume')
|
||||
elem.append(VolumeImageMetadataMetadataTemplate())
|
||||
|
||||
alias = Volume_image_metadata.alias
|
||||
namespace = Volume_image_metadata.namespace
|
||||
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
@ -19,7 +19,6 @@ from webob import exc
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.v2.views import volumes as volume_views
|
||||
from cinder.api.v2 import volumes
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import utils
|
||||
@ -40,8 +39,6 @@ class VolumeManageController(wsgi.Controller):
|
||||
self.volume_api = cinder_volume.API()
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=volumes.VolumeTemplate)
|
||||
@wsgi.deserializers(xml=volumes.CreateDeserializer)
|
||||
def create(self, req, body):
|
||||
"""Instruct Cinder to manage a storage object.
|
||||
|
||||
@ -155,8 +152,6 @@ class Volume_manage(extensions.ExtensionDescriptor):
|
||||
|
||||
name = 'VolumeManage'
|
||||
alias = 'os-volume-manage'
|
||||
namespace = ('http://docs.openstack.org/volume/ext/'
|
||||
'os-volume-manage/api/v1')
|
||||
updated = '2014-02-10T00:00:00+00:00'
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
|
||||
|
||||
authorize = extensions.soft_extension_authorizer('volume',
|
||||
'volume_mig_status_attribute')
|
||||
@ -33,14 +31,12 @@ class VolumeMigStatusAttributeController(wsgi.Controller):
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeMigStatusAttributeTemplate())
|
||||
self._add_volume_mig_status_attribute(req, resp_obj.obj['volume'])
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeListMigStatusAttributeTemplate())
|
||||
for vol in list(resp_obj.obj['volumes']):
|
||||
self._add_volume_mig_status_attribute(req, vol)
|
||||
|
||||
@ -50,37 +46,9 @@ class Volume_mig_status_attribute(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeMigStatusAttribute"
|
||||
alias = "os-vol-mig-status-attr"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"volume_mig_status_attribute/api/v1")
|
||||
updated = "2013-08-08T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = VolumeMigStatusAttributeController()
|
||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
def make_volume(elem):
|
||||
elem.set('{%s}migstat' % Volume_mig_status_attribute.namespace,
|
||||
'%s:migstat' % Volume_mig_status_attribute.alias)
|
||||
elem.set('{%s}name_id' % Volume_mig_status_attribute.namespace,
|
||||
'%s:name_id' % Volume_mig_status_attribute.alias)
|
||||
|
||||
|
||||
class VolumeMigStatusAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
alias = Volume_mig_status_attribute.alias
|
||||
namespace = Volume_mig_status_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumeListMigStatusAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')
|
||||
make_volume(elem)
|
||||
alias = Volume_mig_status_attribute.alias
|
||||
namespace = Volume_mig_status_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
|
||||
|
||||
authorize = extensions.soft_extension_authorizer('volume',
|
||||
@ -31,7 +30,6 @@ class VolumeTenantAttributeController(wsgi.Controller):
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeTenantAttributeTemplate())
|
||||
volume = resp_obj.obj['volume']
|
||||
self._add_volume_tenant_attribute(req, volume)
|
||||
|
||||
@ -39,7 +37,6 @@ class VolumeTenantAttributeController(wsgi.Controller):
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['cinder.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeListTenantAttributeTemplate())
|
||||
for vol in list(resp_obj.obj['volumes']):
|
||||
self._add_volume_tenant_attribute(req, vol)
|
||||
|
||||
@ -49,35 +46,9 @@ class Volume_tenant_attribute(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeTenantAttribute"
|
||||
alias = "os-vol-tenant-attr"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"volume_tenant_attribute/api/v2")
|
||||
updated = "2011-11-03T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = VolumeTenantAttributeController()
|
||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
def make_volume(elem):
|
||||
elem.set('{%s}tenant_id' % Volume_tenant_attribute.namespace,
|
||||
'%s:tenant_id' % Volume_tenant_attribute.alias)
|
||||
|
||||
|
||||
class VolumeTenantAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
alias = Volume_tenant_attribute.alias
|
||||
namespace = Volume_tenant_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumeListTenantAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')
|
||||
make_volume(elem)
|
||||
alias = Volume_tenant_attribute.alias
|
||||
namespace = Volume_tenant_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
@ -21,79 +21,13 @@ from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import transfers as transfer_view
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder import transfer as transferAPI
|
||||
from cinder import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_transfer(elem):
|
||||
elem.set('id')
|
||||
elem.set('volume_id')
|
||||
elem.set('created_at')
|
||||
elem.set('name')
|
||||
elem.set('auth_key')
|
||||
|
||||
|
||||
class TransferTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('transfer', selector='transfer')
|
||||
make_transfer(root)
|
||||
alias = Volume_transfer.alias
|
||||
namespace = Volume_transfer.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class TransfersTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('transfers')
|
||||
elem = xmlutil.SubTemplateElement(root, 'transfer',
|
||||
selector='transfers')
|
||||
make_transfer(elem)
|
||||
alias = Volume_transfer.alias
|
||||
namespace = Volume_transfer.namespace
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class CreateDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
transfer = self._extract_transfer(dom)
|
||||
return {'body': {'transfer': transfer}}
|
||||
|
||||
def _extract_transfer(self, node):
|
||||
transfer = {}
|
||||
transfer_node = self.find_first_child_named(node, 'transfer')
|
||||
|
||||
attributes = ['volume_id', 'name']
|
||||
|
||||
for attr in attributes:
|
||||
if transfer_node.getAttribute(attr):
|
||||
transfer[attr] = transfer_node.getAttribute(attr)
|
||||
return transfer
|
||||
|
||||
|
||||
class AcceptDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
def default(self, string):
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
transfer = self._extract_transfer(dom)
|
||||
return {'body': {'accept': transfer}}
|
||||
|
||||
def _extract_transfer(self, node):
|
||||
transfer = {}
|
||||
transfer_node = self.find_first_child_named(node, 'accept')
|
||||
|
||||
attributes = ['auth_key']
|
||||
|
||||
for attr in attributes:
|
||||
if transfer_node.getAttribute(attr):
|
||||
transfer[attr] = transfer_node.getAttribute(attr)
|
||||
return transfer
|
||||
|
||||
|
||||
class VolumeTransferController(wsgi.Controller):
|
||||
"""The Volume Transfer API controller for the OpenStack API."""
|
||||
|
||||
@ -103,7 +37,6 @@ class VolumeTransferController(wsgi.Controller):
|
||||
self.transfer_api = transferAPI.API()
|
||||
super(VolumeTransferController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=TransferTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about active transfers."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -115,12 +48,10 @@ class VolumeTransferController(wsgi.Controller):
|
||||
|
||||
return self._view_builder.detail(req, transfer)
|
||||
|
||||
@wsgi.serializers(xml=TransfersTemplate)
|
||||
def index(self, req):
|
||||
"""Returns a summary list of transfers."""
|
||||
return self._get_transfers(req, is_detail=False)
|
||||
|
||||
@wsgi.serializers(xml=TransfersTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of transfers."""
|
||||
return self._get_transfers(req, is_detail=True)
|
||||
@ -144,8 +75,6 @@ class VolumeTransferController(wsgi.Controller):
|
||||
return transfers
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=TransferTemplate)
|
||||
@wsgi.deserializers(xml=CreateDeserializer)
|
||||
def create(self, req, body):
|
||||
"""Create a new volume transfer."""
|
||||
LOG.debug('Creating new volume transfer %s', body)
|
||||
@ -183,8 +112,6 @@ class VolumeTransferController(wsgi.Controller):
|
||||
return transfer
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=TransferTemplate)
|
||||
@wsgi.deserializers(xml=AcceptDeserializer)
|
||||
def accept(self, req, id, body):
|
||||
"""Accept a new volume transfer."""
|
||||
transfer_id = id
|
||||
@ -235,8 +162,6 @@ class Volume_transfer(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeTransfer"
|
||||
alias = "os-volume-transfer"
|
||||
namespace = "http://docs.openstack.org/volume/ext/volume-transfer/" + \
|
||||
"api/v1.1"
|
||||
updated = "2013-05-29T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -19,7 +19,6 @@ import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.volume import volume_types
|
||||
@ -30,45 +29,6 @@ soft_authorize = extensions.soft_extension_authorizer('volume',
|
||||
authorize = extensions.extension_authorizer('volume', 'volume_type_access')
|
||||
|
||||
|
||||
def make_volume_type(elem):
|
||||
elem.set('{%s}is_public' % Volume_type_access.namespace,
|
||||
'%s:is_public' % Volume_type_access.alias)
|
||||
|
||||
|
||||
def make_volume_type_access(elem):
|
||||
elem.set('volume_type_id')
|
||||
elem.set('project_id')
|
||||
|
||||
|
||||
class VolumeTypeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_type', selector='volume_type')
|
||||
make_volume_type(root)
|
||||
alias = Volume_type_access.alias
|
||||
namespace = Volume_type_access.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumeTypesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_types')
|
||||
elem = xmlutil.SubTemplateElement(
|
||||
root, 'volume_type', selector='volume_types')
|
||||
make_volume_type(elem)
|
||||
alias = Volume_type_access.alias
|
||||
namespace = Volume_type_access.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumeTypeAccessTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_type_access')
|
||||
elem = xmlutil.SubTemplateElement(root, 'access',
|
||||
selector='volume_type_access')
|
||||
make_volume_type_access(elem)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
def _marshall_volume_type_access(vol_type):
|
||||
rval = []
|
||||
for project_id in vol_type['projects']:
|
||||
@ -84,7 +44,6 @@ class VolumeTypeAccessController(object):
|
||||
def __init__(self):
|
||||
super(VolumeTypeAccessController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeAccessTemplate)
|
||||
def index(self, req, type_id):
|
||||
context = req.environ['cinder.context']
|
||||
authorize(context)
|
||||
@ -123,8 +82,6 @@ class VolumeTypeActionController(wsgi.Controller):
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['cinder.context']
|
||||
if soft_authorize(context):
|
||||
# Attach our slave template to the response object
|
||||
resp_obj.attach(xml=VolumeTypeTemplate())
|
||||
vol_type = req.cached_resource_by_id(id, name='types')
|
||||
self._extend_vol_type(resp_obj.obj['volume_type'], vol_type)
|
||||
|
||||
@ -132,8 +89,6 @@ class VolumeTypeActionController(wsgi.Controller):
|
||||
def index(self, req, resp_obj):
|
||||
context = req.environ['cinder.context']
|
||||
if soft_authorize(context):
|
||||
# Attach our slave template to the response object
|
||||
resp_obj.attach(xml=VolumeTypesTemplate())
|
||||
for vol_type_rval in list(resp_obj.obj['volume_types']):
|
||||
type_id = vol_type_rval['id']
|
||||
vol_type = req.cached_resource_by_id(type_id, name='types')
|
||||
@ -143,8 +98,6 @@ class VolumeTypeActionController(wsgi.Controller):
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['cinder.context']
|
||||
if soft_authorize(context):
|
||||
# Attach our slave template to the response object
|
||||
resp_obj.attach(xml=VolumeTypesTemplate())
|
||||
for vol_type_rval in list(resp_obj.obj['volume_types']):
|
||||
type_id = vol_type_rval['id']
|
||||
vol_type = req.cached_resource_by_id(type_id, name='types')
|
||||
@ -154,8 +107,6 @@ class VolumeTypeActionController(wsgi.Controller):
|
||||
def create(self, req, body, resp_obj):
|
||||
context = req.environ['cinder.context']
|
||||
if soft_authorize(context):
|
||||
# Attach our slave template to the response object
|
||||
resp_obj.attach(xml=VolumeTypeTemplate())
|
||||
type_id = resp_obj.obj['volume_type']['id']
|
||||
vol_type = req.cached_resource_by_id(type_id, name='types')
|
||||
self._extend_vol_type(resp_obj.obj['volume_type'], vol_type)
|
||||
@ -195,8 +146,6 @@ class Volume_type_access(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeTypeAccess"
|
||||
alias = "os-volume-type-access"
|
||||
namespace = ("http://docs.openstack.org/volume/"
|
||||
"ext/os-volume-type-access/api/v1")
|
||||
updated = "2014-06-26T00:00:00Z"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -19,7 +19,6 @@ import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -33,12 +32,6 @@ authorize = extensions.extension_authorizer('volume',
|
||||
CONTROL_LOCATION = ['front-end', 'back-end']
|
||||
|
||||
|
||||
class VolumeTypeEncryptionTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.make_flat_dict('encryption', selector='encryption')
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeTypeEncryptionController(wsgi.Controller):
|
||||
"""The volume type encryption API controller for the OpenStack API."""
|
||||
|
||||
@ -89,7 +82,6 @@ class VolumeTypeEncryptionController(wsgi.Controller):
|
||||
else:
|
||||
return False
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeEncryptionTemplate)
|
||||
def index(self, req, type_id):
|
||||
"""Returns the encryption specs for a given volume type."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -97,7 +89,6 @@ class VolumeTypeEncryptionController(wsgi.Controller):
|
||||
self._check_type(context, type_id)
|
||||
return self._get_volume_type_encryption(context, type_id)
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeEncryptionTemplate)
|
||||
def create(self, req, type_id, body=None):
|
||||
"""Create encryption specs for an existing volume type."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -125,7 +116,6 @@ class VolumeTypeEncryptionController(wsgi.Controller):
|
||||
notifier.info(context, 'volume_type_encryption.create', notifier_info)
|
||||
return body
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeEncryptionTemplate)
|
||||
def update(self, req, type_id, id, body=None):
|
||||
"""Update encryption specs for a given volume type."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -153,7 +143,6 @@ class VolumeTypeEncryptionController(wsgi.Controller):
|
||||
|
||||
return body
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeEncryptionTemplate)
|
||||
def show(self, req, type_id, id):
|
||||
"""Return a single encryption item."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -190,8 +179,6 @@ class Volume_type_encryption(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeTypeEncryption"
|
||||
alias = "encryption"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"volume-type-encryption/api/v1")
|
||||
updated = "2013-07-01T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
|
@ -66,7 +66,6 @@ class Volume_unmanage(extensions.ExtensionDescriptor):
|
||||
|
||||
name = "VolumeUnmanage"
|
||||
alias = "os-volume-unmanage"
|
||||
namespace = "http://docs.openstack.org/volume/ext/volume-unmanage/api/v1.1"
|
||||
updated = "2012-05-31T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
|
@ -24,7 +24,6 @@ import webob.exc
|
||||
|
||||
import cinder.api.openstack
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _LE, _LI, _LW
|
||||
import cinder.policy
|
||||
@ -49,12 +48,6 @@ class ExtensionDescriptor(object):
|
||||
# The alias for the extension, e.g., 'FOXNSOX'
|
||||
alias = None
|
||||
|
||||
# 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
|
||||
@ -82,55 +75,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):
|
||||
|
||||
@ -143,19 +87,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 self.extension_manager.extensions.items():
|
||||
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
|
||||
@ -238,7 +179,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:
|
||||
LOG.exception(_LE("Exception loading extension."))
|
||||
|
@ -38,11 +38,11 @@ class APIMapper(routes.Mapper):
|
||||
|
||||
def connect(self, *args, **kwargs):
|
||||
# NOTE(inhye): Default the format part of a route to only accept json
|
||||
# and xml so it doesn't eat all characters after a '.'
|
||||
# so it doesn't eat all characters after a '.'
|
||||
# in the url.
|
||||
kwargs.setdefault('requirements', {})
|
||||
if not kwargs['requirements'].get('format'):
|
||||
kwargs['requirements']['format'] = 'json|xml'
|
||||
kwargs['requirements']['format'] = 'json'
|
||||
return routes.Mapper.connect(self, *args, **kwargs)
|
||||
|
||||
|
||||
|
@ -18,12 +18,8 @@ import functools
|
||||
import inspect
|
||||
import math
|
||||
import time
|
||||
from xml.dom import minidom
|
||||
from xml.parsers import expat
|
||||
|
||||
from lxml import etree
|
||||
from oslo_log import log as logging
|
||||
from oslo_log import versionutils
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import excutils
|
||||
@ -41,26 +37,16 @@ from cinder import utils
|
||||
from cinder.wsgi import common as wsgi
|
||||
|
||||
|
||||
XML_NS_V1 = 'http://docs.openstack.org/api/openstack-block-storage/1.0/content'
|
||||
XML_NS_V2 = 'http://docs.openstack.org/api/openstack-block-storage/2.0/content'
|
||||
XML_NS_ATOM = 'http://www.w3.org/2005/Atom'
|
||||
XML_WARNING = False
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
|
||||
@ -372,95 +358,6 @@ class JSONDeserializer(TextDeserializer):
|
||||
return {'body': self._from_json(datastring)}
|
||||
|
||||
|
||||
class XMLDeserializer(TextDeserializer):
|
||||
|
||||
def __init__(self, metadata=None):
|
||||
"""Initialize XMLDeserializer.
|
||||
|
||||
: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_in_namespace(self, parent, namespace, name):
|
||||
"""Search a nodes children for the first child with a given name."""
|
||||
for node in parent.childNodes:
|
||||
if (node.localName == name and
|
||||
node.namespaceURI and
|
||||
node.namespaceURI == namespace):
|
||||
return node
|
||||
return None
|
||||
|
||||
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."""
|
||||
text = []
|
||||
# Cannot assume entire text will be in a single child node because SAX
|
||||
# parsers may split contiguous character data into multiple chunks
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == child.TEXT_NODE:
|
||||
text.append(child.nodeValue)
|
||||
return ''.join(text)
|
||||
|
||||
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."""
|
||||
|
||||
@ -478,111 +375,6 @@ class JSONDictSerializer(DictSerializer):
|
||||
return jsonutils.dump_as_bytes(data)
|
||||
|
||||
|
||||
class XMLDictSerializer(DictSerializer):
|
||||
|
||||
def __init__(self, metadata=None, xmlns=None):
|
||||
"""Initialize XMLDictSerializer.
|
||||
|
||||
: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 = list(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 sorted(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 sorted(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.
|
||||
|
||||
@ -784,15 +576,6 @@ def action_peek_json(body):
|
||||
return list(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.
|
||||
|
||||
@ -859,16 +642,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
|
||||
@ -1542,25 +1322,11 @@ class Fault(webob.exc.HTTPException):
|
||||
req.api_version_request.get_string())
|
||||
self.wrapped_exc.headers['Vary'] = API_VERSION_REQUEST_HEADER
|
||||
|
||||
# 'code' is an attribute on the fault tag itself
|
||||
metadata = {'attributes': {fault_name: 'code'}}
|
||||
|
||||
xml_serializer = XMLDictSerializer(metadata, XML_NS_V2)
|
||||
|
||||
content_type = req.best_match_content_type()
|
||||
serializer = {
|
||||
'application/xml': xml_serializer,
|
||||
'application/json': JSONDictSerializer(),
|
||||
}[content_type]
|
||||
|
||||
if content_type == 'application/xml':
|
||||
global XML_WARNING
|
||||
if not XML_WARNING:
|
||||
msg = _('XML support has been deprecated and will be removed '
|
||||
'in the N release.')
|
||||
versionutils.report_deprecated_feature(LOG, msg)
|
||||
XML_WARNING = True
|
||||
|
||||
body = serializer.serialize(fault_data)
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
@ -1606,7 +1372,6 @@ class OverLimitFault(webob.exc.HTTPException):
|
||||
def __call__(self, request):
|
||||
"""Serializes the wrapped exception conforming to our error format."""
|
||||
content_type = request.best_match_content_type()
|
||||
metadata = {"attributes": {"overLimitFault": "code"}}
|
||||
|
||||
def translate(msg):
|
||||
locale = request.best_match_language()
|
||||
@ -1617,9 +1382,7 @@ class OverLimitFault(webob.exc.HTTPException):
|
||||
self.content['overLimitFault']['details'] = \
|
||||
translate(self.content['overLimitFault']['details'])
|
||||
|
||||
xml_serializer = XMLDictSerializer(metadata, XML_NS_V2)
|
||||
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>
|
@ -1,8 +0,0 @@
|
||||
<element name="associations" xmlns="http://relaxng.org/ns/structure/1.0">
|
||||
<attribute name="name"> <text/> </attribute>
|
||||
<attribute name="id"> <text/> </attribute>
|
||||
<attribute name="association_type"> <text/> </attribute>
|
||||
<zeroOrMore>
|
||||
<externalRef href="../atom-link.rng"/>
|
||||
</zeroOrMore>
|
||||
</element>
|
@ -1,5 +0,0 @@
|
||||
<element name="qos_associations" xmlns="http://relaxng.org/ns/structure/1.0">
|
||||
<zeroOrMore>
|
||||
<externalRef href="qos_association.rng"/>
|
||||
</zeroOrMore>
|
||||
</element>
|
@ -1,16 +0,0 @@
|
||||
<element name="qos_spec" xmlns="http://relaxng.org/ns/structure/1.0">
|
||||
<attribute name="name"> <text/> </attribute>
|
||||
<attribute name="id"> <text/> </attribute>
|
||||
<attribute name="consumer"> <text/> </attribute>
|
||||
<element name="specs">
|
||||
<zeroOrMore>
|
||||
<element>
|
||||
<anyName/>
|
||||
<text/>
|
||||
</element>
|
||||
</zeroOrMore>
|
||||
</element>
|
||||
<zeroOrMore>
|
||||
<externalRef href="../atom-link.rng"/>
|
||||
</zeroOrMore>
|
||||
</element>
|
@ -1,5 +0,0 @@
|
||||
<element name="qos_specs" xmlns="http://relaxng.org/ns/structure/1.0">
|
||||
<zeroOrMore>
|
||||
<externalRef href="qos_spec.rng"/>
|
||||
</zeroOrMore>
|
||||
</element>
|
@ -258,7 +258,7 @@ class URLMap(paste.urlmap.URLMap):
|
||||
|
||||
# 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)
|
||||
# 2) Accept header (eg application/json;q=0.8)
|
||||
|
||||
# The API version is determined in one of three ways:
|
||||
# 1) URL path prefix (eg /v1.1/tenant/servers/detail)
|
||||
@ -269,11 +269,6 @@ class URLMap(paste.urlmap.URLMap):
|
||||
|
||||
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 cinder.api.openstack import wsgi
|
||||
from cinder.api.views import limits as limits_views
|
||||
from cinder.api import xmlutil
|
||||
from cinder.i18n import _
|
||||
from cinder import quota
|
||||
from cinder.wsgi import common as base_wsgi
|
||||
@ -47,38 +46,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(wsgi.Controller):
|
||||
"""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['cinder.context']
|
||||
|
@ -16,7 +16,6 @@
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -39,14 +38,11 @@ class Controller(wsgi.Controller):
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return meta
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
def index(self, req, snapshot_id):
|
||||
"""Returns the list of metadata for a given snapshot."""
|
||||
context = req.environ['cinder.context']
|
||||
return {'metadata': self._get_metadata(context, snapshot_id)}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def create(self, req, snapshot_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
@ -63,8 +59,6 @@ class Controller(wsgi.Controller):
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
||||
def update(self, req, snapshot_id, id, body):
|
||||
try:
|
||||
meta_item = body['meta']
|
||||
@ -88,8 +82,6 @@ class Controller(wsgi.Controller):
|
||||
|
||||
return {'meta': meta_item}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def update_all(self, req, snapshot_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
@ -128,7 +120,6 @@ class Controller(wsgi.Controller):
|
||||
except exception.InvalidVolumeMetadataSize as error:
|
||||
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
def show(self, req, snapshot_id, id):
|
||||
"""Return a single metadata item."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -22,7 +22,6 @@ from webob import exc
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder import utils
|
||||
@ -61,33 +60,6 @@ def _translate_snapshot_summary_view(snapshot):
|
||||
return d
|
||||
|
||||
|
||||
def make_snapshot(elem):
|
||||
elem.set('id')
|
||||
elem.set('status')
|
||||
elem.set('size')
|
||||
elem.set('created_at')
|
||||
elem.set('display_name')
|
||||
elem.set('display_description')
|
||||
elem.set('volume_id')
|
||||
elem.append(common.MetadataTemplate())
|
||||
|
||||
|
||||
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 SnapshotsController(wsgi.Controller):
|
||||
"""The Snapshots API controller for the OpenStack API."""
|
||||
|
||||
@ -96,7 +68,6 @@ class SnapshotsController(wsgi.Controller):
|
||||
self.ext_mgr = ext_mgr
|
||||
super(SnapshotsController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given snapshot."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -122,12 +93,10 @@ class SnapshotsController(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._items(req, entity_maker=_translate_snapshot_summary_view)
|
||||
|
||||
@wsgi.serializers(xml=SnapshotsTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of snapshots."""
|
||||
return self._items(req, entity_maker=_translate_snapshot_detail_view)
|
||||
@ -153,7 +122,6 @@ class SnapshotsController(wsgi.Controller):
|
||||
res = [entity_maker(snapshot) for snapshot in limited_list]
|
||||
return {'snapshots': res}
|
||||
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def create(self, req, body):
|
||||
"""Creates a new snapshot."""
|
||||
kwargs = {}
|
||||
@ -204,7 +172,6 @@ class SnapshotsController(wsgi.Controller):
|
||||
|
||||
return {'snapshot': retval}
|
||||
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a snapshot."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -19,40 +19,15 @@ from webob import exc
|
||||
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import types as views_types
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.volume import volume_types
|
||||
|
||||
|
||||
def make_voltype(elem):
|
||||
elem.set('id')
|
||||
elem.set('name')
|
||||
extra_specs = xmlutil.make_flat_dict('extra_specs', selector='extra_specs')
|
||||
elem.append(extra_specs)
|
||||
|
||||
|
||||
class VolumeTypeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_type', selector='volume_type')
|
||||
make_voltype(root)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeTypesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_types')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume_type',
|
||||
selector='volume_types')
|
||||
make_voltype(elem)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeTypesController(wsgi.Controller):
|
||||
"""The volume types API controller for the OpenStack API."""
|
||||
|
||||
_view_builder_class = views_types.ViewBuilder
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypesTemplate)
|
||||
def index(self, req):
|
||||
"""Returns the list of volume types."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -61,7 +36,6 @@ class VolumeTypesController(wsgi.Controller):
|
||||
req.cache_resource(vol_types, name='types')
|
||||
return self._view_builder.index(req, vol_types)
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return a single volume type item."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -16,7 +16,6 @@
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -39,14 +38,11 @@ class Controller(wsgi.Controller):
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return meta
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
def index(self, req, volume_id):
|
||||
"""Returns the list of metadata for a given volume."""
|
||||
context = req.environ['cinder.context']
|
||||
return {'metadata': self._get_metadata(context, volume_id)}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def create(self, req, volume_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
@ -63,8 +59,6 @@ class Controller(wsgi.Controller):
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
||||
def update(self, req, volume_id, id, body):
|
||||
try:
|
||||
meta_item = body['meta']
|
||||
@ -88,8 +82,6 @@ class Controller(wsgi.Controller):
|
||||
|
||||
return {'meta': meta_item}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def update_all(self, req, volume_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
@ -128,7 +120,6 @@ class Controller(wsgi.Controller):
|
||||
except exception.InvalidVolumeMetadataSize as error:
|
||||
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
def show(self, req, volume_id, id):
|
||||
"""Return a single metadata item."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -24,7 +24,6 @@ from webob import exc
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder import utils
|
||||
@ -126,97 +125,6 @@ def _translate_volume_summary_view(context, vol, image_id=None):
|
||||
return d
|
||||
|
||||
|
||||
def make_attachment(elem):
|
||||
elem.set('id')
|
||||
elem.set('server_id')
|
||||
elem.set('host_name')
|
||||
elem.set('volume_id')
|
||||
elem.set('device')
|
||||
|
||||
|
||||
def make_volume(elem):
|
||||
elem.set('id')
|
||||
elem.set('status')
|
||||
elem.set('size')
|
||||
elem.set('availability_zone')
|
||||
elem.set('created_at')
|
||||
elem.set('display_name')
|
||||
elem.set('bootable')
|
||||
elem.set('display_description')
|
||||
elem.set('volume_type')
|
||||
elem.set('snapshot_id')
|
||||
elem.set('source_volid')
|
||||
elem.set('multiattach')
|
||||
|
||||
attachments = xmlutil.SubTemplateElement(elem, 'attachments')
|
||||
attachment = xmlutil.SubTemplateElement(attachments, 'attachment',
|
||||
selector='attachments')
|
||||
make_attachment(attachment)
|
||||
|
||||
# Attach metadata node
|
||||
elem.append(common.MetadataTemplate())
|
||||
|
||||
|
||||
volume_nsmap = {None: xmlutil.XMLNS_VOLUME_V1, 'atom': xmlutil.XMLNS_ATOM}
|
||||
|
||||
|
||||
class VolumeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=volume_nsmap)
|
||||
|
||||
|
||||
class VolumesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')
|
||||
make_volume(elem)
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=volume_nsmap)
|
||||
|
||||
|
||||
class CommonDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
"""Common deserializer to handle xml-formatted volume requests.
|
||||
|
||||
Handles standard volume attributes as well as the optional metadata
|
||||
attribute
|
||||
"""
|
||||
|
||||
metadata_deserializer = common.MetadataXMLDeserializer()
|
||||
|
||||
def _extract_volume(self, node):
|
||||
"""Marshal the volume attribute of a parsed request."""
|
||||
volume = {}
|
||||
volume_node = self.find_first_child_named(node, 'volume')
|
||||
|
||||
attributes = ['display_name', 'display_description', 'size',
|
||||
'volume_type', 'availability_zone', 'imageRef',
|
||||
'snapshot_id', 'source_volid']
|
||||
for attr in attributes:
|
||||
if volume_node.getAttribute(attr):
|
||||
volume[attr] = volume_node.getAttribute(attr)
|
||||
|
||||
metadata_node = self.find_first_child_named(volume_node, 'metadata')
|
||||
if metadata_node is not None:
|
||||
volume['metadata'] = self.extract_metadata(metadata_node)
|
||||
|
||||
return volume
|
||||
|
||||
|
||||
class CreateDeserializer(CommonDeserializer):
|
||||
"""Deserializer to handle xml-formatted create volume requests.
|
||||
|
||||
Handles standard volume attributes as well as the optional metadata
|
||||
attribute
|
||||
"""
|
||||
|
||||
def default(self, string):
|
||||
"""Deserialize an xml-formatted volume create request."""
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
volume = self._extract_volume(dom)
|
||||
return {'body': {'volume': volume}}
|
||||
|
||||
|
||||
class VolumeController(wsgi.Controller):
|
||||
"""The Volumes API controller for the OpenStack API."""
|
||||
|
||||
@ -225,7 +133,6 @@ class VolumeController(wsgi.Controller):
|
||||
self.ext_mgr = ext_mgr
|
||||
super(VolumeController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=VolumeTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given volume."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -253,12 +160,10 @@ class VolumeController(wsgi.Controller):
|
||||
raise exc.HTTPNotFound()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.serializers(xml=VolumesTemplate)
|
||||
def index(self, req):
|
||||
"""Returns a summary list of volumes."""
|
||||
return self._items(req, entity_maker=_translate_volume_summary_view)
|
||||
|
||||
@wsgi.serializers(xml=VolumesTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of volumes."""
|
||||
return self._items(req, entity_maker=_translate_volume_detail_view)
|
||||
@ -312,8 +217,6 @@ class VolumeController(wsgi.Controller):
|
||||
|
||||
return image_uuid
|
||||
|
||||
@wsgi.serializers(xml=VolumeTemplate)
|
||||
@wsgi.deserializers(xml=CreateDeserializer)
|
||||
def create(self, req, body):
|
||||
"""Creates a new volume."""
|
||||
if not self.is_valid_body(body, 'volume'):
|
||||
@ -400,7 +303,6 @@ class VolumeController(wsgi.Controller):
|
||||
"""Return volume search options allowed by non-admin."""
|
||||
return ('display_name', 'status', 'metadata')
|
||||
|
||||
@wsgi.serializers(xml=VolumeTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a volume."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -31,7 +31,6 @@ import webob.exc
|
||||
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import limits as limits_views
|
||||
from cinder.api import xmlutil
|
||||
from cinder.i18n import _
|
||||
from cinder import quota
|
||||
from cinder.wsgi import common as base_wsgi
|
||||
@ -47,38 +46,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(wsgi.Controller):
|
||||
"""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['cinder.context']
|
||||
|
@ -16,7 +16,6 @@
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
@ -39,14 +38,11 @@ class Controller(wsgi.Controller):
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return meta
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
def index(self, req, snapshot_id):
|
||||
"""Returns the list of metadata for a given snapshot."""
|
||||
context = req.environ['cinder.context']
|
||||
return {'metadata': self._get_metadata(context, snapshot_id)}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def create(self, req, snapshot_id, body):
|
||||
self.assert_valid_body(body, 'metadata')
|
||||
context = req.environ['cinder.context']
|
||||
@ -59,8 +55,6 @@ class Controller(wsgi.Controller):
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
||||
def update(self, req, snapshot_id, id, body):
|
||||
self.assert_valid_body(body, 'meta')
|
||||
meta_item = body['meta']
|
||||
@ -81,8 +75,6 @@ class Controller(wsgi.Controller):
|
||||
|
||||
return {'meta': meta_item}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def update_all(self, req, snapshot_id, body):
|
||||
self.assert_valid_body(body, 'metadata')
|
||||
context = req.environ['cinder.context']
|
||||
@ -118,7 +110,6 @@ class Controller(wsgi.Controller):
|
||||
except exception.InvalidVolumeMetadataSize as error:
|
||||
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
def show(self, req, snapshot_id, id):
|
||||
"""Return a single metadata item."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -24,7 +24,6 @@ from webob import exc
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import snapshots as snapshot_views
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder import utils
|
||||
@ -35,33 +34,6 @@ from cinder.volume import utils as volume_utils
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_snapshot(elem):
|
||||
elem.set('id')
|
||||
elem.set('status')
|
||||
elem.set('size')
|
||||
elem.set('created_at')
|
||||
elem.set('name')
|
||||
elem.set('description')
|
||||
elem.set('volume_id')
|
||||
elem.append(common.MetadataTemplate())
|
||||
|
||||
|
||||
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 SnapshotsController(wsgi.Controller):
|
||||
"""The Snapshots API controller for the OpenStack API."""
|
||||
|
||||
@ -72,7 +44,6 @@ class SnapshotsController(wsgi.Controller):
|
||||
self.ext_mgr = ext_mgr
|
||||
super(SnapshotsController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given snapshot."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -99,12 +70,10 @@ class SnapshotsController(wsgi.Controller):
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.serializers(xml=SnapshotsTemplate)
|
||||
def index(self, req):
|
||||
"""Returns a summary list of snapshots."""
|
||||
return self._items(req, is_detail=False)
|
||||
|
||||
@wsgi.serializers(xml=SnapshotsTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of snapshots."""
|
||||
return self._items(req, is_detail=True)
|
||||
@ -145,7 +114,6 @@ class SnapshotsController(wsgi.Controller):
|
||||
return snapshots
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def create(self, req, body):
|
||||
"""Creates a new snapshot."""
|
||||
kwargs = {}
|
||||
@ -200,7 +168,6 @@ class SnapshotsController(wsgi.Controller):
|
||||
|
||||
return self._view_builder.detail(req, new_snapshot)
|
||||
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a snapshot."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -21,51 +21,23 @@ from webob import exc
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.v2.views import types as views_types
|
||||
from cinder.api import xmlutil
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import utils
|
||||
from cinder.volume import volume_types
|
||||
|
||||
|
||||
def make_voltype(elem):
|
||||
elem.set('id')
|
||||
elem.set('name')
|
||||
elem.set('description')
|
||||
elem.set('qos_specs_id')
|
||||
extra_specs = xmlutil.make_flat_dict('extra_specs', selector='extra_specs')
|
||||
elem.append(extra_specs)
|
||||
|
||||
|
||||
class VolumeTypeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_type', selector='volume_type')
|
||||
make_voltype(root)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeTypesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_types')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume_type',
|
||||
selector='volume_types')
|
||||
make_voltype(elem)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeTypesController(wsgi.Controller):
|
||||
"""The volume types API controller for the OpenStack API."""
|
||||
|
||||
_view_builder_class = views_types.ViewBuilder
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypesTemplate)
|
||||
def index(self, req):
|
||||
"""Returns the list of volume types."""
|
||||
limited_types = self._get_volume_types(req)
|
||||
req.cache_resource(limited_types, name='types')
|
||||
return self._view_builder.index(req, limited_types)
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return a single volume type item."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -42,14 +42,11 @@ class Controller(wsgi.Controller):
|
||||
raise webob.exc.HTTPNotFound(explanation=error.msg)
|
||||
return (volume, meta)
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
def index(self, req, volume_id):
|
||||
"""Returns the list of metadata for a given volume."""
|
||||
context = req.environ['cinder.context']
|
||||
return {'metadata': self._get_metadata(context, volume_id)}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def create(self, req, volume_id, body):
|
||||
self.assert_valid_body(body, 'metadata')
|
||||
context = req.environ['cinder.context']
|
||||
@ -62,8 +59,6 @@ class Controller(wsgi.Controller):
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
||||
def update(self, req, volume_id, id, body):
|
||||
self.assert_valid_body(body, 'meta')
|
||||
meta_item = body['meta']
|
||||
@ -84,8 +79,6 @@ class Controller(wsgi.Controller):
|
||||
|
||||
return {'meta': meta_item}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def update_all(self, req, volume_id, body):
|
||||
self.assert_valid_body(body, 'metadata')
|
||||
metadata = body['metadata']
|
||||
@ -122,7 +115,6 @@ class Controller(wsgi.Controller):
|
||||
except exception.InvalidVolumeMetadataSize as error:
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
def show(self, req, volume_id, id):
|
||||
"""Return a single metadata item."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -25,7 +25,6 @@ from webob import exc
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.v2.views import volumes as volume_views
|
||||
from cinder.api import xmlutil
|
||||
from cinder import consistencygroup as consistencygroupAPI
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
@ -38,122 +37,6 @@ from cinder.volume import volume_types
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
SCHEDULER_HINTS_NAMESPACE =\
|
||||
"http://docs.openstack.org/block-service/ext/scheduler-hints/api/v2"
|
||||
|
||||
|
||||
def make_attachment(elem):
|
||||
elem.set('id')
|
||||
elem.set('attachment_id')
|
||||
elem.set('server_id')
|
||||
elem.set('host_name')
|
||||
elem.set('volume_id')
|
||||
elem.set('device')
|
||||
|
||||
|
||||
def make_volume(elem):
|
||||
elem.set('id')
|
||||
elem.set('status')
|
||||
elem.set('size')
|
||||
elem.set('availability_zone')
|
||||
elem.set('created_at')
|
||||
elem.set('name')
|
||||
elem.set('bootable')
|
||||
elem.set('description')
|
||||
elem.set('volume_type')
|
||||
elem.set('snapshot_id')
|
||||
elem.set('source_volid')
|
||||
elem.set('consistencygroup_id')
|
||||
elem.set('multiattach')
|
||||
|
||||
attachments = xmlutil.SubTemplateElement(elem, 'attachments')
|
||||
attachment = xmlutil.SubTemplateElement(attachments, 'attachment',
|
||||
selector='attachments')
|
||||
make_attachment(attachment)
|
||||
|
||||
# Attach metadata node
|
||||
elem.append(common.MetadataTemplate())
|
||||
|
||||
|
||||
volume_nsmap = {None: xmlutil.XMLNS_VOLUME_V2, 'atom': xmlutil.XMLNS_ATOM}
|
||||
|
||||
|
||||
class VolumeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=volume_nsmap)
|
||||
|
||||
|
||||
class VolumesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')
|
||||
make_volume(elem)
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=volume_nsmap)
|
||||
|
||||
|
||||
class CommonDeserializer(wsgi.MetadataXMLDeserializer):
|
||||
"""Common deserializer to handle xml-formatted volume requests.
|
||||
|
||||
Handles standard volume attributes as well as the optional metadata
|
||||
attribute
|
||||
"""
|
||||
|
||||
metadata_deserializer = common.MetadataXMLDeserializer()
|
||||
|
||||
def _extract_scheduler_hints(self, volume_node):
|
||||
"""Marshal the scheduler hints attribute of a parsed request."""
|
||||
node =\
|
||||
self.find_first_child_named_in_namespace(volume_node,
|
||||
SCHEDULER_HINTS_NAMESPACE,
|
||||
"scheduler_hints")
|
||||
if node:
|
||||
scheduler_hints = {}
|
||||
for child in self.extract_elements(node):
|
||||
scheduler_hints.setdefault(child.nodeName, [])
|
||||
value = self.extract_text(child).strip()
|
||||
scheduler_hints[child.nodeName].append(value)
|
||||
return scheduler_hints
|
||||
else:
|
||||
return None
|
||||
|
||||
def _extract_volume(self, node):
|
||||
"""Marshal the volume attribute of a parsed request."""
|
||||
volume = {}
|
||||
volume_node = self.find_first_child_named(node, 'volume')
|
||||
|
||||
attributes = ['name', 'description', 'size',
|
||||
'volume_type', 'availability_zone', 'imageRef',
|
||||
'image_id', 'snapshot_id', 'source_volid',
|
||||
'consistencygroup_id']
|
||||
for attr in attributes:
|
||||
if volume_node.getAttribute(attr):
|
||||
volume[attr] = volume_node.getAttribute(attr)
|
||||
|
||||
metadata_node = self.find_first_child_named(volume_node, 'metadata')
|
||||
if metadata_node is not None:
|
||||
volume['metadata'] = self.extract_metadata(metadata_node)
|
||||
|
||||
scheduler_hints = self._extract_scheduler_hints(volume_node)
|
||||
if scheduler_hints:
|
||||
volume['scheduler_hints'] = scheduler_hints
|
||||
|
||||
return volume
|
||||
|
||||
|
||||
class CreateDeserializer(CommonDeserializer):
|
||||
"""Deserializer to handle xml-formatted create volume requests.
|
||||
|
||||
Handles standard volume attributes as well as the optional metadata
|
||||
attribute
|
||||
"""
|
||||
|
||||
def default(self, string):
|
||||
"""Deserialize an xml-formatted volume create request."""
|
||||
dom = utils.safe_minidom_parse_string(string)
|
||||
volume = self._extract_volume(dom)
|
||||
return {'body': {'volume': volume}}
|
||||
|
||||
|
||||
class VolumeController(wsgi.Controller):
|
||||
@ -167,7 +50,6 @@ class VolumeController(wsgi.Controller):
|
||||
self.ext_mgr = ext_mgr
|
||||
super(VolumeController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=VolumeTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given volume."""
|
||||
context = req.environ['cinder.context']
|
||||
@ -197,12 +79,10 @@ class VolumeController(wsgi.Controller):
|
||||
raise exc.HTTPNotFound(explanation=error.msg)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.serializers(xml=VolumesTemplate)
|
||||
def index(self, req):
|
||||
"""Returns a summary list of volumes."""
|
||||
return self._get_volumes(req, is_detail=False)
|
||||
|
||||
@wsgi.serializers(xml=VolumesTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of volumes."""
|
||||
return self._get_volumes(req, is_detail=True)
|
||||
@ -292,8 +172,6 @@ class VolumeController(wsgi.Controller):
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.serializers(xml=VolumeTemplate)
|
||||
@wsgi.deserializers(xml=CreateDeserializer)
|
||||
def create(self, req, body):
|
||||
"""Creates a new volume."""
|
||||
self.assert_valid_body(body, 'volume')
|
||||
@ -415,7 +293,6 @@ class VolumeController(wsgi.Controller):
|
||||
"""Return volume search options allowed by non-admin."""
|
||||
return CONF.query_volume_filters
|
||||
|
||||
@wsgi.serializers(xml=VolumeTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a volume."""
|
||||
context = req.environ['cinder.context']
|
||||
|
@ -16,9 +16,7 @@
|
||||
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
from lxml import etree
|
||||
from oslo_config import cfg
|
||||
|
||||
from cinder.api import extensions
|
||||
@ -26,7 +24,6 @@ from cinder.api import openstack
|
||||
from cinder.api.openstack import api_version_request
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import versions as views_versions
|
||||
from cinder.api import xmlutil
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -43,11 +40,6 @@ _MEDIA_TYPES = [{
|
||||
"type":
|
||||
"application/vnd.openstack.volume+json;version=1",
|
||||
},
|
||||
{"base":
|
||||
"application/xml",
|
||||
"type":
|
||||
"application/vnd.openstack.volume+xml;version=1",
|
||||
},
|
||||
]
|
||||
|
||||
_KNOWN_VERSIONS = {
|
||||
@ -138,149 +130,5 @@ class VersionsController(wsgi.Controller):
|
||||
return builder.build_versions(known_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.XML_NS_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)
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(VersionsController())
|
||||
|
@ -1,968 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# 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
|
||||
import re
|
||||
|
||||
from lxml import etree
|
||||
import six
|
||||
|
||||
from cinder.i18n import _
|
||||
from cinder 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/api/openstack-block-storage/1.0/'
|
||||
'content')
|
||||
XMLNS_VOLUME_V2 = ('http://docs.openstack.org/api/openstack-block-storage/2.0/'
|
||||
'content')
|
||||
|
||||
_split_pattern = re.compile(r'([^:{]*{[^}]*}[^:]*|[^:]+)')
|
||||
|
||||
|
||||
def validate_schema(xml, schema_name):
|
||||
if isinstance(xml, str):
|
||||
xml = etree.fromstring(xml)
|
||||
base_path = 'cinder/api/schemas/v1.1/'
|
||||
if schema_name in ('atom', 'atom-link'):
|
||||
base_path = 'cinder/api/schemas/'
|
||||
schema_path = os.path.join(utils.cinderdir(),
|
||||
'%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 getAttrib(self, obj):
|
||||
"""Get attribute."""
|
||||
tmpattrib = {}
|
||||
# Now set up all the attributes...
|
||||
for key, value in self.attrib.items():
|
||||
try:
|
||||
tmpattrib[key] = value(obj)
|
||||
except KeyError:
|
||||
# Attribute has no value, so don't include it
|
||||
pass
|
||||
return tmpattrib
|
||||
|
||||
@staticmethod
|
||||
def _splitTagName(name):
|
||||
return _split_pattern.findall(name)
|
||||
|
||||
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
|
||||
|
||||
# If the datum is None
|
||||
if datum is not None:
|
||||
tmpattrib = self.getAttrib(datum)
|
||||
else:
|
||||
tmpattrib = {}
|
||||
|
||||
tagnameList = self._splitTagName(tagname)
|
||||
insertIndex = 0
|
||||
|
||||
# If parent is not none and has same tagname
|
||||
if parent is not None:
|
||||
for i in range(0, len(tagnameList)):
|
||||
tmpInsertPos = parent.find(tagnameList[i])
|
||||
if tmpInsertPos is None:
|
||||
break
|
||||
elif parent.attrib != tmpattrib:
|
||||
break
|
||||
parent = tmpInsertPos
|
||||
insertIndex = i + 1
|
||||
|
||||
if insertIndex >= len(tagnameList):
|
||||
insertIndex = insertIndex - 1
|
||||
|
||||
# Create root elem
|
||||
elem = etree.Element(tagnameList[insertIndex], nsmap=nsmap)
|
||||
rootelem = elem
|
||||
subelem = elem
|
||||
|
||||
# Create subelem
|
||||
for i in range((insertIndex + 1), len(tagnameList)):
|
||||
subelem = etree.SubElement(elem, tagnameList[i])
|
||||
elem = subelem
|
||||
|
||||
# If we have a parent, append the node to the parent
|
||||
if parent is not None:
|
||||
# If we can merge this element, then insert
|
||||
if insertIndex > 0:
|
||||
parent.insert(len(list(parent)), rootelem)
|
||||
else:
|
||||
parent.append(rootelem)
|
||||
|
||||
# If the datum is None, do nothing else
|
||||
if datum is None:
|
||||
return rootelem
|
||||
|
||||
# Apply this template element to the element
|
||||
self.apply(subelem, datum)
|
||||
|
||||
# Additionally, apply the patches
|
||||
for patch in patches:
|
||||
patch.apply(subelem, datum)
|
||||
|
||||
# We have fully rendered the element; return it
|
||||
return rootelem
|
||||
|
||||
def render(self, parent, obj, patches=None, 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.
|
||||
"""
|
||||
|
||||
patches = patches or []
|
||||
# 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, traverse 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 call this function for all data elements recursively
|
||||
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:
|
||||
msg = (_("Template tree mismatch; adding slave %(slavetag)s "
|
||||
"to master %(mastertag)s") %
|
||||
{'slavetag': slave.root.tag,
|
||||
'mastertag': self.root.tag})
|
||||
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.
|
||||
|
||||
Simple templates are 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
|
@ -46,7 +46,6 @@ class FoxInSocksFlavorGooseControllerExtension(wsgi.Controller):
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
# NOTE: This only handles JSON responses.
|
||||
# You can use content type header to test for XML.
|
||||
resp_obj.obj['flavor']['googoose'] = req.GET.get('chewing')
|
||||
|
||||
|
||||
@ -54,7 +53,6 @@ class FoxInSocksFlavorBandsControllerExtension(wsgi.Controller):
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
# NOTE: This only handles JSON responses.
|
||||
# You can use content type header to test for XML.
|
||||
resp_obj.obj['big_bands'] = 'Pig Bands!'
|
||||
|
||||
|
||||
|
@ -15,14 +15,12 @@
|
||||
|
||||
|
||||
import iso8601
|
||||
from lxml import etree
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils
|
||||
import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.v1 import router
|
||||
from cinder.api import xmlutil
|
||||
from cinder.tests.functional import functional_helpers
|
||||
|
||||
|
||||
@ -75,8 +73,7 @@ class ExtensionControllerTest(ExtensionTestCase):
|
||||
(fox_ext, ) = [
|
||||
x for x in data['extensions'] if x['alias'] == 'FOXNSOX']
|
||||
self.assertEqual(
|
||||
{'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',
|
||||
@ -98,8 +95,7 @@ class ExtensionControllerTest(ExtensionTestCase):
|
||||
|
||||
data = jsonutils.loads(response.body)
|
||||
self.assertEqual(
|
||||
{"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",
|
||||
@ -111,55 +107,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(NS, root.tag.split('extensions')[0])
|
||||
|
||||
# Make sure we have all the extensions, extras extensions being OK.
|
||||
exts = root.findall('{0}extension'.format(NS))
|
||||
self.assertGreaterEqual(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 In Socks', fox_ext.get('name'))
|
||||
self.assertEqual(
|
||||
'http://www.fox.in.socks/api/ext/pie/v1.0',
|
||||
fox_ext.get('namespace'))
|
||||
self.assertEqual('2011-01-22T13:25:27-06:00', fox_ext.get('updated'))
|
||||
self.assertEqual(
|
||||
'The Fox In Socks Extension.',
|
||||
fox_ext.findtext('{0}description'.format(NS)))
|
||||
|
||||
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(NS, root.tag.split('extension')[0])
|
||||
self.assertEqual('FOXNSOX', root.get('alias'))
|
||||
self.assertEqual('Fox In Socks', root.get('name'))
|
||||
self.assertEqual(
|
||||
'http://www.fox.in.socks/api/ext/pie/v1.0',
|
||||
root.get('namespace'))
|
||||
self.assertEqual('2011-01-22T13:25:27-06:00', root.get('updated'))
|
||||
self.assertEqual(
|
||||
'The Fox In Socks Extension.',
|
||||
root.findtext('{0}description'.format(NS)))
|
||||
|
||||
xmlutil.validate_schema(root, 'extension')
|
||||
|
||||
|
||||
class StubExtensionManager(object):
|
||||
"""Provides access to Tweedle Beetles."""
|
||||
@ -203,10 +150,6 @@ class ExtensionControllerIdFormatTest(ExtensionTestCase):
|
||||
response = request.get_response(app)
|
||||
return response.body
|
||||
|
||||
def test_id_with_xml_format(self):
|
||||
result = self._bounce_id('foo.xml')
|
||||
self.assertEqual(b'foo', result)
|
||||
|
||||
def test_id_with_json_format(self):
|
||||
result = self._bounce_id('foo.json')
|
||||
self.assertEqual(b'foo', result)
|
||||
|
@ -1,44 +0,0 @@
|
||||
# Copyright 2011 Justin Santa Barbara
|
||||
# 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 lxml import etree
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.tests.functional import functional_helpers
|
||||
|
||||
|
||||
class XmlTests(functional_helpers._FunctionalTestBase):
|
||||
"""Some basic XML sanity checks."""
|
||||
|
||||
# FIXME(ja): does cinder need limits?
|
||||
# def test_namespace_limits(self):
|
||||
# headers = {}
|
||||
# headers['Accept'] = 'application/xml'
|
||||
|
||||
# response = self.api.api_request('/limits', headers=headers)
|
||||
# data = response.read()
|
||||
# LOG.debug("data: %s" % data)
|
||||
# root = etree.XML(data)
|
||||
# self.assertEqual(root.nsmap.get(None), xmlutil.XMLNS_COMMON_V10)
|
||||
|
||||
def test_namespace_volumes(self):
|
||||
headers = {}
|
||||
headers['Accept'] = 'application/xml'
|
||||
|
||||
response = self.api.api_request('/volumes', headers=headers,
|
||||
stream=True)
|
||||
data = response.raw
|
||||
root = etree.parse(data).getroot()
|
||||
self.assertEqual(common.XML_NS_V2, root.nsmap.get(None))
|
@ -1,36 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
|
||||
def compare_links(actual, expected):
|
||||
"""Compare xml atom links."""
|
||||
|
||||
return compare_tree_to_dict(actual, expected, ('rel', 'href', 'type'))
|
||||
|
||||
|
||||
def compare_media_types(actual, expected):
|
||||
"""Compare xml media types."""
|
||||
|
||||
return compare_tree_to_dict(actual, expected, ('base', 'type'))
|
||||
|
||||
|
||||
def compare_tree_to_dict(actual, expected, keys):
|
||||
"""Compare parts of lxml.etree objects to dicts."""
|
||||
|
||||
for elem, data in zip(actual, expected):
|
||||
for key in keys:
|
||||
if elem.get(key) != data.get(key):
|
||||
return False
|
||||
return True
|
@ -15,7 +15,6 @@
|
||||
|
||||
import datetime
|
||||
|
||||
from lxml import etree
|
||||
from oslo_utils import timeutils
|
||||
|
||||
import cinder.api.contrib.availability_zones
|
||||
@ -60,31 +59,3 @@ class ControllerTestCase(cinder.test.TestCase):
|
||||
],
|
||||
}
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
class XMLSerializerTest(cinder.test.TestCase):
|
||||
|
||||
def test_index_xml(self):
|
||||
fixture = {
|
||||
'availabilityZoneInfo': [
|
||||
{'zoneName': 'ping', 'zoneState': {'available': True}},
|
||||
{'zoneName': 'pong', 'zoneState': {'available': False}},
|
||||
],
|
||||
}
|
||||
|
||||
serializer = cinder.api.contrib.availability_zones.ListTemplate()
|
||||
text = serializer.serialize(fixture)
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('availabilityZones', tree.tag)
|
||||
self.assertEqual(2, len(tree))
|
||||
|
||||
self.assertEqual('availabilityZone', tree[0].tag)
|
||||
|
||||
self.assertEqual('ping', tree[0].get('name'))
|
||||
self.assertEqual('zoneState', tree[0][0].tag)
|
||||
self.assertEqual('True', tree[0][0].get('available'))
|
||||
|
||||
self.assertEqual('pong', tree[1].get('name'))
|
||||
self.assertEqual('zoneState', tree[1][0].tag)
|
||||
self.assertEqual('False', tree[1][0].get('available'))
|
||||
|
@ -17,13 +17,10 @@
|
||||
Tests for Backup code.
|
||||
"""
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
import webob
|
||||
|
||||
# needed for stubs to work
|
||||
@ -141,25 +138,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
db.backup_destroy(context.get_admin_context(), backup_id)
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
|
||||
def test_show_backup_xml_content_type(self):
|
||||
volume_id = utils.create_volume(self.context, size=5,
|
||||
status='creating')['id']
|
||||
backup_id = self._create_backup(volume_id)
|
||||
req = webob.Request.blank('/v2/fake/backups/%s' % backup_id)
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
backup = dom.getElementsByTagName('backup')
|
||||
name = backup.item(0).getAttribute('name')
|
||||
container_name = backup.item(0).getAttribute('container')
|
||||
self.assertEqual('volumebackups', container_name.strip())
|
||||
self.assertEqual('test_backup', name.strip())
|
||||
db.backup_destroy(context.get_admin_context(), backup_id)
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
|
||||
def test_show_backup_with_backup_NotFound(self):
|
||||
req = webob.Request.blank('/v2/fake/backups/9999')
|
||||
req.method = 'GET'
|
||||
@ -198,35 +176,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
db.backup_destroy(context.get_admin_context(), backup_id2)
|
||||
db.backup_destroy(context.get_admin_context(), backup_id1)
|
||||
|
||||
def test_list_backups_xml(self):
|
||||
backup_id1 = self._create_backup()
|
||||
backup_id2 = self._create_backup()
|
||||
backup_id3 = self._create_backup()
|
||||
|
||||
req = webob.Request.blank('/v2/fake/backups')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
backup_list = dom.getElementsByTagName('backup')
|
||||
|
||||
self.assertEqual(2, backup_list.item(0).attributes.length)
|
||||
self.assertEqual(backup_id3,
|
||||
backup_list.item(0).getAttribute('id'))
|
||||
self.assertEqual(2, backup_list.item(1).attributes.length)
|
||||
self.assertEqual(backup_id2,
|
||||
backup_list.item(1).getAttribute('id'))
|
||||
self.assertEqual(2, backup_list.item(2).attributes.length)
|
||||
self.assertEqual(backup_id1,
|
||||
backup_list.item(2).getAttribute('id'))
|
||||
|
||||
db.backup_destroy(context.get_admin_context(), backup_id3)
|
||||
db.backup_destroy(context.get_admin_context(), backup_id2)
|
||||
db.backup_destroy(context.get_admin_context(), backup_id1)
|
||||
|
||||
def test_list_backups_with_limit(self):
|
||||
backup_id1 = self._create_backup()
|
||||
backup_id2 = self._create_backup()
|
||||
@ -411,90 +360,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
db.backup_destroy(context.get_admin_context(), backup_id2)
|
||||
db.backup_destroy(context.get_admin_context(), backup_id1)
|
||||
|
||||
def test_list_backups_detail_xml(self):
|
||||
backup_id1 = self._create_backup()
|
||||
backup_id2 = self._create_backup()
|
||||
backup_id3 = self._create_backup()
|
||||
|
||||
req = webob.Request.blank('/v2/fake/backups/detail')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
backup_detail = dom.getElementsByTagName('backup')
|
||||
|
||||
self.assertEqual(11, backup_detail.item(0).attributes.length)
|
||||
self.assertEqual(
|
||||
'az1', backup_detail.item(0).getAttribute('availability_zone'))
|
||||
self.assertEqual(
|
||||
'volumebackups', backup_detail.item(0).getAttribute('container'))
|
||||
self.assertEqual(
|
||||
'this is a test backup',
|
||||
backup_detail.item(0).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_backup', backup_detail.item(0).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
backup_id3, backup_detail.item(0).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
0, int(backup_detail.item(0).getAttribute('object_count')))
|
||||
self.assertEqual(
|
||||
0, int(backup_detail.item(0).getAttribute('size')))
|
||||
self.assertEqual(
|
||||
fields.BackupStatus.CREATING,
|
||||
backup_detail.item(0).getAttribute('status'))
|
||||
self.assertEqual(
|
||||
1, int(backup_detail.item(0).getAttribute('volume_id')))
|
||||
|
||||
self.assertEqual(11, backup_detail.item(1).attributes.length)
|
||||
self.assertEqual(
|
||||
'az1', backup_detail.item(1).getAttribute('availability_zone'))
|
||||
self.assertEqual(
|
||||
'volumebackups', backup_detail.item(1).getAttribute('container'))
|
||||
self.assertEqual(
|
||||
'this is a test backup',
|
||||
backup_detail.item(1).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_backup', backup_detail.item(1).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
backup_id2, backup_detail.item(1).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
0, int(backup_detail.item(1).getAttribute('object_count')))
|
||||
self.assertEqual(
|
||||
0, int(backup_detail.item(1).getAttribute('size')))
|
||||
self.assertEqual(
|
||||
fields.BackupStatus.CREATING,
|
||||
backup_detail.item(1).getAttribute('status'))
|
||||
self.assertEqual(
|
||||
1, int(backup_detail.item(1).getAttribute('volume_id')))
|
||||
|
||||
self.assertEqual(11, backup_detail.item(2).attributes.length)
|
||||
self.assertEqual(
|
||||
'az1', backup_detail.item(2).getAttribute('availability_zone'))
|
||||
self.assertEqual(
|
||||
'volumebackups', backup_detail.item(2).getAttribute('container'))
|
||||
self.assertEqual(
|
||||
'this is a test backup',
|
||||
backup_detail.item(2).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_backup', backup_detail.item(2).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
backup_id1, backup_detail.item(2).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
0, int(backup_detail.item(2).getAttribute('object_count')))
|
||||
self.assertEqual(
|
||||
0, int(backup_detail.item(2).getAttribute('size')))
|
||||
self.assertEqual(fields.BackupStatus.CREATING,
|
||||
backup_detail.item(2).getAttribute('status'))
|
||||
self.assertEqual(
|
||||
1, int(backup_detail.item(2).getAttribute('volume_id')))
|
||||
|
||||
db.backup_destroy(context.get_admin_context(), backup_id3)
|
||||
db.backup_destroy(context.get_admin_context(), backup_id2)
|
||||
db.backup_destroy(context.get_admin_context(), backup_id1)
|
||||
|
||||
def test_list_backups_detail_with_limit_and_sort_args(self):
|
||||
backup_id1 = self._create_backup()
|
||||
backup_id2 = self._create_backup()
|
||||
@ -736,39 +601,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(400, res.status_int)
|
||||
self.assertIsNotNone(res_dict['badRequest']['message'])
|
||||
|
||||
@mock.patch('cinder.db.service_get_all_by_topic')
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_create_backup_xml(self, mock_validate,
|
||||
_mock_service_get_all_by_topic):
|
||||
_mock_service_get_all_by_topic.return_value = [
|
||||
{'availability_zone': 'fake_az', 'host': 'testhost',
|
||||
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
||||
|
||||
volume_id = utils.create_volume(self.context, size=2)['id']
|
||||
|
||||
body = ('<backup display_name="backup-001" '
|
||||
'display_description="Nightly Backup" '
|
||||
'volume_id="%s" container="Container001"/>' % volume_id)
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
|
||||
req = webob.Request.blank('/v2/fake/backups')
|
||||
req.body = body
|
||||
req.method = 'POST'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(202, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
backup = dom.getElementsByTagName('backup')
|
||||
self.assertTrue(backup.item(0).hasAttribute('id'))
|
||||
self.assertTrue(_mock_service_get_all_by_topic.called)
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
|
||||
def test_create_backup_with_invalid_snapshot(self):
|
||||
volume_id = utils.create_volume(self.context, size=5,
|
||||
status='available')['id']
|
||||
@ -1311,37 +1143,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
|
||||
self.assertEqual(volume_name, res_dict['restore']['volume_name'])
|
||||
|
||||
@mock.patch('cinder.backup.api.API._get_available_backup_service_host')
|
||||
def test_restore_backup_volume_id_specified_xml(
|
||||
self, _mock_get_backup_host):
|
||||
_mock_get_backup_host.return_value = 'testhost'
|
||||
volume_name = 'test1'
|
||||
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
||||
volume_id = utils.create_volume(self.context,
|
||||
size=2,
|
||||
display_name=volume_name)['id']
|
||||
|
||||
body = '<restore volume_id="%s"/>' % volume_id
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
|
||||
req = webob.Request.blank('/v2/fake/backups/%s/restore' % backup_id)
|
||||
req.body = body
|
||||
req.method = 'POST'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(202, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
restore = dom.getElementsByTagName('restore')
|
||||
self.assertEqual(backup_id,
|
||||
restore.item(0).getAttribute('backup_id'))
|
||||
self.assertEqual(volume_id, restore.item(0).getAttribute('volume_id'))
|
||||
|
||||
db.backup_destroy(context.get_admin_context(), backup_id)
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
|
||||
def test_restore_backup_with_no_body(self):
|
||||
# omit body from the request
|
||||
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
||||
@ -1774,36 +1575,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
res_dict['backup-record']['backup_url'])
|
||||
db.backup_destroy(context.get_admin_context(), backup_id)
|
||||
|
||||
@mock.patch('cinder.backup.api.API._get_available_backup_service_host')
|
||||
@mock.patch('cinder.backup.rpcapi.BackupAPI.export_record')
|
||||
def test_export_record_backup_id_specified_xml(self,
|
||||
_mock_export_record_rpc,
|
||||
_mock_get_backup_host):
|
||||
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
|
||||
size=10)
|
||||
ctx = context.RequestContext('admin', 'fake', is_admin=True)
|
||||
backup_service = 'fake'
|
||||
backup_url = 'fake'
|
||||
_mock_export_record_rpc.return_value = \
|
||||
{'backup_service': backup_service,
|
||||
'backup_url': backup_url}
|
||||
_mock_get_backup_host.return_value = 'testhost'
|
||||
req = webob.Request.blank('/v2/fake/backups/%s/export_record' %
|
||||
backup_id)
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
export = dom.getElementsByTagName('backup-record')
|
||||
self.assertEqual(backup_service,
|
||||
export.item(0).getAttribute('backup_service'))
|
||||
self.assertEqual(backup_url,
|
||||
export.item(0).getAttribute('backup_url'))
|
||||
|
||||
# db.backup_destroy(context.get_admin_context(), backup_id)
|
||||
|
||||
def test_export_record_with_bad_backup_id(self):
|
||||
|
||||
ctx = context.RequestContext('admin', 'fake', is_admin=True)
|
||||
@ -1960,53 +1731,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
db.backup_destroy(context.get_admin_context(), backup_id)
|
||||
|
||||
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
||||
@mock.patch('cinder.backup.rpcapi.BackupAPI.import_record')
|
||||
def test_import_record_volume_id_specified_xml(self,
|
||||
_mock_import_record_rpc,
|
||||
_mock_list_services):
|
||||
utils.replace_obj_loader(self, objects.Backup)
|
||||
project_id = 'fake'
|
||||
backup_service = 'fake'
|
||||
ctx = context.RequestContext('admin', project_id, is_admin=True)
|
||||
backup = objects.Backup(ctx, id='id', user_id='user_id',
|
||||
project_id=project_id,
|
||||
status=fields.BackupStatus.AVAILABLE)
|
||||
backup_url = backup.encode_record()
|
||||
_mock_import_record_rpc.return_value = None
|
||||
_mock_list_services.return_value = [backup_service]
|
||||
|
||||
if six.PY2:
|
||||
backup_url = backup_url.encode('utf-8')
|
||||
body = ('<backup-record backup_service="%(backup_service)s" '
|
||||
'backup_url="%(backup_url)s"/>'
|
||||
% {'backup_url': backup_url,
|
||||
'backup_service': backup_service})
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
|
||||
req = webob.Request.blank('/v2/fake/backups/import_record')
|
||||
req.body = body
|
||||
req.method = 'POST'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
||||
|
||||
# verify that request is successful
|
||||
self.assertEqual(201, res.status_int)
|
||||
|
||||
# Verify that entry in DB is as expected
|
||||
db_backup = objects.Backup.get_by_id(ctx, 'id')
|
||||
self.assertEqual(ctx.project_id, db_backup.project_id)
|
||||
self.assertEqual(ctx.user_id, db_backup.user_id)
|
||||
self.assertEqual(backup_api.IMPORT_VOLUME_ID, db_backup.volume_id)
|
||||
self.assertEqual(fields.BackupStatus.CREATING, db_backup.status)
|
||||
|
||||
# Verify the response
|
||||
dom = minidom.parseString(res.body)
|
||||
back = dom.getElementsByTagName('backup')
|
||||
self.assertEqual(backup.id, back.item(0).attributes['id'].value)
|
||||
|
||||
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
||||
def test_import_record_with_no_backup_services(self,
|
||||
_mock_list_services):
|
||||
|
@ -17,8 +17,6 @@
|
||||
Tests for cgsnapshot code.
|
||||
"""
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
import mock
|
||||
from oslo_serialization import jsonutils
|
||||
import webob
|
||||
@ -71,30 +69,6 @@ class CgsnapshotsAPITestCase(test.TestCase):
|
||||
volume_id)
|
||||
consistencygroup.destroy()
|
||||
|
||||
def test_show_cgsnapshot_xml_content_type(self):
|
||||
consistencygroup = utils.create_consistencygroup(self.context)
|
||||
volume_id = utils.create_volume(self.context,
|
||||
consistencygroup_id=
|
||||
consistencygroup.id)['id']
|
||||
cgsnapshot = utils.create_cgsnapshot(
|
||||
self.context, consistencygroup_id=consistencygroup.id)
|
||||
req = webob.Request.blank('/v2/fake/cgsnapshots/%s' %
|
||||
cgsnapshot.id)
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
|
||||
cgsnapshots = dom.getElementsByTagName('cgsnapshot')
|
||||
name = cgsnapshots.item(0).getAttribute('name')
|
||||
self.assertEqual("test_cgsnapshot", name.strip())
|
||||
cgsnapshot.destroy()
|
||||
db.volume_destroy(context.get_admin_context(),
|
||||
volume_id)
|
||||
consistencygroup.destroy()
|
||||
|
||||
def test_show_cgsnapshot_with_cgsnapshot_NotFound(self):
|
||||
req = webob.Request.blank('/v2/fake/cgsnapshots/9999')
|
||||
req.method = 'GET'
|
||||
@ -146,42 +120,6 @@ class CgsnapshotsAPITestCase(test.TestCase):
|
||||
volume_id)
|
||||
consistencygroup.destroy()
|
||||
|
||||
def test_list_cgsnapshots_xml(self):
|
||||
consistencygroup = utils.create_consistencygroup(self.context)
|
||||
volume_id = utils.create_volume(self.context,
|
||||
consistencygroup_id=
|
||||
consistencygroup.id)['id']
|
||||
cgsnapshot1 = utils.create_cgsnapshot(
|
||||
self.context, consistencygroup_id=consistencygroup.id)
|
||||
cgsnapshot2 = utils.create_cgsnapshot(
|
||||
self.context, consistencygroup_id=consistencygroup.id)
|
||||
cgsnapshot3 = utils.create_cgsnapshot(
|
||||
self.context, consistencygroup_id=consistencygroup.id)
|
||||
|
||||
req = webob.Request.blank('/v2/fake/cgsnapshots')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
cgsnapshot_list = dom.getElementsByTagName('cgsnapshot')
|
||||
|
||||
self.assertEqual(cgsnapshot1.id,
|
||||
cgsnapshot_list.item(0).getAttribute('id'))
|
||||
self.assertEqual(cgsnapshot2.id,
|
||||
cgsnapshot_list.item(1).getAttribute('id'))
|
||||
self.assertEqual(cgsnapshot3.id,
|
||||
cgsnapshot_list.item(2).getAttribute('id'))
|
||||
|
||||
cgsnapshot3.destroy()
|
||||
cgsnapshot2.destroy()
|
||||
cgsnapshot1.destroy()
|
||||
db.volume_destroy(context.get_admin_context(),
|
||||
volume_id)
|
||||
consistencygroup.destroy()
|
||||
|
||||
def test_list_cgsnapshots_detail_json(self):
|
||||
consistencygroup = utils.create_consistencygroup(self.context)
|
||||
volume_id = utils.create_volume(self.context,
|
||||
@ -236,71 +174,6 @@ class CgsnapshotsAPITestCase(test.TestCase):
|
||||
volume_id)
|
||||
consistencygroup.destroy()
|
||||
|
||||
def test_list_cgsnapshots_detail_xml(self):
|
||||
consistencygroup = utils.create_consistencygroup(self.context)
|
||||
volume_id = utils.create_volume(self.context,
|
||||
consistencygroup_id=
|
||||
consistencygroup.id)['id']
|
||||
cgsnapshot1 = utils.create_cgsnapshot(
|
||||
self.context, consistencygroup_id=consistencygroup.id)
|
||||
cgsnapshot2 = utils.create_cgsnapshot(
|
||||
self.context, consistencygroup_id=consistencygroup.id)
|
||||
cgsnapshot3 = utils.create_cgsnapshot(
|
||||
self.context, consistencygroup_id=consistencygroup.id)
|
||||
|
||||
req = webob.Request.blank('/v2/fake/cgsnapshots/detail')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
cgsnapshot_detail = dom.getElementsByTagName('cgsnapshot')
|
||||
|
||||
self.assertEqual(
|
||||
'this is a test cgsnapshot',
|
||||
cgsnapshot_detail.item(0).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_cgsnapshot',
|
||||
cgsnapshot_detail.item(0).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
cgsnapshot1.id,
|
||||
cgsnapshot_detail.item(0).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
'creating', cgsnapshot_detail.item(0).getAttribute('status'))
|
||||
|
||||
self.assertEqual(
|
||||
'this is a test cgsnapshot',
|
||||
cgsnapshot_detail.item(1).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_cgsnapshot',
|
||||
cgsnapshot_detail.item(1).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
cgsnapshot2.id,
|
||||
cgsnapshot_detail.item(1).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
'creating', cgsnapshot_detail.item(1).getAttribute('status'))
|
||||
|
||||
self.assertEqual(
|
||||
'this is a test cgsnapshot',
|
||||
cgsnapshot_detail.item(2).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_cgsnapshot',
|
||||
cgsnapshot_detail.item(2).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
cgsnapshot3.id,
|
||||
cgsnapshot_detail.item(2).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
'creating', cgsnapshot_detail.item(2).getAttribute('status'))
|
||||
|
||||
cgsnapshot3.destroy()
|
||||
cgsnapshot2.destroy()
|
||||
cgsnapshot1.destroy()
|
||||
db.volume_destroy(context.get_admin_context(),
|
||||
volume_id)
|
||||
consistencygroup.destroy()
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_create_cgsnapshot_json(self, mock_validate):
|
||||
|
@ -17,8 +17,6 @@
|
||||
Tests for consistency group code.
|
||||
"""
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_serialization import jsonutils
|
||||
@ -94,21 +92,6 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
|
||||
|
||||
consistencygroup.destroy()
|
||||
|
||||
def test_show_consistencygroup_xml_content_type(self):
|
||||
consistencygroup = self._create_consistencygroup()
|
||||
req = webob.Request.blank('/v2/fake/consistencygroups/%s' %
|
||||
consistencygroup.id)
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
consistencygroups = dom.getElementsByTagName('consistencygroup')
|
||||
name = consistencygroups.item(0).getAttribute('name')
|
||||
self.assertEqual("test_consistencygroup", name.strip())
|
||||
consistencygroup.destroy()
|
||||
|
||||
def test_show_consistencygroup_with_consistencygroup_NotFound(self):
|
||||
req = webob.Request.blank('/v2/fake/consistencygroups/9999')
|
||||
req.method = 'GET'
|
||||
@ -172,32 +155,6 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
|
||||
consistencygroup2.destroy()
|
||||
consistencygroup3.destroy()
|
||||
|
||||
def test_list_consistencygroups_xml(self):
|
||||
consistencygroup1 = self._create_consistencygroup()
|
||||
consistencygroup2 = self._create_consistencygroup()
|
||||
consistencygroup3 = self._create_consistencygroup()
|
||||
|
||||
req = webob.Request.blank('/v2/fake/consistencygroups')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
consistencygroup_list = dom.getElementsByTagName('consistencygroup')
|
||||
|
||||
self.assertEqual(consistencygroup3.id,
|
||||
consistencygroup_list.item(0).getAttribute('id'))
|
||||
self.assertEqual(consistencygroup2.id,
|
||||
consistencygroup_list.item(1).getAttribute('id'))
|
||||
self.assertEqual(consistencygroup1.id,
|
||||
consistencygroup_list.item(2).getAttribute('id'))
|
||||
|
||||
consistencygroup3.destroy()
|
||||
consistencygroup2.destroy()
|
||||
consistencygroup1.destroy()
|
||||
|
||||
@ddt.data(False, True)
|
||||
def test_list_consistencygroups_with_limit(self, is_detail):
|
||||
consistencygroup1 = self._create_consistencygroup()
|
||||
@ -395,73 +352,6 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
|
||||
consistencygroup2.destroy()
|
||||
consistencygroup3.destroy()
|
||||
|
||||
def test_list_consistencygroups_detail_xml(self):
|
||||
consistencygroup1 = self._create_consistencygroup()
|
||||
consistencygroup2 = self._create_consistencygroup()
|
||||
consistencygroup3 = self._create_consistencygroup()
|
||||
|
||||
req = webob.Request.blank('/v2/fake/consistencygroups/detail')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
consistencygroup_detail = dom.getElementsByTagName('consistencygroup')
|
||||
|
||||
self.assertEqual(
|
||||
'az1',
|
||||
consistencygroup_detail.item(0).getAttribute('availability_zone'))
|
||||
self.assertEqual(
|
||||
'this is a test consistency group',
|
||||
consistencygroup_detail.item(0).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_consistencygroup',
|
||||
consistencygroup_detail.item(0).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
consistencygroup3.id,
|
||||
consistencygroup_detail.item(0).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
'creating',
|
||||
consistencygroup_detail.item(0).getAttribute('status'))
|
||||
|
||||
self.assertEqual(
|
||||
'az1',
|
||||
consistencygroup_detail.item(1).getAttribute('availability_zone'))
|
||||
self.assertEqual(
|
||||
'this is a test consistency group',
|
||||
consistencygroup_detail.item(1).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_consistencygroup',
|
||||
consistencygroup_detail.item(1).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
consistencygroup2.id,
|
||||
consistencygroup_detail.item(1).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
'creating',
|
||||
consistencygroup_detail.item(1).getAttribute('status'))
|
||||
|
||||
self.assertEqual(
|
||||
'az1',
|
||||
consistencygroup_detail.item(2).getAttribute('availability_zone'))
|
||||
self.assertEqual(
|
||||
'this is a test consistency group',
|
||||
consistencygroup_detail.item(2).getAttribute('description'))
|
||||
self.assertEqual(
|
||||
'test_consistencygroup',
|
||||
consistencygroup_detail.item(2).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
consistencygroup1.id,
|
||||
consistencygroup_detail.item(2).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
'creating',
|
||||
consistencygroup_detail.item(2).getAttribute('status'))
|
||||
|
||||
consistencygroup3.destroy()
|
||||
consistencygroup2.destroy()
|
||||
consistencygroup1.destroy()
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_create_consistencygroup_json(self, mock_validate):
|
||||
|
@ -14,12 +14,10 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
from oslo_serialization import jsonutils
|
||||
import webob
|
||||
|
||||
from cinder.api.contrib import extended_snapshot_attributes
|
||||
from cinder import context
|
||||
from cinder import test
|
||||
from cinder.tests.unit.api import fakes
|
||||
@ -107,15 +105,3 @@ class ExtendedSnapshotAttributesTest(test.TestCase):
|
||||
self.assertSnapshotAttributes(snapshot,
|
||||
project_id='fake',
|
||||
progress='0%')
|
||||
|
||||
|
||||
class ExtendedSnapshotAttributesXmlTest(ExtendedSnapshotAttributesTest):
|
||||
content_type = 'application/xml'
|
||||
ext = extended_snapshot_attributes
|
||||
prefix = '{%s}' % ext.Extended_snapshot_attributes.namespace
|
||||
|
||||
def _get_snapshot(self, body):
|
||||
return etree.XML(body)
|
||||
|
||||
def _get_snapshots(self, body):
|
||||
return etree.XML(body).getchildren()
|
||||
|
@ -16,7 +16,6 @@
|
||||
import datetime
|
||||
|
||||
from iso8601 import iso8601
|
||||
from lxml import etree
|
||||
from oslo_utils import timeutils
|
||||
import webob.exc
|
||||
|
||||
@ -161,51 +160,3 @@ class HostTestCase(test.TestCase):
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.show,
|
||||
self.req, dest)
|
||||
|
||||
|
||||
class HostSerializerTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(HostSerializerTest, self).setUp()
|
||||
self.deserializer = os_hosts.HostDeserializer()
|
||||
|
||||
def test_index_serializer(self):
|
||||
serializer = os_hosts.HostIndexTemplate()
|
||||
text = serializer.serialize({"hosts": LIST_RESPONSE})
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('hosts', tree.tag)
|
||||
self.assertEqual(len(LIST_RESPONSE), len(tree))
|
||||
for i in range(len(LIST_RESPONSE)):
|
||||
self.assertEqual('host', tree[i].tag)
|
||||
self.assertEqual(LIST_RESPONSE[i]['service-status'],
|
||||
tree[i].get('service-status'))
|
||||
self.assertEqual(LIST_RESPONSE[i]['service'],
|
||||
tree[i].get('service'))
|
||||
self.assertEqual(LIST_RESPONSE[i]['zone'],
|
||||
tree[i].get('zone'))
|
||||
self.assertEqual(LIST_RESPONSE[i]['service-state'],
|
||||
tree[i].get('service-state'))
|
||||
self.assertEqual(LIST_RESPONSE[i]['host_name'],
|
||||
tree[i].get('host_name'))
|
||||
self.assertEqual(str(LIST_RESPONSE[i]['last-update']),
|
||||
tree[i].get('last-update'))
|
||||
|
||||
def test_update_serializer_with_status(self):
|
||||
exemplar = dict(host='test.host.1', status='enabled')
|
||||
serializer = os_hosts.HostUpdateTemplate()
|
||||
text = serializer.serialize(exemplar)
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('host', tree.tag)
|
||||
for key, value in exemplar.items():
|
||||
self.assertEqual(value, tree.get(key))
|
||||
|
||||
def test_update_deserializer(self):
|
||||
exemplar = dict(status='enabled', foo='bar')
|
||||
intext = ("<?xml version='1.0' encoding='UTF-8'?>\n"
|
||||
'<updates><status>enabled</status><foo>bar</foo></updates>')
|
||||
result = self.deserializer.deserialize(intext)
|
||||
|
||||
self.assertEqual(dict(body=exemplar), result)
|
||||
|
@ -14,14 +14,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
import webob
|
||||
|
||||
from cinder.api.contrib import qos_specs_manage
|
||||
from cinder.api import xmlutil
|
||||
from cinder import context
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
@ -186,28 +182,6 @@ class QoSSpecManageApiTest(test.TestCase):
|
||||
expected_names = ['qos_specs_1', 'qos_specs_2', 'qos_specs_3']
|
||||
self.assertEqual(set(expected_names), names)
|
||||
|
||||
@mock.patch('cinder.volume.qos_specs.get_all_specs',
|
||||
side_effect=return_qos_specs_get_all)
|
||||
def test_index_xml_response(self, mock_get_all_specs):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/qos-specs')
|
||||
res = self.controller.index(req)
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
qos_specs_response = dom.getElementsByTagName('qos_spec')
|
||||
|
||||
names = set()
|
||||
for qos_spec in qos_specs_response:
|
||||
name = qos_spec.getAttribute('name')
|
||||
names.add(name)
|
||||
|
||||
expected_names = ['qos_specs_1', 'qos_specs_2', 'qos_specs_3']
|
||||
self.assertEqual(set(expected_names), names)
|
||||
|
||||
def test_index_with_limit(self):
|
||||
url = '/v2/%s/qos-specs?limit=2' % fake.project_id
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
@ -532,29 +506,6 @@ class QoSSpecManageApiTest(test.TestCase):
|
||||
self.assertEqual('1', res_dict['qos_specs']['id'])
|
||||
self.assertEqual('qos_specs_1', res_dict['qos_specs']['name'])
|
||||
|
||||
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
|
||||
side_effect=return_qos_specs_get_qos_specs)
|
||||
def test_show_xml_response(self, mock_get_qos_specs):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/qos-specs/1')
|
||||
res = self.controller.show(req, '1')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
qos_spec_response = dom.getElementsByTagName('qos_spec')
|
||||
qos_spec = qos_spec_response.item(0)
|
||||
|
||||
id = qos_spec.getAttribute('id')
|
||||
name = qos_spec.getAttribute('name')
|
||||
consumer = qos_spec.getAttribute('consumer')
|
||||
|
||||
self.assertEqual(u'1', id)
|
||||
self.assertEqual('qos_specs_1', name)
|
||||
self.assertEqual('back-end', consumer)
|
||||
|
||||
@mock.patch('cinder.volume.qos_specs.get_associations',
|
||||
side_effect=return_get_qos_associations)
|
||||
def test_get_associations(self, mock_get_assciations):
|
||||
@ -567,29 +518,6 @@ class QoSSpecManageApiTest(test.TestCase):
|
||||
self.assertEqual('FakeVolTypeID',
|
||||
res['qos_associations'][0]['id'])
|
||||
|
||||
@mock.patch('cinder.volume.qos_specs.get_associations',
|
||||
side_effect=return_get_qos_associations)
|
||||
def test_get_associations_xml_response(self, mock_get_assciations):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/qos-specs/1/associations')
|
||||
res = self.controller.associations(req, '1')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
associations_response = dom.getElementsByTagName('associations')
|
||||
association = associations_response.item(0)
|
||||
|
||||
id = association.getAttribute('id')
|
||||
name = association.getAttribute('name')
|
||||
association_type = association.getAttribute('association_type')
|
||||
|
||||
self.assertEqual('FakeVolTypeID', id)
|
||||
self.assertEqual('FakeVolTypeName', name)
|
||||
self.assertEqual('volume_type', association_type)
|
||||
|
||||
@mock.patch('cinder.volume.qos_specs.get_associations',
|
||||
side_effect=return_get_qos_associations)
|
||||
def test_get_associations_not_found(self, mock_get_assciations):
|
||||
@ -731,108 +659,3 @@ class QoSSpecManageApiTest(test.TestCase):
|
||||
'/v2/fake/qos-specs/222/disassociate_all')
|
||||
self.assertRaises(webob.exc.HTTPInternalServerError,
|
||||
self.controller.disassociate_all, req, '222')
|
||||
|
||||
|
||||
class TestQoSSpecsTemplate(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestQoSSpecsTemplate, self).setUp()
|
||||
self.serializer = qos_specs_manage.QoSSpecsTemplate()
|
||||
|
||||
def test_qos_specs_serializer(self):
|
||||
fixture = {
|
||||
"qos_specs": [
|
||||
{
|
||||
"specs": {
|
||||
"key1": "v1",
|
||||
"key2": "v2",
|
||||
},
|
||||
"consumer": "back-end",
|
||||
"name": "qos-2",
|
||||
"id": "61e7b72f-ef15-46d9-b00e-b80f699999d0"
|
||||
},
|
||||
{
|
||||
"specs": {"total_iops_sec": "200"},
|
||||
"consumer": "front-end",
|
||||
"name": "qos-1",
|
||||
"id": "e44bba5e-b629-4b96-9aa3-0404753a619b"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
output = self.serializer.serialize(fixture)
|
||||
root = etree.XML(output)
|
||||
xmlutil.validate_schema(root, 'qos_specs')
|
||||
qos_elems = root.findall("qos_spec")
|
||||
self.assertEqual(2, len(qos_elems))
|
||||
for i, qos_elem in enumerate(qos_elems):
|
||||
qos_dict = fixture['qos_specs'][i]
|
||||
|
||||
# check qos_spec attributes
|
||||
for key in ['name', 'id', 'consumer']:
|
||||
self.assertEqual(str(qos_dict[key]), qos_elem.get(key))
|
||||
|
||||
# check specs
|
||||
specs = qos_elem.find("specs")
|
||||
new_dict = {}
|
||||
for element in specs.iter(tag=etree.Element):
|
||||
# skip root element for specs
|
||||
if element.tag == "specs":
|
||||
continue
|
||||
new_dict.update({element.tag: element.text})
|
||||
|
||||
self.assertDictMatch(qos_dict['specs'], new_dict)
|
||||
|
||||
|
||||
class TestAssociationsTemplate(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestAssociationsTemplate, self).setUp()
|
||||
self.serializer = qos_specs_manage.AssociationsTemplate()
|
||||
|
||||
def test_qos_associations_serializer(self):
|
||||
fixture = {
|
||||
"qos_associations": [
|
||||
{
|
||||
"association_type": "volume_type",
|
||||
"name": "type-4",
|
||||
"id": "14d54d29-51a4-4046-9f6f-cf9800323563"
|
||||
},
|
||||
{
|
||||
"association_type": "volume_type",
|
||||
"name": "type-2",
|
||||
"id": "3689ce83-308d-4ba1-8faf-7f1be04a282b"}
|
||||
]
|
||||
}
|
||||
|
||||
output = self.serializer.serialize(fixture)
|
||||
root = etree.XML(output)
|
||||
xmlutil.validate_schema(root, 'qos_associations')
|
||||
association_elems = root.findall("associations")
|
||||
self.assertEqual(2, len(association_elems))
|
||||
for i, association_elem in enumerate(association_elems):
|
||||
association_dict = fixture['qos_associations'][i]
|
||||
|
||||
# check qos_spec attributes
|
||||
for key in ['name', 'id', 'association_type']:
|
||||
self.assertEqual(str(association_dict[key]),
|
||||
association_elem.get(key))
|
||||
|
||||
|
||||
class TestQoSSpecsKeyDeserializer(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestQoSSpecsKeyDeserializer, self).setUp()
|
||||
self.deserializer = qos_specs_manage.QoSSpecsKeyDeserializer()
|
||||
|
||||
def test_keys(self):
|
||||
self_request = """
|
||||
<keys><xyz /><abc /></keys>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"keys": ["xyz", "abc"]
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_bad_format(self):
|
||||
self_request = """
|
||||
<qos_specs><keys><xyz /><abc /></keys></qos_specs>"""
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.deserializer.deserialize, self_request)
|
||||
|
@ -21,8 +21,6 @@ Tests for cinder.api.contrib.quotas.py
|
||||
|
||||
import mock
|
||||
|
||||
from lxml import etree
|
||||
|
||||
import uuid
|
||||
import webob.exc
|
||||
|
||||
@ -1039,23 +1037,3 @@ class QuotaSetsControllerNestedQuotasTest(QuotaSetsControllerTestBase):
|
||||
quota_limit['volumes'] = 5
|
||||
self.controller.update(self.req, self.B.id, body)
|
||||
self._assert_quota_show(self.A.id, res, allocated=6, in_use=1, limit=7)
|
||||
|
||||
|
||||
class QuotaSerializerTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaSerializerTest, self).setUp()
|
||||
self.req = mock.Mock()
|
||||
self.req.environ = {'cinder.context': context.get_admin_context()}
|
||||
|
||||
def test_update_serializer(self):
|
||||
serializer = quotas.QuotaTemplate()
|
||||
quota_set = make_body(root=False)
|
||||
text = serializer.serialize({'quota_set': quota_set})
|
||||
tree = etree.fromstring(text)
|
||||
self.assertEqual('quota_set', tree.tag)
|
||||
self.assertEqual(quota_set['id'], tree.get('id'))
|
||||
body = make_body(root=False, tenant_id=None)
|
||||
for node in tree:
|
||||
self.assertIn(node.tag, body)
|
||||
self.assertEqual(str(body[node.tag]), node.text)
|
||||
|
@ -20,7 +20,6 @@ Tests for cinder.api.contrib.quota_classes.py
|
||||
|
||||
import mock
|
||||
|
||||
from lxml import etree
|
||||
import webob.exc
|
||||
|
||||
|
||||
@ -152,23 +151,3 @@ class QuotaClassSetsControllerTest(test.TestCase):
|
||||
request_body=body,
|
||||
tenant_id=None),
|
||||
result)
|
||||
|
||||
|
||||
class QuotaClassesSerializerTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaClassesSerializerTest, self).setUp()
|
||||
self.req = mock.Mock()
|
||||
self.req.environ = {'cinder.context': context.get_admin_context()}
|
||||
|
||||
def test_update_serializer(self):
|
||||
serializer = quota_classes.QuotaClassTemplate()
|
||||
quota_class_set = make_body(root=False)
|
||||
text = serializer.serialize({'quota_class_set': quota_class_set})
|
||||
tree = etree.fromstring(text)
|
||||
self.assertEqual('quota_class_set', tree.tag)
|
||||
self.assertEqual(tree.get('id'), quota_class_set['id'])
|
||||
body = make_body(root=False, tenant_id=None)
|
||||
for node in tree:
|
||||
self.assertIn(node.tag, body)
|
||||
self.assertEqual(str(body[node.tag]), node.text)
|
||||
|
@ -15,7 +15,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
import webob
|
||||
|
||||
@ -266,35 +265,3 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
||||
def test_create_invalid_too_many_key(self):
|
||||
body = {"key1": "value1", "ke/y2": "value2", "key3": "value3"}
|
||||
self._extra_specs_create_bad_body(body=body)
|
||||
|
||||
|
||||
class VolumeTypeExtraSpecsSerializerTest(test.TestCase):
|
||||
def test_index_create_serializer(self):
|
||||
serializer = types_extra_specs.VolumeTypeExtraSpecsTemplate()
|
||||
|
||||
# Just getting some input data
|
||||
extra_specs = stub_volume_type_extra_specs()
|
||||
text = serializer.serialize(dict(extra_specs=extra_specs))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('extra_specs', tree.tag)
|
||||
self.assertEqual(len(extra_specs), len(tree))
|
||||
seen = set(extra_specs.keys())
|
||||
for child in tree:
|
||||
self.assertIn(child.tag, seen)
|
||||
self.assertEqual(extra_specs[child.tag], child.text)
|
||||
seen.remove(child.tag)
|
||||
self.assertEqual(0, len(seen))
|
||||
|
||||
def test_update_show_serializer(self):
|
||||
serializer = types_extra_specs.VolumeTypeExtraSpecTemplate()
|
||||
|
||||
exemplar = dict(key1='value1')
|
||||
text = serializer.serialize(exemplar)
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('key1', tree.tag)
|
||||
self.assertEqual('value1', tree.text)
|
||||
self.assertEqual(0, len(tree))
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
@ -119,27 +118,3 @@ class VolumeHostAttributeTest(test.TestCase):
|
||||
res = req.get_response(app())
|
||||
vol = jsonutils.loads(res.body)['volumes']
|
||||
self.assertNotIn('os-vol-host-attr:host', vol[0])
|
||||
|
||||
def test_get_volume_xml(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
req = webob.Request.blank('/v2/fake/volumes/%s' % self.UUID)
|
||||
req.method = 'GET'
|
||||
req.accept = 'application/xml'
|
||||
req.environ['cinder.context'] = ctx
|
||||
res = req.get_response(app())
|
||||
vol = etree.XML(res.body)
|
||||
host_key = ('{http://docs.openstack.org/volume/ext/'
|
||||
'volume_host_attribute/api/v2}host')
|
||||
self.assertEqual('host001', vol.get(host_key))
|
||||
|
||||
def test_list_volumes_detail_xml(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
req = webob.Request.blank('/v2/fake/volumes/detail')
|
||||
req.method = 'GET'
|
||||
req.accept = 'application/xml'
|
||||
req.environ['cinder.context'] = ctx
|
||||
res = req.get_response(app())
|
||||
vol = list(etree.XML(res.body))[0]
|
||||
host_key = ('{http://docs.openstack.org/volume/ext/'
|
||||
'volume_host_attribute/api/v2}host')
|
||||
self.assertEqual('host001', vol.get(host_key))
|
||||
|
@ -13,15 +13,12 @@
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
from xml.dom import minidom
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.contrib import volume_image_metadata
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder import context
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
@ -330,34 +327,3 @@ class VolumeImageMetadataTest(test.TestCase):
|
||||
self.assertEqual(200, res.status_int)
|
||||
self.assertEqual(fake_image_metadata,
|
||||
jsonutils.loads(res.body)["metadata"])
|
||||
|
||||
|
||||
class ImageMetadataXMLDeserializer(common.MetadataXMLDeserializer):
|
||||
metadata_node_name = "volume_image_metadata"
|
||||
|
||||
|
||||
class VolumeImageMetadataXMLTest(VolumeImageMetadataTest):
|
||||
content_type = 'application/xml'
|
||||
|
||||
def _get_image_metadata(self, body):
|
||||
deserializer = wsgi.XMLDeserializer()
|
||||
volume = deserializer.find_first_child_named(
|
||||
minidom.parseString(body), 'volume')
|
||||
image_metadata = deserializer.find_first_child_named(
|
||||
volume, 'volume_image_metadata')
|
||||
return wsgi.MetadataXMLDeserializer().extract_metadata(image_metadata)
|
||||
|
||||
def _get_image_metadata_list(self, body):
|
||||
deserializer = wsgi.XMLDeserializer()
|
||||
volumes = deserializer.find_first_child_named(
|
||||
minidom.parseString(body), 'volumes')
|
||||
volume_list = deserializer.find_children_named(volumes, 'volume')
|
||||
image_metadata_list = [
|
||||
deserializer.find_first_child_named(
|
||||
volume, 'volume_image_metadata'
|
||||
)
|
||||
for volume in volume_list]
|
||||
|
||||
metadata_deserializer = wsgi.MetadataXMLDeserializer()
|
||||
return [metadata_deserializer.extract_metadata(image_metadata)
|
||||
for image_metadata in image_metadata_list]
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
@ -121,33 +120,3 @@ class VolumeMigStatusAttributeTest(test.TestCase):
|
||||
vol = jsonutils.loads(res.body)['volumes']
|
||||
self.assertNotIn('os-vol-mig-status-attr:migstat', vol[0])
|
||||
self.assertNotIn('os-vol-mig-status-attr:name_id', vol[0])
|
||||
|
||||
def test_get_volume_xml(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
req = webob.Request.blank('/v2/fake/volumes/%s' % self.UUID)
|
||||
req.method = 'GET'
|
||||
req.accept = 'application/xml'
|
||||
req.environ['cinder.context'] = ctx
|
||||
res = req.get_response(app())
|
||||
vol = etree.XML(res.body)
|
||||
mig_key = ('{http://docs.openstack.org/volume/ext/'
|
||||
'volume_mig_status_attribute/api/v1}migstat')
|
||||
self.assertEqual('migrating', vol.get(mig_key))
|
||||
mig_key = ('{http://docs.openstack.org/volume/ext/'
|
||||
'volume_mig_status_attribute/api/v1}name_id')
|
||||
self.assertEqual('fake2', vol.get(mig_key))
|
||||
|
||||
def test_list_volumes_detail_xml(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
req = webob.Request.blank('/v2/fake/volumes/detail')
|
||||
req.method = 'GET'
|
||||
req.accept = 'application/xml'
|
||||
req.environ['cinder.context'] = ctx
|
||||
res = req.get_response(app())
|
||||
vol = list(etree.XML(res.body))[0]
|
||||
mig_key = ('{http://docs.openstack.org/volume/ext/'
|
||||
'volume_mig_status_attribute/api/v1}migstat')
|
||||
self.assertEqual('migrating', vol.get(mig_key))
|
||||
mig_key = ('{http://docs.openstack.org/volume/ext/'
|
||||
'volume_mig_status_attribute/api/v1}name_id')
|
||||
self.assertEqual('fake2', vol.get(mig_key))
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
from oslo_serialization import jsonutils
|
||||
import webob
|
||||
|
||||
@ -102,27 +101,3 @@ class VolumeTenantAttributeTest(test.TestCase):
|
||||
res = req.get_response(app())
|
||||
vol = jsonutils.loads(res.body)['volumes']
|
||||
self.assertNotIn('os-vol-tenant-attr:tenant_id', vol[0])
|
||||
|
||||
def test_get_volume_xml(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
req = webob.Request.blank('/v2/fake/volumes/%s' % self.UUID)
|
||||
req.method = 'GET'
|
||||
req.accept = 'application/xml'
|
||||
req.environ['cinder.context'] = ctx
|
||||
res = req.get_response(app())
|
||||
vol = etree.XML(res.body)
|
||||
tenant_key = ('{http://docs.openstack.org/volume/ext/'
|
||||
'volume_tenant_attribute/api/v2}tenant_id')
|
||||
self.assertEqual(PROJECT_ID, vol.get(tenant_key))
|
||||
|
||||
def test_list_volumes_detail_xml(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
req = webob.Request.blank('/v2/fake/volumes/detail')
|
||||
req.method = 'GET'
|
||||
req.accept = 'application/xml'
|
||||
req.environ['cinder.context'] = ctx
|
||||
res = req.get_response(app())
|
||||
vol = list(etree.XML(res.body))[0]
|
||||
tenant_key = ('{http://docs.openstack.org/volume/ext/'
|
||||
'volume_tenant_attribute/api/v2}tenant_id')
|
||||
self.assertEqual(PROJECT_ID, vol.get(tenant_key))
|
||||
|
@ -18,10 +18,8 @@ Tests for volume transfer code.
|
||||
"""
|
||||
|
||||
import mock
|
||||
from xml.dom import minidom
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
import webob
|
||||
|
||||
from cinder.api.contrib import volume_transfer
|
||||
@ -84,24 +82,6 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
db.transfer_destroy(context.get_admin_context(), transfer['id'])
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
|
||||
def test_show_transfer_xml_content_type(self):
|
||||
volume_id = self._create_volume(size=5)
|
||||
transfer = self._create_transfer(volume_id)
|
||||
req = webob.Request.blank('/v2/fake/os-volume-transfer/%s' %
|
||||
transfer['id'])
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
transfer_xml = dom.getElementsByTagName('transfer')
|
||||
name = transfer_xml.item(0).getAttribute('name')
|
||||
self.assertEqual('test_transfer', name.strip())
|
||||
|
||||
db.transfer_destroy(context.get_admin_context(), transfer['id'])
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
|
||||
def test_show_transfer_with_transfer_NotFound(self):
|
||||
req = webob.Request.blank('/v2/fake/os-volume-transfer/1234')
|
||||
req.method = 'GET'
|
||||
@ -138,33 +118,6 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
db.volume_destroy(context.get_admin_context(), volume_id_1)
|
||||
db.volume_destroy(context.get_admin_context(), volume_id_2)
|
||||
|
||||
def test_list_transfers_xml(self):
|
||||
volume_id_1 = self._create_volume(size=5)
|
||||
volume_id_2 = self._create_volume(size=5)
|
||||
transfer1 = self._create_transfer(volume_id_1)
|
||||
transfer2 = self._create_transfer(volume_id_2)
|
||||
|
||||
req = webob.Request.blank('/v2/fake/os-volume-transfer')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
transfer_list = dom.getElementsByTagName('transfer')
|
||||
self.assertEqual(3, transfer_list.item(0).attributes.length)
|
||||
self.assertEqual(transfer1['id'],
|
||||
transfer_list.item(0).getAttribute('id'))
|
||||
self.assertEqual(3, transfer_list.item(1).attributes.length)
|
||||
self.assertEqual(transfer2['id'],
|
||||
transfer_list.item(1).getAttribute('id'))
|
||||
|
||||
db.transfer_destroy(context.get_admin_context(), transfer2['id'])
|
||||
db.transfer_destroy(context.get_admin_context(), transfer1['id'])
|
||||
db.volume_destroy(context.get_admin_context(), volume_id_2)
|
||||
db.volume_destroy(context.get_admin_context(), volume_id_1)
|
||||
|
||||
def test_list_transfers_detail_json(self):
|
||||
volume_id_1 = self._create_volume(size=5)
|
||||
volume_id_2 = self._create_volume(size=5)
|
||||
@ -196,43 +149,6 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
db.volume_destroy(context.get_admin_context(), volume_id_2)
|
||||
db.volume_destroy(context.get_admin_context(), volume_id_1)
|
||||
|
||||
def test_list_transfers_detail_xml(self):
|
||||
volume_id_1 = self._create_volume(size=5)
|
||||
volume_id_2 = self._create_volume(size=5)
|
||||
transfer1 = self._create_transfer(volume_id_1)
|
||||
transfer2 = self._create_transfer(volume_id_2)
|
||||
|
||||
req = webob.Request.blank('/v2/fake/os-volume-transfer/detail')
|
||||
req.method = 'GET'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
transfer_detail = dom.getElementsByTagName('transfer')
|
||||
|
||||
self.assertEqual(4, transfer_detail.item(0).attributes.length)
|
||||
self.assertEqual(
|
||||
'test_transfer', transfer_detail.item(0).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
transfer1['id'], transfer_detail.item(0).getAttribute('id'))
|
||||
self.assertEqual(volume_id_1,
|
||||
transfer_detail.item(0).getAttribute('volume_id'))
|
||||
|
||||
self.assertEqual(4, transfer_detail.item(1).attributes.length)
|
||||
self.assertEqual(
|
||||
'test_transfer', transfer_detail.item(1).getAttribute('name'))
|
||||
self.assertEqual(
|
||||
transfer2['id'], transfer_detail.item(1).getAttribute('id'))
|
||||
self.assertEqual(
|
||||
volume_id_2, transfer_detail.item(1).getAttribute('volume_id'))
|
||||
|
||||
db.transfer_destroy(context.get_admin_context(), transfer2['id'])
|
||||
db.transfer_destroy(context.get_admin_context(), transfer1['id'])
|
||||
db.volume_destroy(context.get_admin_context(), volume_id_2)
|
||||
db.volume_destroy(context.get_admin_context(), volume_id_1)
|
||||
|
||||
def test_list_transfers_with_all_tenants(self):
|
||||
volume_id_1 = self._create_volume(size=5)
|
||||
volume_id_2 = self._create_volume(size=5, project_id='fake1')
|
||||
@ -280,35 +196,6 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_string_length')
|
||||
def test_create_transfer_xml(self, mock_validate):
|
||||
volume_size = 2
|
||||
volume_id = self._create_volume(status='available', size=volume_size)
|
||||
|
||||
body = '<transfer name="transfer-001" volume_id="%s"/>' % volume_id
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
|
||||
req = webob.Request.blank('/v2/fake/os-volume-transfer')
|
||||
req.body = body
|
||||
req.method = 'POST'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(202, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
transfer = dom.getElementsByTagName('transfer')
|
||||
self.assertTrue(transfer.item(0).hasAttribute('id'))
|
||||
self.assertTrue(transfer.item(0).hasAttribute('auth_key'))
|
||||
self.assertTrue(transfer.item(0).hasAttribute('created_at'))
|
||||
self.assertEqual('transfer-001', transfer.item(0).getAttribute('name'))
|
||||
self.assertTrue(transfer.item(0).hasAttribute('volume_id'))
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
|
||||
def test_create_transfer_with_no_body(self):
|
||||
req = webob.Request.blank('/v2/fake/os-volume-transfer')
|
||||
req.body = jsonutils.dump_as_bytes(None)
|
||||
@ -433,34 +320,6 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
# cleanup
|
||||
svc.stop()
|
||||
|
||||
def test_accept_transfer_volume_id_specified_xml(self):
|
||||
volume_id = self._create_volume(size=5)
|
||||
transfer = self._create_transfer(volume_id)
|
||||
svc = self.start_service('volume', host='fake_host')
|
||||
|
||||
body = '<accept auth_key="%s"/>' % transfer['auth_key']
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
|
||||
req = webob.Request.blank('/v2/fake/os-volume-transfer/%s/accept' %
|
||||
transfer['id'])
|
||||
req.body = body
|
||||
req.method = 'POST'
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(202, res.status_int)
|
||||
dom = minidom.parseString(res.body)
|
||||
accept = dom.getElementsByTagName('transfer')
|
||||
self.assertEqual(transfer['id'],
|
||||
accept.item(0).getAttribute('id'))
|
||||
self.assertEqual(volume_id, accept.item(0).getAttribute('volume_id'))
|
||||
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
# cleanup
|
||||
svc.stop()
|
||||
|
||||
def test_accept_transfer_with_no_body(self):
|
||||
volume_id = self._create_volume(size=5)
|
||||
transfer = self._create_transfer(volume_id)
|
||||
|
@ -205,25 +205,6 @@ class VolumeTypeEncryptionTest(test.TestCase):
|
||||
self._create('fake_cipher', 'front-end', 128, 'fake_encryptor')
|
||||
self.assertTrue(mock_validate_integer.called)
|
||||
|
||||
def test_create_xml(self):
|
||||
volume_type = self._default_volume_type
|
||||
db.volume_type_create(context.get_admin_context(), volume_type)
|
||||
|
||||
ctxt = context.RequestContext('fake', 'fake', is_admin=True)
|
||||
|
||||
req = webob.Request.blank('/v2/fake/types/%s/encryption'
|
||||
% volume_type['id'])
|
||||
req.method = 'POST'
|
||||
req.body = (b'<encryption provider="test_provider" '
|
||||
b'cipher="cipher" control_location="front-end" />')
|
||||
req.headers['Content-Type'] = 'application/xml'
|
||||
req.headers['Accept'] = 'application/xml'
|
||||
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctxt))
|
||||
|
||||
self.assertEqual(200, res.status_int)
|
||||
|
||||
db.volume_type_destroy(context.get_admin_context(), volume_type['id'])
|
||||
|
||||
def test_create_invalid_volume_type(self):
|
||||
volume_type = self._default_volume_type
|
||||
body = {"encryption": stub_volume_type_encryption()}
|
||||
|
@ -12,33 +12,15 @@
|
||||
# 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
|
||||
|
||||
import mock
|
||||
from oslo_i18n import fixture as i18n_fixture
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
import webob.dec
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.i18n import _
|
||||
from cinder import test
|
||||
|
||||
|
||||
class TestCase(test.TestCase):
|
||||
def _prepare_xml(self, xml_string):
|
||||
"""Remove characters from string which hinder XML equality testing."""
|
||||
if six.PY3 and isinstance(xml_string, bytes):
|
||||
xml_string = xml_string.decode('utf-8')
|
||||
xml_string = xml_string.replace(" ", "")
|
||||
xml_string = xml_string.replace("\n", "")
|
||||
xml_string = xml_string.replace("\t", "")
|
||||
return xml_string
|
||||
|
||||
|
||||
class TestFaults(TestCase):
|
||||
class TestFaults(test.TestCase):
|
||||
"""Tests covering `cinder.api.openstack.faults:Fault` class."""
|
||||
|
||||
def setUp(self):
|
||||
@ -92,219 +74,7 @@ class TestFaults(TestCase):
|
||||
self.assertEqual("application/json", response.content_type)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_raise(self):
|
||||
"""Ensure the ability to raise :class:`Fault` in WSGI-ified methods."""
|
||||
@webob.dec.wsgify
|
||||
def raiser(req):
|
||||
raise wsgi.Fault(webob.exc.HTTPNotFound(explanation='whut?'))
|
||||
|
||||
req = webob.Request.blank('/.xml')
|
||||
resp = req.get_response(raiser)
|
||||
self.assertEqual("application/xml", resp.content_type)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
self.assertIn(b'whut?', resp.body)
|
||||
|
||||
def test_raise_403(self):
|
||||
"""Ensure the ability to raise :class:`Fault` in WSGI-ified methods."""
|
||||
@webob.dec.wsgify
|
||||
def raiser(req):
|
||||
raise wsgi.Fault(webob.exc.HTTPForbidden(explanation='whut?'))
|
||||
|
||||
req = webob.Request.blank('/.xml')
|
||||
resp = req.get_response(raiser)
|
||||
self.assertEqual("application/xml", resp.content_type)
|
||||
self.assertEqual(403, resp.status_int)
|
||||
self.assertNotIn('resizeNotAllowed', resp.body)
|
||||
self.assertIn(b'forbidden', resp.body)
|
||||
|
||||
@mock.patch('cinder.api.openstack.wsgi.i18n.translate')
|
||||
def test_raise_http_with_localized_explanation(self, mock_translate):
|
||||
params = ('blah', )
|
||||
expl = _("String with params: %s") % params
|
||||
|
||||
def _mock_translation(msg, locale):
|
||||
return "Mensaje traducido"
|
||||
|
||||
mock_translate.side_effect = _mock_translation
|
||||
|
||||
@webob.dec.wsgify
|
||||
def raiser(req):
|
||||
raise wsgi.Fault(webob.exc.HTTPNotFound(explanation=expl))
|
||||
|
||||
req = webob.Request.blank('/.xml')
|
||||
resp = req.get_response(raiser)
|
||||
self.assertEqual("application/xml", resp.content_type)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
self.assertIn(b"Mensaje traducido", resp.body)
|
||||
self.stubs.UnsetAll()
|
||||
|
||||
def test_fault_has_status_int(self):
|
||||
"""Ensure the status_int is set correctly on faults."""
|
||||
fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='what?'))
|
||||
self.assertEqual(400, fault.status_int)
|
||||
|
||||
def test_xml_serializer(self):
|
||||
"""Ensure that a v2 request responds with a v2 xmlns."""
|
||||
request = webob.Request.blank('/v2',
|
||||
headers={"Accept": "application/xml"})
|
||||
|
||||
fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='scram'))
|
||||
response = request.get_response(fault)
|
||||
|
||||
self.assertIn(common.XML_NS_V2, response.body.decode())
|
||||
self.assertEqual("application/xml", response.content_type)
|
||||
self.assertEqual(400, response.status_int)
|
||||
|
||||
|
||||
class FaultsXMLSerializationTestV11(TestCase):
|
||||
"""Tests covering `cinder.api.openstack.faults:Fault` class."""
|
||||
|
||||
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)
|
||||
if six.PY3:
|
||||
output = output.decode('utf-8')
|
||||
actual = minidom.parseString(self._prepare_xml(output))
|
||||
|
||||
expected = minidom.parseString(self._prepare_xml("""
|
||||
<overLimit code="413" xmlns="%s">
|
||||
<message>sorry</message>
|
||||
<retryAfter>4</retryAfter>
|
||||
</overLimit>
|
||||
""") % common.XML_NS_V1)
|
||||
|
||||
self.assertEqual(expected.toxml(), actual.toxml())
|
||||
|
||||
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)
|
||||
if six.PY3:
|
||||
output = output.decode('utf-8')
|
||||
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())
|
||||
|
||||
|
||||
class FaultsXMLSerializationTestV2(TestCase):
|
||||
"""Tests covering `cinder.api.openstack.faults:Fault` class."""
|
||||
|
||||
def test_400_fault(self):
|
||||
metadata = {'attributes': {"badRequest": 'code'}}
|
||||
serializer = wsgi.XMLDictSerializer(metadata=metadata,
|
||||
xmlns=common.XML_NS_V2)
|
||||
|
||||
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_V2)
|
||||
|
||||
self.assertEqual(expected.toxml(), actual.toxml())
|
||||
|
||||
def test_413_fault(self):
|
||||
metadata = {'attributes': {"overLimit": 'code'}}
|
||||
serializer = wsgi.XMLDictSerializer(metadata=metadata,
|
||||
xmlns=common.XML_NS_V2)
|
||||
|
||||
fixture = {
|
||||
"overLimit": {
|
||||
"message": "sorry",
|
||||
"code": 413,
|
||||
"retryAfter": 4,
|
||||
},
|
||||
}
|
||||
|
||||
output = serializer.serialize(fixture)
|
||||
actual = minidom.parseString(self._prepare_xml(output))
|
||||
|
||||
expected = minidom.parseString(self._prepare_xml("""
|
||||
<overLimit code="413" xmlns="%s">
|
||||
<message>sorry</message>
|
||||
<retryAfter>4</retryAfter>
|
||||
</overLimit>
|
||||
""") % common.XML_NS_V2)
|
||||
|
||||
self.assertEqual(expected.toxml(), actual.toxml())
|
||||
|
||||
def test_404_fault(self):
|
||||
metadata = {'attributes': {"itemNotFound": 'code'}}
|
||||
serializer = wsgi.XMLDictSerializer(metadata=metadata,
|
||||
xmlns=common.XML_NS_V2)
|
||||
|
||||
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_V2)
|
||||
|
||||
self.assertEqual(expected.toxml(), actual.toxml())
|
||||
|
@ -40,9 +40,7 @@ class RequestTest(test.TestCase):
|
||||
self.assertEqual("application/json", result)
|
||||
|
||||
def test_content_type_from_accept(self):
|
||||
for content_type in ('application/xml',
|
||||
'application/vnd.openstack.volume+xml',
|
||||
'application/json',
|
||||
for content_type in ('application/json',
|
||||
'application/vnd.openstack.volume+json'):
|
||||
request = wsgi.Request.blank('/tests/123')
|
||||
request.headers["Accept"] = content_type
|
||||
@ -51,21 +49,11 @@ class RequestTest(test.TestCase):
|
||||
|
||||
def test_content_type_from_accept_best(self):
|
||||
request = wsgi.Request.blank('/tests/123')
|
||||
request.headers["Accept"] = "application/xml, application/json"
|
||||
request.headers["Accept"] = "application/json"
|
||||
result = request.best_match_content_type()
|
||||
self.assertEqual("application/json", result)
|
||||
|
||||
request = wsgi.Request.blank('/tests/123')
|
||||
request.headers["Accept"] = ("application/json; q=0.3, "
|
||||
"application/xml; q=0.9")
|
||||
result = request.best_match_content_type()
|
||||
self.assertEqual("application/xml", result)
|
||||
|
||||
def test_content_type_from_query_extension(self):
|
||||
request = wsgi.Request.blank('/tests/123.xml')
|
||||
result = request.best_match_content_type()
|
||||
self.assertEqual("application/xml", result)
|
||||
|
||||
request = wsgi.Request.blank('/tests/123.json')
|
||||
result = request.best_match_content_type()
|
||||
self.assertEqual("application/json", result)
|
||||
@ -74,12 +62,6 @@ class RequestTest(test.TestCase):
|
||||
result = request.best_match_content_type()
|
||||
self.assertEqual("application/json", result)
|
||||
|
||||
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("application/xml", result)
|
||||
|
||||
def test_content_type_accept_default(self):
|
||||
request = wsgi.Request.blank('/tests/123.unsupported')
|
||||
request.headers["Accept"] = "application/unsupported1"
|
||||
@ -203,16 +185,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 = b'<serversxmlns="asdf"><a>(2,3)</a></servers>'
|
||||
serializer = wsgi.XMLDictSerializer(xmlns="asdf")
|
||||
result = serializer.serialize(input_dict)
|
||||
result = result.replace(b'\n', b'').replace(b' ', b'')
|
||||
self.assertEqual(expected_xml, result)
|
||||
|
||||
|
||||
class JSONDictSerializerTest(test.TestCase):
|
||||
def test_json(self):
|
||||
input_dict = dict(servers=dict(a=(2, 3)))
|
||||
@ -252,62 +224,6 @@ class JSONDeserializerTest(test.TestCase):
|
||||
self.assertEqual(as_dict, deserializer.deserialize(data))
|
||||
|
||||
|
||||
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(as_dict, deserializer.deserialize(xml))
|
||||
|
||||
def test_xml_empty(self):
|
||||
xml = """<a></a>"""
|
||||
as_dict = {"body": {"a": {}}}
|
||||
deserializer = wsgi.XMLDeserializer()
|
||||
self.assertEqual(as_dict, deserializer.deserialize(xml))
|
||||
|
||||
|
||||
class MetadataXMLDeserializerTest(test.TestCase):
|
||||
def test_xml_meta_parsing_special_character(self):
|
||||
"""Test XML meta parsing with special characters.
|
||||
|
||||
Test that when a SaxParser splits a string containing special
|
||||
characters into multiple childNodes there are no issues extracting
|
||||
the text.
|
||||
"""
|
||||
meta_xml_str = """
|
||||
<metadata>
|
||||
<meta key="key3">value&3</meta>
|
||||
<meta key="key2">value2</meta>
|
||||
<meta key="key1">value1</meta>
|
||||
</metadata>
|
||||
""".strip()
|
||||
meta_expected = {'key1': 'value1',
|
||||
'key2': 'value2',
|
||||
'key3': 'value&3'}
|
||||
meta_deserializer = wsgi.MetadataXMLDeserializer()
|
||||
document = wsgi.utils.safe_minidom_parse_string(meta_xml_str)
|
||||
root_node = document.childNodes[0]
|
||||
meta_extracted = meta_deserializer.extract_metadata(root_node)
|
||||
self.assertEqual(meta_expected, meta_extracted)
|
||||
|
||||
|
||||
class ResourceTest(test.TestCase):
|
||||
def test_resource_call(self):
|
||||
class Controller(object):
|
||||
@ -365,18 +281,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')
|
||||
@ -400,18 +304,6 @@ class ResourceTest(test.TestCase):
|
||||
None, 'action', 'application/json',
|
||||
'{"barAction": true}')
|
||||
|
||||
def test_get_method_action_method(self):
|
||||
class Controller(object):
|
||||
def action(self, req, pants=None):
|
||||
return pants
|
||||
|
||||
controller = Controller()
|
||||
resource = wsgi.Resource(controller)
|
||||
method, _extensions = resource.get_method(None, 'action',
|
||||
'application/xml',
|
||||
'<fooAction>true</fooAction')
|
||||
self.assertEqual(controller.action, method)
|
||||
|
||||
def test_get_action_args(self):
|
||||
class Controller(object):
|
||||
def index(self, req, pants=None):
|
||||
@ -512,12 +404,7 @@ 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
|
||||
|
||||
@ -527,26 +414,6 @@ class ResourceTest(test.TestCase):
|
||||
obj = resource.deserialize(controller.index, 'application/json', 'foo')
|
||||
self.assertEqual('json', obj)
|
||||
|
||||
def test_deserialize_decorator(self):
|
||||
class JSONDeserializer(object):
|
||||
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('xml', obj)
|
||||
|
||||
def test_register_actions(self):
|
||||
class Controller(object):
|
||||
def index(self, req, pants=None):
|
||||
@ -925,57 +792,6 @@ class ResponseObjectTest(test.TestCase):
|
||||
robj = wsgi.ResponseObject({})
|
||||
self.assertEqual({}, robj.serializers)
|
||||
|
||||
def test_bind_serializers(self):
|
||||
robj = wsgi.ResponseObject({}, json='foo')
|
||||
robj._bind_method_serializers(dict(xml='bar', json='baz'))
|
||||
self.assertEqual(dict(xml='bar', json='foo'), robj.serializers)
|
||||
|
||||
def test_get_serializer(self):
|
||||
robj = wsgi.ResponseObject({}, json='json', xml='xml', atom='atom')
|
||||
for content_type, mtype in wsgi._MEDIA_TYPE_MAP.items():
|
||||
_mtype, serializer = robj.get_serializer(content_type)
|
||||
self.assertEqual(mtype, serializer)
|
||||
|
||||
def test_get_serializer_defaults(self):
|
||||
robj = wsgi.ResponseObject({})
|
||||
default_serializers = dict(json='json', xml='xml', atom='atom')
|
||||
for content_type, mtype in wsgi._MEDIA_TYPE_MAP.items():
|
||||
self.assertRaises(exception.InvalidContentType,
|
||||
robj.get_serializer, content_type)
|
||||
_mtype, serializer = robj.get_serializer(content_type,
|
||||
default_serializers)
|
||||
self.assertEqual(mtype, serializer)
|
||||
|
||||
def test_serialize(self):
|
||||
class JSONSerializer(object):
|
||||
def serialize(self, obj):
|
||||
return 'json'
|
||||
|
||||
class XMLSerializer(object):
|
||||
def serialize(self, obj):
|
||||
return 'xml'
|
||||
|
||||
class AtomSerializer(object):
|
||||
def serialize(self, obj):
|
||||
return 'atom'
|
||||
|
||||
robj = wsgi.ResponseObject({}, code=202,
|
||||
json=JSONSerializer,
|
||||
xml=XMLSerializer,
|
||||
atom=AtomSerializer)
|
||||
robj['X-header1'] = 'header1'
|
||||
robj['X-header2'] = 'header2'
|
||||
|
||||
for content_type, mtype in wsgi._MEDIA_TYPE_MAP.items():
|
||||
request = wsgi.Request.blank('/tests/123')
|
||||
response = robj.serialize(request, content_type)
|
||||
|
||||
self.assertEqual(content_type, response.headers['Content-Type'])
|
||||
self.assertEqual('header1', response.headers['X-header1'])
|
||||
self.assertEqual('header2', response.headers['X-header2'])
|
||||
self.assertEqual(202, response.status_int)
|
||||
self.assertEqual(mtype, response.body.decode('utf-8'))
|
||||
|
||||
|
||||
class ValidBodyTest(test.TestCase):
|
||||
|
||||
|
@ -1,734 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# 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 lxml import etree
|
||||
|
||||
from cinder.api import xmlutil
|
||||
from cinder 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(0, len(sel.chain))
|
||||
self.assertEqual(self.obj_for_test, sel(self.obj_for_test))
|
||||
|
||||
def test_dict_selector(self):
|
||||
sel = xmlutil.Selector('test')
|
||||
self.assertEqual(1, len(sel.chain))
|
||||
self.assertEqual('test', sel.chain[0])
|
||||
self.assertEqual(self.obj_for_test['test'],
|
||||
sel(self.obj_for_test))
|
||||
|
||||
def test_datum_selector(self):
|
||||
sel = xmlutil.Selector('test', 'name')
|
||||
self.assertEqual(2, len(sel.chain))
|
||||
self.assertEqual('test', sel.chain[0])
|
||||
self.assertEqual('name', sel.chain[1])
|
||||
self.assertEqual('test', sel(self.obj_for_test))
|
||||
|
||||
def test_list_selector(self):
|
||||
sel = xmlutil.Selector('test', 'values', 0)
|
||||
self.assertEqual(3, len(sel.chain))
|
||||
self.assertEqual('test', sel.chain[0])
|
||||
self.assertEqual('values', sel.chain[1])
|
||||
self.assertEqual(0, sel.chain[2])
|
||||
self.assertEqual(1, sel(self.obj_for_test))
|
||||
|
||||
def test_items_selector(self):
|
||||
sel = xmlutil.Selector('test', 'attrs', xmlutil.get_items)
|
||||
self.assertEqual(3, len(sel.chain))
|
||||
self.assertEqual(xmlutil.get_items, sel.chain[2])
|
||||
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.assertIsNone(sel(self.obj_for_test))
|
||||
self.assertRaises(KeyError, sel, self.obj_for_test, True)
|
||||
|
||||
def test_constant_selector(self):
|
||||
sel = xmlutil.ConstantSelector('Foobar')
|
||||
self.assertEqual('Foobar', sel.value)
|
||||
self.assertEqual('Foobar', sel(self.obj_for_test))
|
||||
|
||||
|
||||
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(v, elem.attrib[k].chain[0])
|
||||
|
||||
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(v, elem.get(k).chain[0])
|
||||
|
||||
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(1, len(elem.attrib['a'].chain))
|
||||
self.assertEqual('a', elem.attrib['a'].chain[0])
|
||||
self.assertEqual(1, len(elem.attrib['b'].chain))
|
||||
self.assertEqual('foo', elem.attrib['b'].chain[0])
|
||||
self.assertEqual(attrs['c'], elem.attrib['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(expected, set(elem.keys()))
|
||||
|
||||
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(0, len(keys))
|
||||
|
||||
def test_element_selector_none(self):
|
||||
# Create a template element with no selector
|
||||
elem = xmlutil.TemplateElement('test')
|
||||
|
||||
self.assertEqual(0, len(elem.selector.chain))
|
||||
|
||||
def test_element_selector_string(self):
|
||||
# Create a template element with a string selector
|
||||
elem = xmlutil.TemplateElement('test', selector='test')
|
||||
|
||||
self.assertEqual(1, len(elem.selector.chain))
|
||||
self.assertEqual('test', elem.selector.chain[0])
|
||||
|
||||
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(sel, elem.selector)
|
||||
|
||||
def test_element_subselector_none(self):
|
||||
# Create a template element with no subselector
|
||||
elem = xmlutil.TemplateElement('test')
|
||||
|
||||
self.assertIsNone(elem.subselector)
|
||||
|
||||
def test_element_subselector_string(self):
|
||||
# Create a template element with a string subselector
|
||||
elem = xmlutil.TemplateElement('test', subselector='test')
|
||||
|
||||
self.assertEqual(1, len(elem.subselector.chain))
|
||||
self.assertEqual('test', elem.subselector.chain[0])
|
||||
|
||||
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(sel, elem.subselector)
|
||||
|
||||
def test_element_append_child(self):
|
||||
# Create an element
|
||||
elem = xmlutil.TemplateElement('test')
|
||||
|
||||
# Make sure the element starts off empty
|
||||
self.assertEqual(0, len(elem))
|
||||
|
||||
# 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(1, len(elem))
|
||||
self.assertEqual(child, elem[0])
|
||||
self.assertIn('child', elem)
|
||||
self.assertEqual(child, elem['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(0, len(elem))
|
||||
|
||||
# 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(3, len(elem))
|
||||
for idx in range(len(elem)):
|
||||
self.assertEqual(children[idx], elem[idx])
|
||||
self.assertIn(children[idx].tag, elem)
|
||||
self.assertEqual(children[idx], elem[children[idx].tag])
|
||||
|
||||
# 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(3, len(elem))
|
||||
self.assertEqual('child3', elem[-1].tag)
|
||||
|
||||
def test_element_insert_child(self):
|
||||
# Create an element
|
||||
elem = xmlutil.TemplateElement('test')
|
||||
|
||||
# Make sure the element starts off empty
|
||||
self.assertEqual(0, len(elem))
|
||||
|
||||
# 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(4, len(elem))
|
||||
children.insert(1, child)
|
||||
for idx in range(len(elem)):
|
||||
self.assertEqual(children[idx], elem[idx])
|
||||
self.assertIn(children[idx].tag, elem)
|
||||
self.assertEqual(children[idx], elem[children[idx].tag])
|
||||
|
||||
# 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(0, len(elem))
|
||||
|
||||
# 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(3, len(elem))
|
||||
|
||||
# Now remove a legitimate child
|
||||
elem.remove(children[1])
|
||||
|
||||
# Ensure that the child was removed
|
||||
self.assertEqual(2, len(elem))
|
||||
self.assertEqual(children[0], elem[0])
|
||||
self.assertEqual(children[2], elem[1])
|
||||
self.assertNotIn('child2', elem)
|
||||
|
||||
# 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.assertIsNone(elem.text)
|
||||
|
||||
# Try setting it to a string and ensure it becomes a selector
|
||||
elem.text = 'test'
|
||||
self.assertTrue(hasattr(elem.text, 'chain'))
|
||||
self.assertEqual(1, len(elem.text.chain))
|
||||
self.assertEqual('test', elem.text.chain[0])
|
||||
|
||||
# Try resetting the text to None
|
||||
elem.text = None
|
||||
self.assertIsNone(elem.text)
|
||||
|
||||
# Now make up a selector and try setting the text to that
|
||||
sel = xmlutil.Selector()
|
||||
elem.text = sel
|
||||
self.assertEqual(sel, elem.text)
|
||||
|
||||
# Finally, try deleting the text and see what happens
|
||||
del elem.text
|
||||
self.assertIsNone(elem.text)
|
||||
|
||||
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('test', elem.tag)
|
||||
self.assertEqual(0, len(elem.nsmap))
|
||||
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(1, len(parent))
|
||||
self.assertEqual(parent[0], elem)
|
||||
self.assertEqual(1, len(elem.nsmap))
|
||||
self.assertEqual('foo', elem.nsmap['a'])
|
||||
|
||||
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(0, len(elems))
|
||||
|
||||
# Try a render with one object
|
||||
elems = tmpl_elem.render(None, 'foo')
|
||||
self.assertEqual(1, len(elems))
|
||||
self.assertEqual('foo', elems[0][0].text)
|
||||
self.assertEqual('foo', elems[0][1])
|
||||
|
||||
# Now, try rendering an object with multiple entries
|
||||
parent = etree.Element('parent')
|
||||
elems = tmpl_elem.render(parent, obj)
|
||||
self.assertEqual(4, len(elems))
|
||||
|
||||
# Check the results
|
||||
for idx in range(len(obj)):
|
||||
self.assertEqual(obj[idx], elems[idx][0].text)
|
||||
self.assertEqual(obj[idx], elems[idx][1])
|
||||
|
||||
def test_subelement(self):
|
||||
# Try the SubTemplateElement constructor
|
||||
parent = xmlutil.SubTemplateElement(None, 'parent')
|
||||
self.assertEqual('parent', parent.tag)
|
||||
self.assertEqual(0, len(parent))
|
||||
|
||||
# Now try it with a parent element
|
||||
child = xmlutil.SubTemplateElement(parent, 'child')
|
||||
self.assertEqual('child', child.tag)
|
||||
self.assertEqual(1, len(parent))
|
||||
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, elem.unwrap())
|
||||
self.assertEqual(elem, elem.wrap().root)
|
||||
|
||||
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(obj), len(elems))
|
||||
for idx in range(len(obj)):
|
||||
self.assertEqual(obj[idx], elems[idx][0].tag)
|
||||
|
||||
|
||||
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(elem, tmpl.unwrap())
|
||||
self.assertEqual(tmpl, tmpl.wrap())
|
||||
|
||||
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(1, len(siblings))
|
||||
self.assertEqual(elem, siblings[0])
|
||||
|
||||
def test__splitTagName(self):
|
||||
test_cases = [
|
||||
('a', ['a']),
|
||||
('a:b', ['a', 'b']),
|
||||
('{http://test.com}a:b', ['{http://test.com}a', 'b']),
|
||||
('a:b{http://test.com}:c', ['a', 'b{http://test.com}', 'c']),
|
||||
]
|
||||
|
||||
for test_case, expected in test_cases:
|
||||
result = xmlutil.TemplateElement._splitTagName(test_case)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
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(1, len(nsmap))
|
||||
self.assertEqual('foo', nsmap['a'])
|
||||
|
||||
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(elem, tmpl.root)
|
||||
self.assertEqual(0, len(tmpl.slaves))
|
||||
|
||||
# Try to attach an invalid slave
|
||||
bad_elem = xmlutil.TemplateElement('test2')
|
||||
self.assertRaises(ValueError, tmpl.attach, bad_elem)
|
||||
self.assertEqual(0, len(tmpl.slaves))
|
||||
|
||||
# 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(0, len(tmpl.slaves))
|
||||
|
||||
# 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(0, len(tmpl.slaves))
|
||||
|
||||
# Now try attaching an applicable template
|
||||
tmpl.attach(good_elem)
|
||||
self.assertEqual(1, len(tmpl.slaves))
|
||||
self.assertEqual(good_elem, tmpl.slaves[0].root)
|
||||
|
||||
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.assertTrue(slave.apply(master))
|
||||
|
||||
# Construct a slave template with equal minimum version
|
||||
slave = xmlutil.SlaveTemplate(elem, 3)
|
||||
self.assertTrue(slave.apply(master))
|
||||
|
||||
# Construct a slave template with inapplicable minimum version
|
||||
slave = xmlutil.SlaveTemplate(elem, 4)
|
||||
self.assertFalse(slave.apply(master))
|
||||
|
||||
# Construct a slave template with applicable version range
|
||||
slave = xmlutil.SlaveTemplate(elem, 2, 4)
|
||||
self.assertTrue(slave.apply(master))
|
||||
|
||||
# Construct a slave template with low version range
|
||||
slave = xmlutil.SlaveTemplate(elem, 1, 2)
|
||||
self.assertFalse(slave.apply(master))
|
||||
|
||||
# Construct a slave template with high version range
|
||||
slave = xmlutil.SlaveTemplate(elem, 4, 5)
|
||||
self.assertFalse(slave.apply(master))
|
||||
|
||||
# Construct a slave template with matching version range
|
||||
slave = xmlutil.SlaveTemplate(elem, 3, 3)
|
||||
self.assertTrue(slave.apply(master))
|
||||
|
||||
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('test', result.tag)
|
||||
self.assertEqual(2, len(result.nsmap))
|
||||
self.assertEqual('foo', result.nsmap['f'])
|
||||
self.assertEqual('bar', result.nsmap['b'])
|
||||
self.assertEqual(result.get('name'), obj['test']['name'])
|
||||
for idx, val in enumerate(obj['test']['values']):
|
||||
self.assertEqual('value', result[idx].tag)
|
||||
self.assertEqual(str(val), result[idx].text)
|
||||
idx += 1
|
||||
self.assertEqual('attrs', result[idx].tag)
|
||||
for attr in result[idx]:
|
||||
self.assertEqual('attr', attr.tag)
|
||||
self.assertEqual(str(obj['test']['attrs'][attr.get('key')]),
|
||||
attr.get('value'))
|
||||
idx += 1
|
||||
self.assertEqual('image', result[idx].tag)
|
||||
self.assertEqual(str(obj['test']['image']['id']),
|
||||
result[idx].get('id'))
|
||||
self.assertEqual(obj['test']['image']['name'], result[idx].text)
|
||||
|
||||
def test_serialize_with_delimiter(self):
|
||||
# Our test object to serialize
|
||||
obj = {'test': {'scope0:key1': 'Value1',
|
||||
'scope0:scope1:key2': 'Value2',
|
||||
'scope0:scope1:scope2:key3': 'Value3'
|
||||
}}
|
||||
|
||||
# Set up our master template
|
||||
root = xmlutil.TemplateElement('test', selector='test')
|
||||
key1 = xmlutil.SubTemplateElement(root, 'scope0:key1',
|
||||
selector='scope0:key1')
|
||||
key1.text = xmlutil.Selector()
|
||||
key2 = xmlutil.SubTemplateElement(root, 'scope0:scope1:key2',
|
||||
selector='scope0:scope1:key2')
|
||||
key2.text = xmlutil.Selector()
|
||||
key3 = xmlutil.SubTemplateElement(root, 'scope0:scope1:scope2:key3',
|
||||
selector='scope0:scope1:scope2:key3')
|
||||
key3.text = xmlutil.Selector()
|
||||
serializer = xmlutil.MasterTemplate(root, 1)
|
||||
expected_xml = (b"<?xmlversion='1.0'encoding='UTF-8'?><test>"
|
||||
b"<scope0><key1>Value1</key1><scope1>"
|
||||
b"<key2>Value2</key2><scope2><key3>Value3</key3>"
|
||||
b"</scope2></scope1></scope0></test>")
|
||||
result = serializer.serialize(obj)
|
||||
result = result.replace(b'\n', b'').replace(b' ', b'')
|
||||
self.assertEqual(expected_xml, result)
|
||||
|
||||
|
||||
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.assertIsNone(MasterTemplateBuilder._tmpl)
|
||||
|
||||
# Now, construct the template
|
||||
tmpl1 = MasterTemplateBuilder()
|
||||
|
||||
# Make sure that there is a template cached...
|
||||
self.assertIsNotNone(MasterTemplateBuilder._tmpl)
|
||||
|
||||
# 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.assertIsNone(SlaveTemplateBuilder._tmpl)
|
||||
|
||||
# Now, construct the template
|
||||
tmpl1 = SlaveTemplateBuilder()
|
||||
|
||||
# Make sure there is a template cached...
|
||||
self.assertIsNotNone(SlaveTemplateBuilder._tmpl)
|
||||
|
||||
# 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_xml = (b"<?xml version='1.0' encoding='UTF-8'?>\n"
|
||||
b'<wrapper><a>foo</a></wrapper>')
|
||||
root = xmlutil.make_flat_dict('wrapper')
|
||||
tmpl = xmlutil.MasterTemplate(root, 1)
|
||||
result = tmpl.serialize(dict(wrapper=dict(a='foo')))
|
||||
self.assertEqual(expected_xml, result)
|
@ -17,9 +17,6 @@
|
||||
Tests dealing with HTTP rate-limiting.
|
||||
"""
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
from lxml import etree
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
from six.moves import http_client
|
||||
@ -28,7 +25,6 @@ import webob
|
||||
|
||||
from cinder.api.v1 import limits
|
||||
from cinder.api import views
|
||||
from cinder.api import xmlutil
|
||||
import cinder.context
|
||||
from cinder import test
|
||||
from cinder.tests.unit import fake_constants as fake
|
||||
@ -276,26 +272,6 @@ class LimitMiddlewareTest(BaseLimitTestSuite):
|
||||
value = body["overLimitFault"]["details"].strip()
|
||||
self.assertEqual(expected, value)
|
||||
|
||||
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(413, response.status_int)
|
||||
|
||||
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(1, details.length)
|
||||
|
||||
value = details.item(0).firstChild.data.strip()
|
||||
self.assertEqual(expected, value)
|
||||
|
||||
|
||||
class LimitTest(BaseLimitTestSuite):
|
||||
"""Tests for the `limits.Limit` class."""
|
||||
@ -820,89 +796,3 @@ class LimitsViewBuilderTest(test.TestCase):
|
||||
rate_limits = []
|
||||
output = self.view_builder.build(rate_limits, abs_limits)
|
||||
self.assertDictMatch(expected_limits, output)
|
||||
|
||||
|
||||
class LimitsXMLSerializationTest(test.TestCase):
|
||||
def test_xml_declaration(self):
|
||||
serializer = limits.LimitsTemplate()
|
||||
|
||||
fixture = {"limits": {
|
||||
"rate": [],
|
||||
"absolute": {}}}
|
||||
|
||||
output = serializer.serialize(fixture)
|
||||
has_dec = output.startswith(b"<?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(4, len(absolutes))
|
||||
for limit in absolutes:
|
||||
name = limit.get('name')
|
||||
value = limit.get('value')
|
||||
self.assertEqual(str(fixture['limits']['absolute'][name]), value)
|
||||
|
||||
# verify rate limits
|
||||
rates = root.xpath('ns:rates/ns:rate', namespaces=NS)
|
||||
self.assertEqual(2, len(rates))
|
||||
for i, rate in enumerate(rates):
|
||||
for key in ['uri', 'regex']:
|
||||
self.assertEqual(str(fixture['limits']['rate'][i][key]),
|
||||
rate.get(key))
|
||||
rate_limits = rate.xpath('ns:limit', namespaces=NS)
|
||||
self.assertEqual(1, len(rate_limits))
|
||||
for j, limit in enumerate(rate_limits):
|
||||
for key in ['verb', 'value', 'remaining', 'unit',
|
||||
'next-available']:
|
||||
self.assertEqual(
|
||||
str(fixture['limits']['rate'][i]['limit'][j][key]),
|
||||
limit.get(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(0, len(absolutes))
|
||||
|
||||
# verify rate limits
|
||||
rates = root.xpath('ns:rates/ns:rate', namespaces=NS)
|
||||
self.assertEqual(0, len(rates))
|
||||
|
@ -13,9 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from cinder.api.v1 import snapshots
|
||||
@ -379,56 +377,6 @@ class SnapshotApiTest(test.TestCase):
|
||||
self.assertEqual(1, len(res['snapshots']))
|
||||
|
||||
|
||||
class SnapshotSerializerTest(test.TestCase):
|
||||
def _verify_snapshot(self, snap, tree):
|
||||
self.assertEqual('snapshot', tree.tag)
|
||||
|
||||
for attr in ('id', 'status', 'size', 'created_at',
|
||||
'display_name', 'display_description', 'volume_id'):
|
||||
self.assertEqual(str(snap[attr]), tree.get(attr))
|
||||
|
||||
def test_snapshot_show_create_serializer(self):
|
||||
serializer = snapshots.SnapshotTemplate()
|
||||
raw_snapshot = dict(
|
||||
id='snap_id',
|
||||
status='snap_status',
|
||||
size=1024,
|
||||
created_at=timeutils.utcnow(),
|
||||
display_name='snap_name',
|
||||
display_description='snap_desc',
|
||||
volume_id='vol_id', )
|
||||
text = serializer.serialize(dict(snapshot=raw_snapshot))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self._verify_snapshot(raw_snapshot, tree)
|
||||
|
||||
def test_snapshot_index_detail_serializer(self):
|
||||
serializer = snapshots.SnapshotsTemplate()
|
||||
raw_snapshots = [dict(id='snap1_id',
|
||||
status='snap1_status',
|
||||
size=1024,
|
||||
created_at=timeutils.utcnow(),
|
||||
display_name='snap1_name',
|
||||
display_description='snap1_desc',
|
||||
volume_id='vol1_id', ),
|
||||
dict(id='snap2_id',
|
||||
status='snap2_status',
|
||||
size=1024,
|
||||
created_at=timeutils.utcnow(),
|
||||
display_name='snap2_name',
|
||||
display_description='snap2_desc',
|
||||
volume_id='vol2_id', )]
|
||||
text = serializer.serialize(dict(snapshots=raw_snapshots))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('snapshots', tree.tag)
|
||||
self.assertEqual(len(raw_snapshots), len(tree))
|
||||
for idx, child in enumerate(tree):
|
||||
self._verify_snapshot(raw_snapshots[idx], child)
|
||||
|
||||
|
||||
class SnapshotsUnprocessableEntityTestCase(test.TestCase):
|
||||
|
||||
"""Tests of places we throw 422 Unprocessable Entity."""
|
||||
|
@ -15,7 +15,6 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
@ -166,45 +165,3 @@ class VolumeTypesApiTest(test.TestCase):
|
||||
description=None)
|
||||
self.assertDictMatch(expected_volume_type,
|
||||
output['volume_types'][i])
|
||||
|
||||
|
||||
class VolumeTypesSerializerTest(test.TestCase):
|
||||
def _verify_volume_type(self, vtype, tree):
|
||||
self.assertEqual('volume_type', tree.tag)
|
||||
self.assertEqual(vtype['name'], tree.get('name'))
|
||||
self.assertEqual(vtype['id'], tree.get('id'))
|
||||
self.assertEqual(1, len(tree))
|
||||
extra_specs = tree[0]
|
||||
self.assertEqual('extra_specs', extra_specs.tag)
|
||||
seen = set(vtype['extra_specs'].keys())
|
||||
for child in extra_specs:
|
||||
self.assertIn(child.tag, seen)
|
||||
self.assertEqual(vtype['extra_specs'][child.tag], child.text)
|
||||
seen.remove(child.tag)
|
||||
self.assertEqual(0, len(seen))
|
||||
|
||||
def test_index_serializer(self):
|
||||
serializer = types.VolumeTypesTemplate()
|
||||
|
||||
# Just getting some input data
|
||||
vtypes = return_volume_types_get_all_types(None)
|
||||
text = serializer.serialize({'volume_types': list(vtypes.values())})
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('volume_types', tree.tag)
|
||||
self.assertEqual(len(vtypes), len(tree))
|
||||
for child in tree:
|
||||
name = child.get('name')
|
||||
self.assertIn(name, vtypes)
|
||||
self._verify_volume_type(vtypes[name], child)
|
||||
|
||||
def test_voltype_serializer(self):
|
||||
serializer = types.VolumeTypeTemplate()
|
||||
|
||||
vtype = stub_volume_type(fake.volume_type_id)
|
||||
text = serializer.serialize(dict(volume_type=vtype))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self._verify_volume_type(vtype, tree)
|
||||
|
@ -16,10 +16,8 @@
|
||||
import datetime
|
||||
import iso8601
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
@ -861,285 +859,6 @@ class VolumeApiTest(test.TestCase):
|
||||
marker=None)
|
||||
|
||||
|
||||
class VolumeSerializerTest(test.TestCase):
|
||||
def _verify_volume_attachment(self, attach, tree):
|
||||
for attr in ('id', 'volume_id', 'server_id', 'device'):
|
||||
self.assertEqual(str(attach[attr]), tree.get(attr))
|
||||
|
||||
def _verify_volume(self, vol, tree):
|
||||
self.assertEqual(NS + 'volume', tree.tag)
|
||||
|
||||
for attr in ('id', 'status', 'size', 'availability_zone', 'created_at',
|
||||
'display_name', 'display_description', 'volume_type',
|
||||
'bootable', 'snapshot_id'):
|
||||
self.assertEqual(str(vol[attr]), tree.get(attr))
|
||||
|
||||
for child in tree:
|
||||
self.assertIn(child.tag, (NS + 'attachments', NS + 'metadata'))
|
||||
if child.tag == 'attachments':
|
||||
self.assertEqual(1, len(child))
|
||||
self.assertEqual('attachment', child[0].tag)
|
||||
self._verify_volume_attachment(vol['attachments'][0], child[0])
|
||||
elif child.tag == 'metadata':
|
||||
not_seen = set(vol['metadata'].keys())
|
||||
for gr_child in child:
|
||||
self.assertIn(gr_child.get("key"), not_seen)
|
||||
self.assertEqual(str(vol['metadata'][gr_child.get("key")]),
|
||||
gr_child.text)
|
||||
not_seen.remove(gr_child.get('key'))
|
||||
self.assertEqual(0, len(not_seen))
|
||||
|
||||
def test_volume_show_create_serializer(self):
|
||||
serializer = volumes.VolumeTemplate()
|
||||
raw_volume = dict(
|
||||
id=fake.volume_id,
|
||||
status='vol_status',
|
||||
size=1024,
|
||||
availability_zone='vol_availability',
|
||||
bootable='false',
|
||||
created_at=timeutils.utcnow(),
|
||||
attachments=[dict(id=fake.volume_id,
|
||||
volume_id=fake.volume_id,
|
||||
server_id=fake.instance_id,
|
||||
device='/foo')],
|
||||
display_name='vol_name',
|
||||
display_description='vol_desc',
|
||||
volume_type=fake.volume_type_id,
|
||||
snapshot_id=fake.snapshot_id,
|
||||
source_volid=fake.volume2_id,
|
||||
metadata=dict(foo='bar',
|
||||
baz='quux', ), )
|
||||
text = serializer.serialize(dict(volume=raw_volume))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self._verify_volume(raw_volume, tree)
|
||||
|
||||
def test_volume_index_detail_serializer(self):
|
||||
serializer = volumes.VolumesTemplate()
|
||||
raw_volumes = [dict(id=fake.volume_id,
|
||||
status='vol1_status',
|
||||
size=1024,
|
||||
availability_zone='vol1_availability',
|
||||
bootable='true',
|
||||
created_at=timeutils.utcnow(),
|
||||
attachments=[dict(id=fake.attachment_id,
|
||||
volume_id=fake.volume_id,
|
||||
server_id=fake.instance_id,
|
||||
device='/foo1')],
|
||||
display_name='vol1_name',
|
||||
display_description='vol1_desc',
|
||||
volume_type=fake.volume_type_id,
|
||||
snapshot_id=fake.snapshot_id,
|
||||
source_volid=None,
|
||||
metadata=dict(foo='vol1_foo',
|
||||
bar='vol1_bar', ), ),
|
||||
dict(id=fake.volume2_id,
|
||||
status='vol2_status',
|
||||
size=1024,
|
||||
availability_zone='vol2_availability',
|
||||
bootable='true',
|
||||
created_at=timeutils.utcnow(),
|
||||
attachments=[dict(id=fake.attachment2_id,
|
||||
volume_id=fake.volume2_id,
|
||||
server_id=fake.instance_id,
|
||||
device='/foo2')],
|
||||
display_name='vol2_name',
|
||||
display_description='vol2_desc',
|
||||
volume_type=fake.volume_type2_id,
|
||||
snapshot_id=fake.snapshot2_id,
|
||||
source_volid=None,
|
||||
metadata=dict(foo='vol2_foo',
|
||||
bar='vol2_bar', ), )]
|
||||
text = serializer.serialize(dict(volumes=raw_volumes))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual(NS + 'volumes', tree.tag)
|
||||
self.assertEqual(len(raw_volumes), len(tree))
|
||||
for idx, child in enumerate(tree):
|
||||
self._verify_volume(raw_volumes[idx], child)
|
||||
|
||||
|
||||
class TestVolumeCreateRequestXMLDeserializer(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVolumeCreateRequestXMLDeserializer, self).setUp()
|
||||
self.deserializer = volumes.CreateDeserializer()
|
||||
|
||||
def test_minimal_volume(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/compute/api/v1.1"
|
||||
size="1"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {"volume": {"size": "1", }, }
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_display_name(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/compute/api/v1.1"
|
||||
size="1"
|
||||
display_name="Volume-xml"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"display_name": "Volume-xml",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_display_description(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/compute/api/v1.1"
|
||||
size="1"
|
||||
display_name="Volume-xml"
|
||||
display_description="description"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"display_name": "Volume-xml",
|
||||
"display_description": "description",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_volume_type(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/compute/api/v1.1"
|
||||
size="1"
|
||||
display_name="Volume-xml"
|
||||
display_description="description"
|
||||
volume_type="289da7f8-6440-407c-9fb4-7db01ec49164"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"display_name": "Volume-xml",
|
||||
"display_description": "description",
|
||||
"volume_type": "289da7f8-6440-407c-9fb4-7db01ec49164",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_availability_zone(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/compute/api/v1.1"
|
||||
size="1"
|
||||
display_name="Volume-xml"
|
||||
display_description="description"
|
||||
volume_type="289da7f8-6440-407c-9fb4-7db01ec49164"
|
||||
availability_zone="us-east1"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"display_name": "Volume-xml",
|
||||
"display_description": "description",
|
||||
"volume_type": "289da7f8-6440-407c-9fb4-7db01ec49164",
|
||||
"availability_zone": "us-east1",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_metadata(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/compute/api/v1.1"
|
||||
display_name="Volume-xml"
|
||||
size="1">
|
||||
<metadata><meta key="Type">work</meta></metadata></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"display_name": "Volume-xml",
|
||||
"size": "1",
|
||||
"metadata": {
|
||||
"Type": "work",
|
||||
},
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_full_volume(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/compute/api/v1.1"
|
||||
size="1"
|
||||
display_name="Volume-xml"
|
||||
display_description="description"
|
||||
volume_type="289da7f8-6440-407c-9fb4-7db01ec49164"
|
||||
availability_zone="us-east1">
|
||||
<metadata><meta key="Type">work</meta></metadata></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"display_name": "Volume-xml",
|
||||
"display_description": "description",
|
||||
"volume_type": "289da7f8-6440-407c-9fb4-7db01ec49164",
|
||||
"availability_zone": "us-east1",
|
||||
"metadata": {
|
||||
"Type": "work",
|
||||
},
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_imageref(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/volume/api/v1"
|
||||
size="1"
|
||||
display_name="Volume-xml"
|
||||
display_description="description"
|
||||
imageRef="4a90189d-d702-4c7c-87fc-6608c554d737"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"display_name": "Volume-xml",
|
||||
"display_description": "description",
|
||||
"imageRef": "4a90189d-d702-4c7c-87fc-6608c554d737",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_snapshot_id(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/volume/api/v1"
|
||||
size="1"
|
||||
display_name="Volume-xml"
|
||||
display_description="description"
|
||||
snapshot_id="4a90189d-d702-4c7c-87fc-6608c554d737"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"display_name": "Volume-xml",
|
||||
"display_description": "description",
|
||||
"snapshot_id": "4a90189d-d702-4c7c-87fc-6608c554d737",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_source_volid(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/volume/api/v1"
|
||||
size="1"
|
||||
display_name="Volume-xml"
|
||||
display_description="description"
|
||||
source_volid="4a90189d-d702-4c7c-87fc-6608c554d737"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"display_name": "Volume-xml",
|
||||
"display_description": "description",
|
||||
"source_volid": "4a90189d-d702-4c7c-87fc-6608c554d737",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
|
||||
class VolumesUnprocessableEntityTestCase(test.TestCase):
|
||||
|
||||
"""Tests of places we throw 422 Unprocessable Entity from."""
|
||||
|
@ -17,9 +17,6 @@
|
||||
Tests dealing with HTTP rate-limiting.
|
||||
"""
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
from lxml import etree
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
from six.moves import http_client
|
||||
@ -28,7 +25,6 @@ import webob
|
||||
|
||||
from cinder.api.v2 import limits
|
||||
from cinder.api import views
|
||||
from cinder.api import xmlutil
|
||||
import cinder.context
|
||||
from cinder import test
|
||||
|
||||
@ -277,26 +273,6 @@ class LimitMiddlewareTest(BaseLimitTestSuite):
|
||||
value = body["overLimitFault"]["details"].strip()
|
||||
self.assertEqual(expected, value)
|
||||
|
||||
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(413, response.status_int)
|
||||
|
||||
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(1, details.length)
|
||||
|
||||
value = details.item(0).firstChild.data.strip()
|
||||
self.assertEqual(expected, value)
|
||||
|
||||
|
||||
class LimitTest(BaseLimitTestSuite):
|
||||
|
||||
@ -826,85 +802,3 @@ class LimitsViewBuilderTest(test.TestCase):
|
||||
rate_limits = []
|
||||
output = self.view_builder.build(rate_limits, abs_limits)
|
||||
self.assertDictMatch(expected_limits, output)
|
||||
|
||||
|
||||
class LimitsXMLSerializationTest(test.TestCase):
|
||||
def test_xml_declaration(self):
|
||||
serializer = limits.LimitsTemplate()
|
||||
|
||||
fixture = {"limits": {
|
||||
"rate": [],
|
||||
"absolute": {}}}
|
||||
|
||||
output = serializer.serialize(fixture)
|
||||
has_dec = output.startswith(b"<?xml version='1.0' encoding='UTF-8'?>")
|
||||
self.assertTrue(has_dec)
|
||||
|
||||
def test_index(self):
|
||||
tdate = "2011-12-15T22:42:45Z"
|
||||
serializer = limits.LimitsTemplate()
|
||||
fixture = {"limits": {"rate": [{"uri": "*",
|
||||
"regex": ".*",
|
||||
"limit": [{"value": 10,
|
||||
"verb": "POST",
|
||||
"remaining": 2,
|
||||
"unit": "MINUTE",
|
||||
"next-available": tdate}]},
|
||||
{"uri": "*/servers",
|
||||
"regex": "^/servers",
|
||||
"limit": [{"value": 50,
|
||||
"verb": "POST",
|
||||
"remaining": 10,
|
||||
"unit": "DAY",
|
||||
"next-available": tdate}]}],
|
||||
"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(4, len(absolutes))
|
||||
for limit in absolutes:
|
||||
name = limit.get('name')
|
||||
value = limit.get('value')
|
||||
self.assertEqual(str(fixture['limits']['absolute'][name]), value)
|
||||
|
||||
# verify rate limits
|
||||
rates = root.xpath('ns:rates/ns:rate', namespaces=NS)
|
||||
self.assertEqual(2, len(rates))
|
||||
for i, rate in enumerate(rates):
|
||||
for key in ['uri', 'regex']:
|
||||
self.assertEqual(str(fixture['limits']['rate'][i][key]),
|
||||
rate.get(key))
|
||||
rate_limits = rate.xpath('ns:limit', namespaces=NS)
|
||||
self.assertEqual(1, len(rate_limits))
|
||||
for j, limit in enumerate(rate_limits):
|
||||
for key in ['verb', 'value', 'remaining', 'unit',
|
||||
'next-available']:
|
||||
self.assertEqual(
|
||||
str(fixture['limits']['rate'][i]['limit'][j][key]),
|
||||
limit.get(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(0, len(absolutes))
|
||||
|
||||
# verify rate limits
|
||||
rates = root.xpath('ns:rates/ns:rate', namespaces=NS)
|
||||
self.assertEqual(0, len(rates))
|
||||
|
@ -13,10 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import timeutils
|
||||
from six.moves.urllib import parse as urllib
|
||||
import webob
|
||||
|
||||
@ -582,61 +580,3 @@ class SnapshotApiTest(test.TestCase):
|
||||
def test_create_malformed_entity(self):
|
||||
body = {'snapshot': 'string'}
|
||||
self._create_snapshot_bad_body(body=body)
|
||||
|
||||
|
||||
class SnapshotSerializerTest(test.TestCase):
|
||||
def _verify_snapshot(self, snap, tree):
|
||||
self.assertEqual('snapshot', tree.tag)
|
||||
|
||||
for attr in ('id', 'status', 'size', 'created_at',
|
||||
'name', 'description', 'volume_id'):
|
||||
self.assertEqual(str(snap[attr]), tree.get(attr))
|
||||
|
||||
def test_snapshot_show_create_serializer(self):
|
||||
serializer = snapshots.SnapshotTemplate()
|
||||
raw_snapshot = dict(
|
||||
id='snap_id',
|
||||
status='snap_status',
|
||||
size=1024,
|
||||
created_at=timeutils.utcnow(),
|
||||
name='snap_name',
|
||||
description='snap_desc',
|
||||
display_description='snap_desc',
|
||||
volume_id='vol_id',
|
||||
)
|
||||
text = serializer.serialize(dict(snapshot=raw_snapshot))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self._verify_snapshot(raw_snapshot, tree)
|
||||
|
||||
def test_snapshot_index_detail_serializer(self):
|
||||
serializer = snapshots.SnapshotsTemplate()
|
||||
raw_snapshots = [
|
||||
dict(
|
||||
id='snap1_id',
|
||||
status='snap1_status',
|
||||
size=1024,
|
||||
created_at=timeutils.utcnow(),
|
||||
name='snap1_name',
|
||||
description='snap1_desc',
|
||||
volume_id='vol1_id',
|
||||
),
|
||||
dict(
|
||||
id='snap2_id',
|
||||
status='snap2_status',
|
||||
size=1024,
|
||||
created_at=timeutils.utcnow(),
|
||||
name='snap2_name',
|
||||
description='snap2_desc',
|
||||
volume_id='vol2_id',
|
||||
)
|
||||
]
|
||||
text = serializer.serialize(dict(snapshots=raw_snapshots))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('snapshots', tree.tag)
|
||||
self.assertEqual(len(raw_snapshots), len(tree))
|
||||
for idx, child in enumerate(tree):
|
||||
self._verify_snapshot(raw_snapshots[idx], child)
|
||||
|
@ -15,7 +15,6 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
@ -506,46 +505,3 @@ class VolumeTypesApiTest(test.TestCase):
|
||||
)
|
||||
self.assertDictMatch(expected_volume_type,
|
||||
output['volume_types'][i])
|
||||
|
||||
|
||||
class VolumeTypesSerializerTest(test.TestCase):
|
||||
def _verify_volume_type(self, vtype, tree):
|
||||
self.assertEqual('volume_type', tree.tag)
|
||||
self.assertEqual(vtype['name'], tree.get('name'))
|
||||
self.assertEqual(vtype['description'], tree.get('description'))
|
||||
self.assertEqual(str(vtype['id']), tree.get('id'))
|
||||
self.assertEqual(1, len(tree))
|
||||
extra_specs = tree[0]
|
||||
self.assertEqual('extra_specs', extra_specs.tag)
|
||||
seen = set(vtype['extra_specs'].keys())
|
||||
for child in extra_specs:
|
||||
self.assertIn(child.tag, seen)
|
||||
self.assertEqual(vtype['extra_specs'][child.tag], child.text)
|
||||
seen.remove(child.tag)
|
||||
self.assertEqual(0, len(seen))
|
||||
|
||||
def test_index_serializer(self):
|
||||
serializer = types.VolumeTypesTemplate()
|
||||
|
||||
# Just getting some input data
|
||||
vtypes = return_volume_types_get_all_types(None)
|
||||
text = serializer.serialize({'volume_types': list(vtypes.values())})
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual('volume_types', tree.tag)
|
||||
self.assertEqual(len(vtypes), len(tree))
|
||||
for child in tree:
|
||||
name = child.get('name')
|
||||
self.assertIn(name, vtypes)
|
||||
self._verify_volume_type(vtypes[name], child)
|
||||
|
||||
def test_voltype_serializer(self):
|
||||
serializer = types.VolumeTypeTemplate()
|
||||
|
||||
vtype = stub_volume_type(1)
|
||||
text = serializer.serialize(dict(volume_type=vtype))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self._verify_volume_type(vtype, tree)
|
||||
|
@ -17,10 +17,8 @@
|
||||
import datetime
|
||||
import iso8601
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
from six.moves import range
|
||||
from six.moves import urllib
|
||||
@ -1592,300 +1590,3 @@ class VolumeApiTest(test.TestCase):
|
||||
self.override_config('query_volume_filters', filter_list)
|
||||
self.assertEqual(filter_list,
|
||||
self.controller._get_volume_filter_options())
|
||||
|
||||
|
||||
class VolumeSerializerTest(test.TestCase):
|
||||
def _verify_volume_attachment(self, attach, tree):
|
||||
for attr in ('id', 'volume_id', 'server_id', 'device'):
|
||||
self.assertEqual(str(attach[attr]), tree.get(attr))
|
||||
|
||||
def _verify_volume(self, vol, tree):
|
||||
self.assertEqual(NS + 'volume', tree.tag)
|
||||
|
||||
for attr in ('id', 'status', 'size', 'availability_zone', 'created_at',
|
||||
'name', 'description', 'volume_type', 'bootable',
|
||||
'snapshot_id', 'source_volid'):
|
||||
self.assertEqual(str(vol[attr]), tree.get(attr))
|
||||
|
||||
for child in tree:
|
||||
self.assertIn(child.tag, (NS + 'attachments', NS + 'metadata'))
|
||||
if child.tag == 'attachments':
|
||||
self.assertEqual(1, len(child))
|
||||
self.assertEqual('attachment', child[0].tag)
|
||||
self._verify_volume_attachment(vol['attachments'][0], child[0])
|
||||
elif child.tag == 'metadata':
|
||||
not_seen = set(vol['metadata'].keys())
|
||||
for gr_child in child:
|
||||
self.assertIn(gr_child.get("key"), not_seen)
|
||||
self.assertEqual(str(vol['metadata'][gr_child.get("key")]),
|
||||
gr_child.text)
|
||||
not_seen.remove(gr_child.get('key'))
|
||||
self.assertEqual(0, len(not_seen))
|
||||
|
||||
def test_volume_show_create_serializer(self):
|
||||
serializer = volumes.VolumeTemplate()
|
||||
raw_volume = dict(
|
||||
id='vol_id',
|
||||
status='vol_status',
|
||||
size=1024,
|
||||
availability_zone='vol_availability',
|
||||
bootable=False,
|
||||
created_at=timeutils.utcnow(),
|
||||
attachments=[
|
||||
dict(
|
||||
id='vol_id',
|
||||
volume_id='vol_id',
|
||||
server_id='instance_uuid',
|
||||
device='/foo'
|
||||
)
|
||||
],
|
||||
name='vol_name',
|
||||
description='vol_desc',
|
||||
volume_type='vol_type',
|
||||
snapshot_id='snap_id',
|
||||
source_volid='source_volid',
|
||||
metadata=dict(
|
||||
foo='bar',
|
||||
baz='quux',
|
||||
),
|
||||
)
|
||||
text = serializer.serialize(dict(volume=raw_volume))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self._verify_volume(raw_volume, tree)
|
||||
|
||||
def test_volume_index_detail_serializer(self):
|
||||
serializer = volumes.VolumesTemplate()
|
||||
raw_volumes = [
|
||||
dict(
|
||||
id='vol1_id',
|
||||
status='vol1_status',
|
||||
size=1024,
|
||||
availability_zone='vol1_availability',
|
||||
bootable=True,
|
||||
created_at=timeutils.utcnow(),
|
||||
attachments=[
|
||||
dict(
|
||||
id='vol1_id',
|
||||
volume_id='vol1_id',
|
||||
server_id='instance_uuid',
|
||||
device='/foo1'
|
||||
)
|
||||
],
|
||||
name='vol1_name',
|
||||
description='vol1_desc',
|
||||
volume_type='vol1_type',
|
||||
snapshot_id='snap1_id',
|
||||
source_volid=None,
|
||||
metadata=dict(foo='vol1_foo',
|
||||
bar='vol1_bar', ), ),
|
||||
dict(
|
||||
id='vol2_id',
|
||||
status='vol2_status',
|
||||
size=1024,
|
||||
availability_zone='vol2_availability',
|
||||
bootable=False,
|
||||
created_at=timeutils.utcnow(),
|
||||
attachments=[dict(id='vol2_id',
|
||||
volume_id='vol2_id',
|
||||
server_id='instance_uuid',
|
||||
device='/foo2')],
|
||||
name='vol2_name',
|
||||
description='vol2_desc',
|
||||
volume_type='vol2_type',
|
||||
snapshot_id='snap2_id',
|
||||
source_volid=None,
|
||||
metadata=dict(foo='vol2_foo',
|
||||
bar='vol2_bar', ), )]
|
||||
text = serializer.serialize(dict(volumes=raw_volumes))
|
||||
|
||||
tree = etree.fromstring(text)
|
||||
|
||||
self.assertEqual(NS + 'volumes', tree.tag)
|
||||
self.assertEqual(len(raw_volumes), len(tree))
|
||||
for idx, child in enumerate(tree):
|
||||
self._verify_volume(raw_volumes[idx], child)
|
||||
|
||||
|
||||
class TestVolumeCreateRequestXMLDeserializer(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVolumeCreateRequestXMLDeserializer, self).setUp()
|
||||
self.deserializer = volumes.CreateDeserializer()
|
||||
|
||||
def test_minimal_volume(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_name(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"
|
||||
name="Volume-xml"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"name": "Volume-xml",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_description(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"
|
||||
name="Volume-xml"
|
||||
description="description"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"name": "Volume-xml",
|
||||
"description": "description",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_volume_type(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"
|
||||
name="Volume-xml"
|
||||
description="description"
|
||||
volume_type="289da7f8-6440-407c-9fb4-7db01ec49164"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"name": "Volume-xml",
|
||||
"description": "description",
|
||||
"volume_type": "289da7f8-6440-407c-9fb4-7db01ec49164",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_availability_zone(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"
|
||||
name="Volume-xml"
|
||||
description="description"
|
||||
volume_type="289da7f8-6440-407c-9fb4-7db01ec49164"
|
||||
availability_zone="us-east1"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"name": "Volume-xml",
|
||||
"description": "description",
|
||||
"volume_type": "289da7f8-6440-407c-9fb4-7db01ec49164",
|
||||
"availability_zone": "us-east1",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_metadata(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
name="Volume-xml"
|
||||
size="1">
|
||||
<metadata><meta key="Type">work</meta></metadata></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"name": "Volume-xml",
|
||||
"size": "1",
|
||||
"metadata": {
|
||||
"Type": "work",
|
||||
},
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_full_volume(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"
|
||||
name="Volume-xml"
|
||||
description="description"
|
||||
volume_type="289da7f8-6440-407c-9fb4-7db01ec49164"
|
||||
availability_zone="us-east1">
|
||||
<metadata><meta key="Type">work</meta></metadata></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"name": "Volume-xml",
|
||||
"description": "description",
|
||||
"volume_type": "289da7f8-6440-407c-9fb4-7db01ec49164",
|
||||
"availability_zone": "us-east1",
|
||||
"metadata": {
|
||||
"Type": "work",
|
||||
},
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_imageref(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"
|
||||
name="Volume-xml"
|
||||
description="description"
|
||||
imageRef="4a90189d-d702-4c7c-87fc-6608c554d737"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"name": "Volume-xml",
|
||||
"description": "description",
|
||||
"imageRef": "4a90189d-d702-4c7c-87fc-6608c554d737",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_snapshot_id(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"
|
||||
name="Volume-xml"
|
||||
description="description"
|
||||
snapshot_id="4a90189d-d702-4c7c-87fc-6608c554d737"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"name": "Volume-xml",
|
||||
"description": "description",
|
||||
"snapshot_id": "4a90189d-d702-4c7c-87fc-6608c554d737",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
||||
def test_source_volid(self):
|
||||
self_request = """
|
||||
<volume xmlns="http://docs.openstack.org/api/openstack-volume/2.0/content"
|
||||
size="1"
|
||||
name="Volume-xml"
|
||||
description="description"
|
||||
source_volid="4a90189d-d702-4c7c-87fc-6608c554d737"></volume>"""
|
||||
request = self.deserializer.deserialize(self_request)
|
||||
expected = {
|
||||
"volume": {
|
||||
"size": "1",
|
||||
"name": "Volume-xml",
|
||||
"description": "description",
|
||||
"source_volid": "4a90189d-d702-4c7c-87fc-6608c554d737",
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, request['body'])
|
||||
|
@ -48,10 +48,6 @@ FAKE_VERSIONS = {
|
||||
{
|
||||
"base": "application/json",
|
||||
"type": "application/vnd.openstack.share+json;version=1",
|
||||
},
|
||||
{
|
||||
"base": "application/xml",
|
||||
"type": "application/vnd.openstack.share+xml;version=1",
|
||||
}
|
||||
],
|
||||
},
|
||||
|
@ -57,7 +57,7 @@ class TestAccept(test.TestCase):
|
||||
accept = urlmap.Accept(arg)
|
||||
self.assertEqual(('application/json', {'q': '0.7'}),
|
||||
accept.best_match(['application/json',
|
||||
'application/xml', 'text/html']))
|
||||
'text/html']))
|
||||
|
||||
def test_match_mask_one_asterisk(self):
|
||||
arg = 'text/*; q=0.7'
|
||||
@ -77,14 +77,14 @@ class TestAccept(test.TestCase):
|
||||
self.assertEqual((None, {}), accept.best_match(['text/html']))
|
||||
|
||||
def test_content_type_params(self):
|
||||
arg = "application/xml; q=0.1, application/json; q=0.2," \
|
||||
arg = "application/json; q=0.2," \
|
||||
" text/html; q=0.3"
|
||||
accept = urlmap.Accept(arg)
|
||||
self.assertEqual({'q': '0.2'},
|
||||
accept.content_type_params('application/json'))
|
||||
|
||||
def test_content_type_params_wrong_content_type(self):
|
||||
arg = 'application/xml; q=0.1, text/html; q=0.1'
|
||||
arg = 'text/html; q=0.1'
|
||||
accept = urlmap.Accept(arg)
|
||||
self.assertEqual({}, accept.content_type_params('application/json'))
|
||||
|
||||
@ -142,16 +142,6 @@ class TestURLMap(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestURLMap, self).setUp()
|
||||
self.urlmap = urlmap.URLMap()
|
||||
self.input_environ = {'HTTP_ACCEPT': "application/json;"
|
||||
"version=9.0", 'REQUEST_METHOD': "GET",
|
||||
'CONTENT_TYPE': 'application/xml',
|
||||
'SCRIPT_NAME': '/scriptname',
|
||||
'PATH_INFO': "/resource.xml"}
|
||||
self.environ = {'HTTP_ACCEPT': "application/json;"
|
||||
"version=9.0", 'REQUEST_METHOD': "GET",
|
||||
'CONTENT_TYPE': 'application/xml',
|
||||
'SCRIPT_NAME': '/scriptname/app_url',
|
||||
'PATH_INFO': "/resource.xml"}
|
||||
|
||||
def test_match_with_applications(self):
|
||||
self.urlmap[('http://10.20.30.40:50', '/path/somepath')] = 'app'
|
||||
@ -178,61 +168,11 @@ class TestURLMap(test.TestCase):
|
||||
self.urlmap._match('http://20.30.40.50', '60',
|
||||
'/path/somepath/elsepath'))
|
||||
|
||||
def test_set_script_name(self):
|
||||
app = self.mox.CreateMockAnything()
|
||||
start_response = self.mox.CreateMockAnything()
|
||||
app.__call__(self.environ, start_response).AndReturn('value')
|
||||
self.mox.ReplayAll()
|
||||
wrap = self.urlmap._set_script_name(app, '/app_url')
|
||||
self.assertEqual('value', wrap(self.input_environ, start_response))
|
||||
|
||||
def test_munge_path(self):
|
||||
app = self.mox.CreateMockAnything()
|
||||
start_response = self.mox.CreateMockAnything()
|
||||
app.__call__(self.environ, start_response).AndReturn('value')
|
||||
self.mox.ReplayAll()
|
||||
wrap = self.urlmap._munge_path(app, '/app_url/resource.xml',
|
||||
'/app_url')
|
||||
self.assertEqual('value', wrap(self.input_environ, start_response))
|
||||
|
||||
def test_content_type_strategy_without_version(self):
|
||||
self.assertIsNone(self.urlmap._content_type_strategy('host', 20,
|
||||
self.environ))
|
||||
|
||||
def test_content_type_strategy_with_version(self):
|
||||
environ = {'HTTP_ACCEPT': "application/vnd.openstack.melange+xml;"
|
||||
"version=9.0", 'REQUEST_METHOD': "GET",
|
||||
'PATH_INFO': "/resource.xml",
|
||||
'CONTENT_TYPE': 'application/xml; version=2.0'}
|
||||
self.urlmap[('http://10.20.30.40:50', '/v2.0')] = 'app'
|
||||
self.mox.StubOutWithMock(self.urlmap, '_set_script_name')
|
||||
self.urlmap._set_script_name('app', '/v2.0').AndReturn('value')
|
||||
self.mox.ReplayAll()
|
||||
self.assertEqual('value',
|
||||
self.urlmap._content_type_strategy(
|
||||
'http://10.20.30.40', '50', environ))
|
||||
|
||||
def test_path_strategy_wrong_path_info(self):
|
||||
self.assertEqual((None, None, None),
|
||||
self.urlmap._path_strategy('http://10.20.30.40', '50',
|
||||
'/resource'))
|
||||
|
||||
def test_path_strategy_mime_type_only(self):
|
||||
self.assertEqual(('application/xml', None, None),
|
||||
self.urlmap._path_strategy('http://10.20.30.40', '50',
|
||||
'/resource.xml'))
|
||||
|
||||
def test_path_strategy(self):
|
||||
self.urlmap[('http://10.20.30.40:50', '/path/elsepath/')] = 'app'
|
||||
self.mox.StubOutWithMock(self.urlmap, '_munge_path')
|
||||
self.urlmap._munge_path('app', '/path/elsepath/resource.xml',
|
||||
'/path/elsepath').AndReturn('value')
|
||||
self.mox.ReplayAll()
|
||||
self.assertEqual(
|
||||
('application/xml', 'value', '/path/elsepath'),
|
||||
self.urlmap._path_strategy('http://10.20.30.40', '50',
|
||||
'/path/elsepath/resource.xml'))
|
||||
|
||||
def test_path_strategy_wrong_mime_type(self):
|
||||
self.urlmap[('http://10.20.30.40:50', '/path/elsepath/')] = 'app'
|
||||
self.mox.StubOutWithMock(self.urlmap, '_munge_path')
|
||||
@ -243,29 +183,3 @@ class TestURLMap(test.TestCase):
|
||||
(None, 'value', '/path/elsepath'),
|
||||
self.urlmap._path_strategy('http://10.20.30.40', '50',
|
||||
'/path/elsepath/resource.abc'))
|
||||
|
||||
def test_accept_strategy_version_not_in_params(self):
|
||||
environ = {'HTTP_ACCEPT': "application/xml; q=0.1, application/json; "
|
||||
"q=0.2", 'REQUEST_METHOD': "GET",
|
||||
'PATH_INFO': "/resource.xml",
|
||||
'CONTENT_TYPE': 'application/xml; version=2.0'}
|
||||
self.assertEqual(('application/xml', None),
|
||||
self.urlmap._accept_strategy('http://10.20.30.40',
|
||||
'50',
|
||||
environ,
|
||||
['application/xml']))
|
||||
|
||||
def test_accept_strategy_version(self):
|
||||
environ = {'HTTP_ACCEPT': "application/xml; q=0.1; version=1.0,"
|
||||
"application/json; q=0.2; version=2.0",
|
||||
'REQUEST_METHOD': "GET", 'PATH_INFO': "/resource.xml",
|
||||
'CONTENT_TYPE': 'application/xml; version=2.0'}
|
||||
self.urlmap[('http://10.20.30.40:50', '/v1.0')] = 'app'
|
||||
self.mox.StubOutWithMock(self.urlmap, '_set_script_name')
|
||||
self.urlmap._set_script_name('app', '/v1.0').AndReturn('value')
|
||||
self.mox.ReplayAll()
|
||||
self.assertEqual(('application/xml', 'value'),
|
||||
self.urlmap._accept_strategy('http://10.20.30.40',
|
||||
'50',
|
||||
environ,
|
||||
['application/xml']))
|
||||
|
@ -160,11 +160,6 @@ def check_ssh_injection(cmd_list):
|
||||
raise exception.SSHInjectionThreat(command=cmd_list)
|
||||
|
||||
|
||||
def cinderdir():
|
||||
import cinder
|
||||
return os.path.abspath(cinder.__file__).split('cinder/__init__.py')[0]
|
||||
|
||||
|
||||
def last_completed_audit_period(unit=None):
|
||||
"""This method gives you the most recently *completed* audit period.
|
||||
|
||||
|
5
releasenotes/notes/remove-xml-api-392b41f387e60eb1.yaml
Normal file
5
releasenotes/notes/remove-xml-api-392b41f387e60eb1.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
upgrade:
|
||||
- The XML API has been removed in Newton release.
|
||||
Cinder supports only JSON API request/response format now.
|
||||
|
Loading…
Reference in New Issue
Block a user