Remove XML API
Cinder XML API is not tested by Tempest about for one year. We don't know if it works or not. It was deprecated in Mitaka release. Implements blueprint: remove-xml-api APIImpact DocImpact UpgradeImpact Change-Id: If98db25f1f4032725444c3959acb9aad8f869802
This commit is contained in:
parent
3c04ab1e2a
commit
c042a05ac3
@ -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)
|
||||
|
@ -323,7 +323,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.
|
||||
|
||||
@ -371,7 +268,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.
|
||||
|
||||
@ -135,8 +133,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.
|
||||
|
||||
@ -151,8 +148,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