Merge "Remove XML API"
This commit is contained in:
commit
3ec6a6b791
@ -23,8 +23,6 @@ from oslo_log import log as logging
|
|||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api.openstack import wsgi
|
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder.common import constants
|
from cinder.common import constants
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -59,9 +57,6 @@ CONF.register_opts(api_common_opts)
|
|||||||
LOG = logging.getLogger(__name__)
|
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')
|
METADATA_TYPES = enum.Enum('METADATA_TYPES', 'user image')
|
||||||
|
|
||||||
|
|
||||||
@ -370,75 +365,3 @@ class ViewBuilder(object):
|
|||||||
url_parts[2] = prefix_parts[2] + url_parts[2]
|
url_parts[2] = prefix_parts[2] + url_parts[2]
|
||||||
|
|
||||||
return urllib.parse.urlunsplit(url_parts).rstrip('/')
|
return urllib.parse.urlunsplit(url_parts).rstrip('/')
|
||||||
|
|
||||||
|
|
||||||
class MetadataDeserializer(wsgi.MetadataXMLDeserializer):
|
|
||||||
def deserialize(self, text):
|
|
||||||
dom = utils.safe_minidom_parse_string(text)
|
|
||||||
metadata_node = self.find_first_child_named(dom, "metadata")
|
|
||||||
metadata = self.extract_metadata(metadata_node)
|
|
||||||
return {'body': {'metadata': metadata}}
|
|
||||||
|
|
||||||
|
|
||||||
class MetaItemDeserializer(wsgi.MetadataXMLDeserializer):
|
|
||||||
def deserialize(self, text):
|
|
||||||
dom = utils.safe_minidom_parse_string(text)
|
|
||||||
metadata_item = self.extract_metadata(dom)
|
|
||||||
return {'body': {'meta': metadata_item}}
|
|
||||||
|
|
||||||
|
|
||||||
class MetadataXMLDeserializer(wsgi.XMLDeserializer):
|
|
||||||
|
|
||||||
def extract_metadata(self, metadata_node):
|
|
||||||
"""Marshal the metadata attribute of a parsed request."""
|
|
||||||
if metadata_node is None:
|
|
||||||
return {}
|
|
||||||
metadata = {}
|
|
||||||
for meta_node in self.find_children_named(metadata_node, "meta"):
|
|
||||||
key = meta_node.getAttribute("key")
|
|
||||||
metadata[key] = self.extract_text(meta_node)
|
|
||||||
return metadata
|
|
||||||
|
|
||||||
def _extract_metadata_container(self, datastring):
|
|
||||||
dom = utils.safe_minidom_parse_string(datastring)
|
|
||||||
metadata_node = self.find_first_child_named(dom, "metadata")
|
|
||||||
metadata = self.extract_metadata(metadata_node)
|
|
||||||
return {'body': {'metadata': metadata}}
|
|
||||||
|
|
||||||
def create(self, datastring):
|
|
||||||
return self._extract_metadata_container(datastring)
|
|
||||||
|
|
||||||
def update_all(self, datastring):
|
|
||||||
return self._extract_metadata_container(datastring)
|
|
||||||
|
|
||||||
def update(self, datastring):
|
|
||||||
dom = utils.safe_minidom_parse_string(datastring)
|
|
||||||
metadata_item = self.extract_metadata(dom)
|
|
||||||
return {'body': {'meta': metadata_item}}
|
|
||||||
|
|
||||||
|
|
||||||
metadata_nsmap = {None: xmlutil.XMLNS_V11}
|
|
||||||
|
|
||||||
|
|
||||||
class MetaItemTemplate(xmlutil.TemplateBuilder):
|
|
||||||
def construct(self):
|
|
||||||
sel = xmlutil.Selector('meta', xmlutil.get_items, 0)
|
|
||||||
root = xmlutil.TemplateElement('meta', selector=sel)
|
|
||||||
root.set('key', 0)
|
|
||||||
root.text = 1
|
|
||||||
return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
|
|
||||||
|
|
||||||
|
|
||||||
class MetadataTemplateElement(xmlutil.TemplateElement):
|
|
||||||
def will_render(self, datum):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class MetadataTemplate(xmlutil.TemplateBuilder):
|
|
||||||
def construct(self):
|
|
||||||
root = MetadataTemplateElement('metadata', selector='metadata')
|
|
||||||
elem = xmlutil.SubTemplateElement(root, 'meta',
|
|
||||||
selector=xmlutil.get_items)
|
|
||||||
elem.set('key', 0)
|
|
||||||
elem.text = 1
|
|
||||||
return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
|
|
||||||
|
@ -334,7 +334,6 @@ class Admin_actions(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "AdminActions"
|
name = "AdminActions"
|
||||||
alias = "os-admin-actions"
|
alias = "os-admin-actions"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/admin-actions/api/v1.1"
|
|
||||||
updated = "2012-08-25T00:00:00+00:00"
|
updated = "2012-08-25T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
|
@ -16,29 +16,10 @@
|
|||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
import cinder.api.views.availability_zones
|
import cinder.api.views.availability_zones
|
||||||
from cinder.api import xmlutil
|
|
||||||
import cinder.exception
|
import cinder.exception
|
||||||
import cinder.volume.api
|
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):
|
class Controller(wsgi.Controller):
|
||||||
|
|
||||||
_view_builder_class = cinder.api.views.availability_zones.ViewBuilder
|
_view_builder_class = cinder.api.views.availability_zones.ViewBuilder
|
||||||
@ -47,7 +28,6 @@ class Controller(wsgi.Controller):
|
|||||||
super(Controller, self).__init__(*args, **kwargs)
|
super(Controller, self).__init__(*args, **kwargs)
|
||||||
self.volume_api = cinder.volume.api.API()
|
self.volume_api = cinder.volume.api.API()
|
||||||
|
|
||||||
@wsgi.serializers(xml=ListTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Describe all known availability zones."""
|
"""Describe all known availability zones."""
|
||||||
azs = self.volume_api.list_availability_zones()
|
azs = self.volume_api.list_availability_zones()
|
||||||
@ -59,8 +39,6 @@ class Availability_zones(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = 'AvailabilityZones'
|
name = 'AvailabilityZones'
|
||||||
alias = 'os-availability-zone'
|
alias = 'os-availability-zone'
|
||||||
namespace = ('http://docs.openstack.org/volume/ext/'
|
|
||||||
'os-availability-zone/api/v1')
|
|
||||||
updated = '2013-06-27T00:00:00+00:00'
|
updated = '2013-06-27T00:00:00+00:00'
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -25,7 +25,6 @@ from cinder.api import common
|
|||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import backups as backup_views
|
from cinder.api.views import backups as backup_views
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import backup as backupAPI
|
from cinder import backup as backupAPI
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
@ -34,123 +33,6 @@ from cinder import utils
|
|||||||
LOG = logging.getLogger(__name__)
|
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):
|
class BackupsController(wsgi.Controller):
|
||||||
"""The Backups API controller for the OpenStack API."""
|
"""The Backups API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -160,7 +42,6 @@ class BackupsController(wsgi.Controller):
|
|||||||
self.backup_api = backupAPI.API()
|
self.backup_api = backupAPI.API()
|
||||||
super(BackupsController, self).__init__()
|
super(BackupsController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=BackupTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about the given backup."""
|
"""Return data about the given backup."""
|
||||||
LOG.debug('show called for member %s', id)
|
LOG.debug('show called for member %s', id)
|
||||||
@ -191,12 +72,10 @@ class BackupsController(wsgi.Controller):
|
|||||||
|
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.serializers(xml=BackupsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns a summary list of backups."""
|
"""Returns a summary list of backups."""
|
||||||
return self._get_backups(req, is_detail=False)
|
return self._get_backups(req, is_detail=False)
|
||||||
|
|
||||||
@wsgi.serializers(xml=BackupsTemplate)
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of backups."""
|
"""Returns a detailed list of backups."""
|
||||||
return self._get_backups(req, is_detail=True)
|
return self._get_backups(req, is_detail=True)
|
||||||
@ -242,8 +121,6 @@ class BackupsController(wsgi.Controller):
|
|||||||
# immediately
|
# immediately
|
||||||
# - maybe also do validation of swift container name
|
# - maybe also do validation of swift container name
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=BackupTemplate)
|
|
||||||
@wsgi.deserializers(xml=CreateDeserializer)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Create a new backup."""
|
"""Create a new backup."""
|
||||||
LOG.debug('Creating new backup %s', body)
|
LOG.debug('Creating new backup %s', body)
|
||||||
@ -287,8 +164,6 @@ class BackupsController(wsgi.Controller):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=BackupRestoreTemplate)
|
|
||||||
@wsgi.deserializers(xml=RestoreDeserializer)
|
|
||||||
def restore(self, req, id, body):
|
def restore(self, req, id, body):
|
||||||
"""Restore an existing backup to a volume."""
|
"""Restore an existing backup to a volume."""
|
||||||
LOG.debug('Restoring backup %(backup_id)s (%(body)s)',
|
LOG.debug('Restoring backup %(backup_id)s (%(body)s)',
|
||||||
@ -331,7 +206,6 @@ class BackupsController(wsgi.Controller):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
@wsgi.response(200)
|
@wsgi.response(200)
|
||||||
@wsgi.serializers(xml=BackupExportImportTemplate)
|
|
||||||
def export_record(self, req, id):
|
def export_record(self, req, id):
|
||||||
"""Export a backup."""
|
"""Export a backup."""
|
||||||
LOG.debug('export record called for member %s.', id)
|
LOG.debug('export record called for member %s.', id)
|
||||||
@ -350,8 +224,6 @@ class BackupsController(wsgi.Controller):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
@wsgi.response(201)
|
@wsgi.response(201)
|
||||||
@wsgi.serializers(xml=BackupTemplate)
|
|
||||||
@wsgi.deserializers(xml=BackupImportDeserializer)
|
|
||||||
def import_record(self, req, body):
|
def import_record(self, req, body):
|
||||||
"""Import a backup."""
|
"""Import a backup."""
|
||||||
LOG.debug('Importing record from %s.', body)
|
LOG.debug('Importing record from %s.', body)
|
||||||
@ -389,7 +261,6 @@ class Backups(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = 'Backups'
|
name = 'Backups'
|
||||||
alias = 'backups'
|
alias = 'backups'
|
||||||
namespace = 'http://docs.openstack.org/volume/ext/backups/api/v1'
|
|
||||||
updated = '2012-12-12T00:00:00+00:00'
|
updated = '2012-12-12T00:00:00+00:00'
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -65,7 +65,6 @@ class Capabilities(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "Capabilities"
|
name = "Capabilities"
|
||||||
alias = "capabilities"
|
alias = "capabilities"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/capabilities/api/v2"
|
|
||||||
updated = "2015-08-31T00:00:00+00:00"
|
updated = "2015-08-31T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -23,63 +23,13 @@ from cinder.api import common
|
|||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import cgsnapshots as cgsnapshot_views
|
from cinder.api.views import cgsnapshots as cgsnapshot_views
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import consistencygroup as consistencygroupAPI
|
from cinder import consistencygroup as consistencygroupAPI
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
from cinder import utils
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
class CgsnapshotsController(wsgi.Controller):
|
||||||
"""The cgsnapshots API controller for the OpenStack API."""
|
"""The cgsnapshots API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -89,7 +39,6 @@ class CgsnapshotsController(wsgi.Controller):
|
|||||||
self.cgsnapshot_api = consistencygroupAPI.API()
|
self.cgsnapshot_api = consistencygroupAPI.API()
|
||||||
super(CgsnapshotsController, self).__init__()
|
super(CgsnapshotsController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=CgsnapshotTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about the given cgsnapshot."""
|
"""Return data about the given cgsnapshot."""
|
||||||
LOG.debug('show called for member %s', id)
|
LOG.debug('show called for member %s', id)
|
||||||
@ -127,12 +76,10 @@ class CgsnapshotsController(wsgi.Controller):
|
|||||||
|
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.serializers(xml=CgsnapshotsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns a summary list of cgsnapshots."""
|
"""Returns a summary list of cgsnapshots."""
|
||||||
return self._get_cgsnapshots(req, is_detail=False)
|
return self._get_cgsnapshots(req, is_detail=False)
|
||||||
|
|
||||||
@wsgi.serializers(xml=CgsnapshotsTemplate)
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of cgsnapshots."""
|
"""Returns a detailed list of cgsnapshots."""
|
||||||
return self._get_cgsnapshots(req, is_detail=True)
|
return self._get_cgsnapshots(req, is_detail=True)
|
||||||
@ -150,8 +97,6 @@ class CgsnapshotsController(wsgi.Controller):
|
|||||||
return cgsnapshots
|
return cgsnapshots
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=CgsnapshotTemplate)
|
|
||||||
@wsgi.deserializers(xml=CreateDeserializer)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Create a new cgsnapshot."""
|
"""Create a new cgsnapshot."""
|
||||||
LOG.debug('Creating new cgsnapshot %s', body)
|
LOG.debug('Creating new cgsnapshot %s', body)
|
||||||
@ -197,7 +142,6 @@ class Cgsnapshots(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = 'Cgsnapshots'
|
name = 'Cgsnapshots'
|
||||||
alias = 'cgsnapshots'
|
alias = 'cgsnapshots'
|
||||||
namespace = 'http://docs.openstack.org/volume/ext/cgsnapshots/api/v1'
|
|
||||||
updated = '2014-08-18T00:00:00+00:00'
|
updated = '2014-08-18T00:00:00+00:00'
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -24,108 +24,13 @@ from cinder.api import common
|
|||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import consistencygroups as consistencygroup_views
|
from cinder.api.views import consistencygroups as consistencygroup_views
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import consistencygroup as consistencygroupAPI
|
from cinder import consistencygroup as consistencygroupAPI
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
from cinder import utils
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
class ConsistencyGroupsController(wsgi.Controller):
|
||||||
"""The ConsistencyGroups API controller for the OpenStack API."""
|
"""The ConsistencyGroups API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -135,7 +40,6 @@ class ConsistencyGroupsController(wsgi.Controller):
|
|||||||
self.consistencygroup_api = consistencygroupAPI.API()
|
self.consistencygroup_api = consistencygroupAPI.API()
|
||||||
super(ConsistencyGroupsController, self).__init__()
|
super(ConsistencyGroupsController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=ConsistencyGroupTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about the given consistency group."""
|
"""Return data about the given consistency group."""
|
||||||
LOG.debug('show called for member %s', id)
|
LOG.debug('show called for member %s', id)
|
||||||
@ -182,12 +86,10 @@ class ConsistencyGroupsController(wsgi.Controller):
|
|||||||
|
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.serializers(xml=ConsistencyGroupsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns a summary list of consistency groups."""
|
"""Returns a summary list of consistency groups."""
|
||||||
return self._get_consistencygroups(req, is_detail=False)
|
return self._get_consistencygroups(req, is_detail=False)
|
||||||
|
|
||||||
@wsgi.serializers(xml=ConsistencyGroupsTemplate)
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of consistency groups."""
|
"""Returns a detailed list of consistency groups."""
|
||||||
return self._get_consistencygroups(req, is_detail=True)
|
return self._get_consistencygroups(req, is_detail=True)
|
||||||
@ -212,8 +114,6 @@ class ConsistencyGroupsController(wsgi.Controller):
|
|||||||
return consistencygroups
|
return consistencygroups
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=ConsistencyGroupTemplate)
|
|
||||||
@wsgi.deserializers(xml=CreateDeserializer)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Create a new consistency group."""
|
"""Create a new consistency group."""
|
||||||
LOG.debug('Creating new consistency group %s', body)
|
LOG.debug('Creating new consistency group %s', body)
|
||||||
@ -250,8 +150,6 @@ class ConsistencyGroupsController(wsgi.Controller):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=ConsistencyGroupFromSrcTemplate)
|
|
||||||
@wsgi.deserializers(xml=CreateFromSrcDeserializer)
|
|
||||||
def create_from_src(self, req, body):
|
def create_from_src(self, req, body):
|
||||||
"""Create a new consistency group from a source.
|
"""Create a new consistency group from a source.
|
||||||
|
|
||||||
@ -307,7 +205,6 @@ class ConsistencyGroupsController(wsgi.Controller):
|
|||||||
retval = self._view_builder.summary(req, new_consistencygroup)
|
retval = self._view_builder.summary(req, new_consistencygroup)
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
@wsgi.serializers(xml=ConsistencyGroupTemplate)
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Update the consistency group.
|
"""Update the consistency group.
|
||||||
|
|
||||||
@ -375,7 +272,6 @@ class Consistencygroups(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = 'Consistencygroups'
|
name = 'Consistencygroups'
|
||||||
alias = 'consistencygroups'
|
alias = 'consistencygroups'
|
||||||
namespace = 'http://docs.openstack.org/volume/ext/consistencygroups/api/v1'
|
|
||||||
updated = '2014-08-18T00:00:00+00:00'
|
updated = '2014-08-18T00:00:00+00:00'
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -20,6 +20,4 @@ class Extended_services(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "ExtendedServices"
|
name = "ExtendedServices"
|
||||||
alias = "os-extended-services"
|
alias = "os-extended-services"
|
||||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
|
||||||
"extended_services/api/v2")
|
|
||||||
updated = "2014-01-10T00:00:00-00:00"
|
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 import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -39,7 +38,6 @@ class ExtendedSnapshotAttributesController(wsgi.Controller):
|
|||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
# Attach our slave template to the response object
|
# Attach our slave template to the response object
|
||||||
resp_obj.attach(xml=ExtendedSnapshotAttributeTemplate())
|
|
||||||
snapshot = resp_obj.obj['snapshot']
|
snapshot = resp_obj.obj['snapshot']
|
||||||
self._extend_snapshot(req, snapshot)
|
self._extend_snapshot(req, snapshot)
|
||||||
|
|
||||||
@ -48,7 +46,6 @@ class ExtendedSnapshotAttributesController(wsgi.Controller):
|
|||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
# Attach our slave template to the response object
|
# Attach our slave template to the response object
|
||||||
resp_obj.attach(xml=ExtendedSnapshotAttributesTemplate())
|
|
||||||
for snapshot in list(resp_obj.obj['snapshots']):
|
for snapshot in list(resp_obj.obj['snapshots']):
|
||||||
self._extend_snapshot(req, snapshot)
|
self._extend_snapshot(req, snapshot)
|
||||||
|
|
||||||
@ -58,8 +55,6 @@ class Extended_snapshot_attributes(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "ExtendedSnapshotAttributes"
|
name = "ExtendedSnapshotAttributes"
|
||||||
alias = "os-extended-snapshot-attributes"
|
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"
|
updated = "2012-06-19T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
@ -67,30 +62,3 @@ class Extended_snapshot_attributes(extensions.ExtensionDescriptor):
|
|||||||
extension = extensions.ControllerExtension(self, 'snapshots',
|
extension = extensions.ControllerExtension(self, 'snapshots',
|
||||||
controller)
|
controller)
|
||||||
return [extension]
|
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."""
|
"""The hosts admin extension."""
|
||||||
|
|
||||||
from xml.parsers import expat
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
@ -24,12 +22,10 @@ import webob.exc
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import db
|
from cinder import db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
from cinder import objects
|
from cinder import objects
|
||||||
from cinder import utils
|
|
||||||
from cinder.volume import api as volume_api
|
from cinder.volume import api as volume_api
|
||||||
|
|
||||||
|
|
||||||
@ -39,62 +35,6 @@ LOG = logging.getLogger(__name__)
|
|||||||
authorize = extensions.extension_authorizer('volume', 'hosts')
|
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):
|
def _list_hosts(req, service=None):
|
||||||
"""Returns a summary list of hosts."""
|
"""Returns a summary list of hosts."""
|
||||||
curr_time = timeutils.utcnow(with_timezone=True)
|
curr_time = timeutils.utcnow(with_timezone=True)
|
||||||
@ -151,13 +91,10 @@ class HostController(wsgi.Controller):
|
|||||||
self.api = volume_api.HostAPI()
|
self.api = volume_api.HostAPI()
|
||||||
super(HostController, self).__init__()
|
super(HostController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=HostIndexTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
authorize(req.environ['cinder.context'])
|
authorize(req.environ['cinder.context'])
|
||||||
return {'hosts': _list_hosts(req)}
|
return {'hosts': _list_hosts(req)}
|
||||||
|
|
||||||
@wsgi.serializers(xml=HostUpdateTemplate)
|
|
||||||
@wsgi.deserializers(xml=HostDeserializer)
|
|
||||||
@check_host
|
@check_host
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
authorize(req.environ['cinder.context'])
|
authorize(req.environ['cinder.context'])
|
||||||
@ -194,7 +131,6 @@ class HostController(wsgi.Controller):
|
|||||||
raise webob.exc.HTTPBadRequest(explanation=result)
|
raise webob.exc.HTTPBadRequest(explanation=result)
|
||||||
return {"host": host, "status": result}
|
return {"host": host, "status": result}
|
||||||
|
|
||||||
@wsgi.serializers(xml=HostShowTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Shows the volume usage info given by hosts.
|
"""Shows the volume usage info given by hosts.
|
||||||
|
|
||||||
@ -260,7 +196,6 @@ class Hosts(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "Hosts"
|
name = "Hosts"
|
||||||
alias = "os-hosts"
|
alias = "os-hosts"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/hosts/api/v1.1"
|
|
||||||
updated = "2011-06-29T00:00:00+00:00"
|
updated = "2011-06-29T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -25,5 +25,4 @@ class Image_create(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "CreateVolumeExtension"
|
name = "CreateVolumeExtension"
|
||||||
alias = "os-image-create"
|
alias = "os-image-create"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/image-create/api/v1"
|
|
||||||
updated = "2012-08-13T00:00:00+00:00"
|
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 import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import qos_specs as view_qos_specs
|
from cinder.api.views import qos_specs as view_qos_specs
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _
|
||||||
from cinder import rpc
|
from cinder import rpc
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder.volume import qos_specs
|
from cinder.volume import qos_specs
|
||||||
@ -37,62 +36,6 @@ LOG = logging.getLogger(__name__)
|
|||||||
authorize = extensions.extension_authorizer('volume', 'qos_specs_manage')
|
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):
|
def _check_specs(context, specs_id):
|
||||||
try:
|
try:
|
||||||
qos_specs.get_qos_specs(context, specs_id)
|
qos_specs.get_qos_specs(context, specs_id)
|
||||||
@ -111,7 +54,6 @@ class QoSSpecsController(wsgi.Controller):
|
|||||||
method,
|
method,
|
||||||
payload)
|
payload)
|
||||||
|
|
||||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns the list of qos_specs."""
|
"""Returns the list of qos_specs."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -132,7 +74,6 @@ class QoSSpecsController(wsgi.Controller):
|
|||||||
sort_dirs=sort_dirs)
|
sort_dirs=sort_dirs)
|
||||||
return self._view_builder.summary_list(req, specs)
|
return self._view_builder.summary_list(req, specs)
|
||||||
|
|
||||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
|
||||||
def create(self, req, body=None):
|
def create(self, req, body=None):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
@ -178,7 +119,6 @@ class QoSSpecsController(wsgi.Controller):
|
|||||||
|
|
||||||
return self._view_builder.detail(req, spec)
|
return self._view_builder.detail(req, spec)
|
||||||
|
|
||||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
|
||||||
def update(self, req, id, body=None):
|
def update(self, req, id, body=None):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
@ -213,7 +153,6 @@ class QoSSpecsController(wsgi.Controller):
|
|||||||
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return a single qos spec item."""
|
"""Return a single qos spec item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -263,7 +202,6 @@ class QoSSpecsController(wsgi.Controller):
|
|||||||
|
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.deserializers(xml=QoSSpecsKeyDeserializer)
|
|
||||||
def delete_keys(self, req, id, body):
|
def delete_keys(self, req, id, body):
|
||||||
"""Deletes specified keys in qos specs."""
|
"""Deletes specified keys in qos specs."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -297,7 +235,6 @@ class QoSSpecsController(wsgi.Controller):
|
|||||||
|
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.serializers(xml=AssociationsTemplate)
|
|
||||||
def associations(self, req, id):
|
def associations(self, req, id):
|
||||||
"""List all associations of given qos specs."""
|
"""List all associations of given qos specs."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -461,7 +398,6 @@ class Qos_specs_manage(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "Qos_specs_manage"
|
name = "Qos_specs_manage"
|
||||||
alias = "qos-specs"
|
alias = "qos-specs"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/qos-specs/api/v1"
|
|
||||||
updated = "2013-08-02T00:00:00+00:00"
|
updated = "2013-08-02T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -17,7 +17,6 @@ import webob
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import db
|
from cinder import db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -31,19 +30,6 @@ QUOTAS = quota.QUOTAS
|
|||||||
authorize = extensions.extension_authorizer('volume', 'quota_classes')
|
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):
|
class QuotaClassSetsController(wsgi.Controller):
|
||||||
|
|
||||||
def _format_quota_set(self, quota_class, quota_set):
|
def _format_quota_set(self, quota_class, quota_set):
|
||||||
@ -53,7 +39,6 @@ class QuotaClassSetsController(wsgi.Controller):
|
|||||||
|
|
||||||
return dict(quota_class_set=quota_set)
|
return dict(quota_class_set=quota_set)
|
||||||
|
|
||||||
@wsgi.serializers(xml=QuotaClassTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
@ -65,7 +50,6 @@ class QuotaClassSetsController(wsgi.Controller):
|
|||||||
return self._format_quota_set(id,
|
return self._format_quota_set(id,
|
||||||
QUOTAS.get_class_quotas(context, id))
|
QUOTAS.get_class_quotas(context, id))
|
||||||
|
|
||||||
@wsgi.serializers(xml=QuotaClassTemplate)
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
@ -97,8 +81,6 @@ class Quota_classes(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "QuotaClasses"
|
name = "QuotaClasses"
|
||||||
alias = "os-quota-class-sets"
|
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"
|
updated = "2012-03-12T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -17,7 +17,6 @@ import webob
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import db
|
from cinder import db
|
||||||
from cinder.db.sqlalchemy import api as sqlalchemy_api
|
from cinder.db.sqlalchemy import api as sqlalchemy_api
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
@ -39,18 +38,6 @@ authorize_show = extensions.extension_authorizer('volume', 'quotas:show')
|
|||||||
authorize_delete = extensions.extension_authorizer('volume', 'quotas:delete')
|
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):
|
class QuotaSetsController(wsgi.Controller):
|
||||||
|
|
||||||
def _format_quota_set(self, project_id, quota_set):
|
def _format_quota_set(self, project_id, quota_set):
|
||||||
@ -155,7 +142,6 @@ class QuotaSetsController(wsgi.Controller):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@wsgi.serializers(xml=QuotaTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Show quota for a particular tenant
|
"""Show quota for a particular tenant
|
||||||
|
|
||||||
@ -197,7 +183,6 @@ class QuotaSetsController(wsgi.Controller):
|
|||||||
quotas = self._get_quotas(context, target_project_id, usage)
|
quotas = self._get_quotas(context, target_project_id, usage)
|
||||||
return self._format_quota_set(target_project_id, quotas)
|
return self._format_quota_set(target_project_id, quotas)
|
||||||
|
|
||||||
@wsgi.serializers(xml=QuotaTemplate)
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Update Quota for a particular tenant
|
"""Update Quota for a particular tenant
|
||||||
|
|
||||||
@ -337,7 +322,6 @@ class QuotaSetsController(wsgi.Controller):
|
|||||||
|
|
||||||
return reservations
|
return reservations
|
||||||
|
|
||||||
@wsgi.serializers(xml=QuotaTemplate)
|
|
||||||
def defaults(self, req, id):
|
def defaults(self, req, id):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize_show(context)
|
authorize_show(context)
|
||||||
@ -345,7 +329,6 @@ class QuotaSetsController(wsgi.Controller):
|
|||||||
return self._format_quota_set(id, QUOTAS.get_defaults(
|
return self._format_quota_set(id, QUOTAS.get_defaults(
|
||||||
context, project_id=id))
|
context, project_id=id))
|
||||||
|
|
||||||
@wsgi.serializers(xml=QuotaTemplate)
|
|
||||||
def delete(self, req, id):
|
def delete(self, req, id):
|
||||||
"""Delete Quota for a particular tenant.
|
"""Delete Quota for a particular tenant.
|
||||||
|
|
||||||
@ -436,7 +419,6 @@ class Quotas(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "Quotas"
|
name = "Quotas"
|
||||||
alias = "os-quota-sets"
|
alias = "os-quota-sets"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/quotas-sets/api/v1.1"
|
|
||||||
updated = "2011-08-08T00:00:00+00:00"
|
updated = "2011-08-08T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -17,7 +17,6 @@ import webob.exc
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.v2 import volumes
|
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -53,7 +52,6 @@ class Scheduler_hints(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "SchedulerHints"
|
name = "SchedulerHints"
|
||||||
alias = "OS-SCH-HNT"
|
alias = "OS-SCH-HNT"
|
||||||
namespace = volumes.SCHEDULER_HINTS_NAMESPACE
|
|
||||||
updated = "2013-04-18T00:00:00+00:00"
|
updated = "2013-04-18T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
|
@ -56,7 +56,6 @@ class Scheduler_stats(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "Scheduler_stats"
|
name = "Scheduler_stats"
|
||||||
alias = "scheduler-stats"
|
alias = "scheduler-stats"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/scheduler-stats/api/v1"
|
|
||||||
updated = "2014-09-07T00:00:00+00:00"
|
updated = "2014-09-07T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -22,7 +22,6 @@ import webob.exc
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import objects
|
from cinder import objects
|
||||||
@ -36,51 +35,12 @@ LOG = logging.getLogger(__name__)
|
|||||||
authorize = extensions.extension_authorizer('volume', 'services')
|
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):
|
class ServiceController(wsgi.Controller):
|
||||||
def __init__(self, ext_mgr=None):
|
def __init__(self, ext_mgr=None):
|
||||||
self.ext_mgr = ext_mgr
|
self.ext_mgr = ext_mgr
|
||||||
super(ServiceController, self).__init__()
|
super(ServiceController, self).__init__()
|
||||||
self.volume_api = volume.API()
|
self.volume_api = volume.API()
|
||||||
|
|
||||||
@wsgi.serializers(xml=ServicesIndexTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Return a list of all running services.
|
"""Return a list of all running services.
|
||||||
|
|
||||||
@ -154,7 +114,6 @@ class ServiceController(wsgi.Controller):
|
|||||||
def _failover(self, context, host, backend_id=None):
|
def _failover(self, context, host, backend_id=None):
|
||||||
return self.volume_api.failover_host(context, host, backend_id)
|
return self.volume_api.failover_host(context, host, backend_id)
|
||||||
|
|
||||||
@wsgi.serializers(xml=ServicesUpdateTemplate)
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Enable/Disable scheduling for a service.
|
"""Enable/Disable scheduling for a service.
|
||||||
|
|
||||||
@ -236,7 +195,6 @@ class Services(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "Services"
|
name = "Services"
|
||||||
alias = "os-services"
|
alias = "os-services"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/services/api/v2"
|
|
||||||
updated = "2012-10-28T00:00:00-00:00"
|
updated = "2012-10-28T00:00:00-00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -100,8 +100,6 @@ class Snapshot_actions(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "SnapshotActions"
|
name = "SnapshotActions"
|
||||||
alias = "os-snapshot-actions"
|
alias = "os-snapshot-actions"
|
||||||
namespace = \
|
|
||||||
"http://docs.openstack.org/volume/ext/snapshot-actions/api/v1.1"
|
|
||||||
updated = "2013-07-16T00:00:00+00:00"
|
updated = "2013-07-16T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
|
@ -18,7 +18,6 @@ from webob import exc
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.v2 import snapshots
|
|
||||||
from cinder.api.views import snapshots as snapshot_views
|
from cinder.api.views import snapshots as snapshot_views
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -39,7 +38,6 @@ class SnapshotManageController(wsgi.Controller):
|
|||||||
self.volume_api = cinder_volume.API()
|
self.volume_api = cinder_volume.API()
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=snapshots.SnapshotTemplate)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Instruct Cinder to manage a storage snapshot object.
|
"""Instruct Cinder to manage a storage snapshot object.
|
||||||
|
|
||||||
@ -138,8 +136,6 @@ class Snapshot_manage(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = 'SnapshotManage'
|
name = 'SnapshotManage'
|
||||||
alias = 'os-snapshot-manage'
|
alias = 'os-snapshot-manage'
|
||||||
namespace = ('http://docs.openstack.org/volume/ext/'
|
|
||||||
'os-snapshot-manage/api/v1')
|
|
||||||
updated = '2014-12-31T00:00:00+00:00'
|
updated = '2014-12-31T00:00:00+00:00'
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -66,8 +66,6 @@ class Snapshot_unmanage(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "SnapshotUnmanage"
|
name = "SnapshotUnmanage"
|
||||||
alias = "os-snapshot-unmanage"
|
alias = "os-snapshot-unmanage"
|
||||||
namespace = ('http://docs.openstack.org/snapshot/ext/snapshot-unmanage'
|
|
||||||
'/api/v1')
|
|
||||||
updated = "2014-12-31T00:00:00+00:00"
|
updated = "2014-12-31T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
|
@ -20,7 +20,6 @@ import webob
|
|||||||
from cinder.api import common
|
from cinder.api import common
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import db
|
from cinder import db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -30,26 +29,6 @@ from cinder.volume import volume_types
|
|||||||
authorize = extensions.extension_authorizer('volume', 'types_extra_specs')
|
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):
|
class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||||
"""The volume type extra specs API controller for the OpenStack API."""
|
"""The volume type extra specs API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -66,7 +45,6 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
|||||||
except exception.VolumeTypeNotFound as ex:
|
except exception.VolumeTypeNotFound as ex:
|
||||||
raise webob.exc.HTTPNotFound(explanation=ex.msg)
|
raise webob.exc.HTTPNotFound(explanation=ex.msg)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate)
|
|
||||||
def index(self, req, type_id):
|
def index(self, req, type_id):
|
||||||
"""Returns the list of extra specs for a given volume type."""
|
"""Returns the list of extra specs for a given volume type."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -85,7 +63,6 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
|||||||
self.validate_string_length(value, 'Value for key "%s"' % key,
|
self.validate_string_length(value, 'Value for key "%s"' % key,
|
||||||
min_length=0, max_length=255)
|
min_length=0, max_length=255)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate)
|
|
||||||
def create(self, req, type_id, body=None):
|
def create(self, req, type_id, body=None):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
@ -106,7 +83,6 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
|||||||
notifier_info)
|
notifier_info)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeExtraSpecTemplate)
|
|
||||||
def update(self, req, type_id, id, body=None):
|
def update(self, req, type_id, id, body=None):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
@ -133,7 +109,6 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
|||||||
notifier_info)
|
notifier_info)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeExtraSpecTemplate)
|
|
||||||
def show(self, req, type_id, id):
|
def show(self, req, type_id, id):
|
||||||
"""Return a single extra spec item."""
|
"""Return a single extra spec item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -178,7 +153,6 @@ class Types_extra_specs(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "TypesExtraSpecs"
|
name = "TypesExtraSpecs"
|
||||||
alias = "os-types-extra-specs"
|
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"
|
updated = "2011-08-24T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -20,7 +20,6 @@ import webob
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.v1 import types
|
|
||||||
from cinder.api.views import types as views_types
|
from cinder.api.views import types as views_types
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -48,7 +47,6 @@ class VolumeTypesManageController(wsgi.Controller):
|
|||||||
rpc.get_notifier('volumeType').info(context, method, payload)
|
rpc.get_notifier('volumeType').info(context, method, payload)
|
||||||
|
|
||||||
@wsgi.action("create")
|
@wsgi.action("create")
|
||||||
@wsgi.serializers(xml=types.VolumeTypeTemplate)
|
|
||||||
def _create(self, req, body):
|
def _create(self, req, body):
|
||||||
"""Creates a new volume type."""
|
"""Creates a new volume type."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -101,7 +99,6 @@ class VolumeTypesManageController(wsgi.Controller):
|
|||||||
return self._view_builder.show(req, vol_type)
|
return self._view_builder.show(req, vol_type)
|
||||||
|
|
||||||
@wsgi.action("update")
|
@wsgi.action("update")
|
||||||
@wsgi.serializers(xml=types.VolumeTypeTemplate)
|
|
||||||
def _update(self, req, id, body):
|
def _update(self, req, id, body):
|
||||||
# Update description for a given volume type.
|
# Update description for a given volume type.
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -192,7 +189,6 @@ class Types_manage(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "TypesManage"
|
name = "TypesManage"
|
||||||
alias = "os-types-manage"
|
alias = "os-types-manage"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/types-manage/api/v1"
|
|
||||||
updated = "2011-08-24T00:00:00+00:00"
|
updated = "2011-08-24T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
|
@ -51,7 +51,6 @@ class Used_limits(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "UsedLimits"
|
name = "UsedLimits"
|
||||||
alias = 'os-used-limits'
|
alias = 'os-used-limits'
|
||||||
namespace = "http://docs.openstack.org/volume/ext/used-limits/api/v1.1"
|
|
||||||
updated = "2013-10-03T00:00:00+00:00"
|
updated = "2013-10-03T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
|
@ -24,7 +24,6 @@ import webob
|
|||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import api_version_request
|
from cinder.api.openstack import api_version_request
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
@ -40,50 +39,6 @@ def authorize(context, action_name):
|
|||||||
extensions.extension_authorizer('volume', action)(context)
|
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):
|
class VolumeActionsController(wsgi.Controller):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(VolumeActionsController, self).__init__(*args, **kwargs)
|
super(VolumeActionsController, self).__init__(*args, **kwargs)
|
||||||
@ -261,8 +216,6 @@ class VolumeActionsController(wsgi.Controller):
|
|||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.action('os-volume_upload_image')
|
@wsgi.action('os-volume_upload_image')
|
||||||
@wsgi.serializers(xml=VolumeToImageSerializer)
|
|
||||||
@wsgi.deserializers(xml=VolumeToImageDeserializer)
|
|
||||||
def _volume_upload_image(self, req, id, body):
|
def _volume_upload_image(self, req, id, body):
|
||||||
"""Uploads the specified volume to image service."""
|
"""Uploads the specified volume to image service."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -423,7 +376,6 @@ class Volume_actions(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeActions"
|
name = "VolumeActions"
|
||||||
alias = "os-volume-actions"
|
alias = "os-volume-actions"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/volume-actions/api/v1.1"
|
|
||||||
updated = "2012-05-31T00:00:00+00:00"
|
updated = "2012-05-31T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
|
@ -17,30 +17,21 @@
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import db
|
from cinder import db
|
||||||
|
|
||||||
authorize = extensions.extension_authorizer('volume',
|
authorize = extensions.extension_authorizer('volume',
|
||||||
'volume_encryption_metadata')
|
'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):
|
class VolumeEncryptionMetadataController(wsgi.Controller):
|
||||||
"""The volume encryption metadata API extension."""
|
"""The volume encryption metadata API extension."""
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeEncryptionMetadataTemplate)
|
|
||||||
def index(self, req, volume_id):
|
def index(self, req, volume_id):
|
||||||
"""Returns the encryption metadata for a given volume."""
|
"""Returns the encryption metadata for a given volume."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
return db.volume_encryption_metadata_get(context, volume_id)
|
return db.volume_encryption_metadata_get(context, volume_id)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeEncryptionMetadataTemplate)
|
|
||||||
def show(self, req, volume_id, id):
|
def show(self, req, volume_id, id):
|
||||||
"""Return a single encryption item."""
|
"""Return a single encryption item."""
|
||||||
encryption_item = self.index(req, volume_id)
|
encryption_item = self.index(req, volume_id)
|
||||||
@ -55,8 +46,6 @@ class Volume_encryption_metadata(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeEncryptionMetadata"
|
name = "VolumeEncryptionMetadata"
|
||||||
alias = "os-volume-encryption-metadata"
|
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"
|
updated = "2013-07-10T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -16,7 +16,6 @@ from oslo_log import log as logging
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -34,7 +33,6 @@ class VolumeHostAttributeController(wsgi.Controller):
|
|||||||
def show(self, req, resp_obj, id):
|
def show(self, req, resp_obj, id):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
resp_obj.attach(xml=VolumeHostAttributeTemplate())
|
|
||||||
volume = resp_obj.obj['volume']
|
volume = resp_obj.obj['volume']
|
||||||
self._add_volume_host_attribute(req, volume)
|
self._add_volume_host_attribute(req, volume)
|
||||||
|
|
||||||
@ -42,7 +40,6 @@ class VolumeHostAttributeController(wsgi.Controller):
|
|||||||
def detail(self, req, resp_obj):
|
def detail(self, req, resp_obj):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
resp_obj.attach(xml=VolumeListHostAttributeTemplate())
|
|
||||||
for vol in list(resp_obj.obj['volumes']):
|
for vol in list(resp_obj.obj['volumes']):
|
||||||
self._add_volume_host_attribute(req, vol)
|
self._add_volume_host_attribute(req, vol)
|
||||||
|
|
||||||
@ -52,35 +49,9 @@ class Volume_host_attribute(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeHostAttribute"
|
name = "VolumeHostAttribute"
|
||||||
alias = "os-vol-host-attr"
|
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"
|
updated = "2011-11-03T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
controller = VolumeHostAttributeController()
|
controller = VolumeHostAttributeController()
|
||||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||||
return [extension]
|
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 common
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import volume
|
from cinder import volume
|
||||||
@ -75,22 +74,18 @@ class VolumeImageMetadataController(wsgi.Controller):
|
|||||||
def show(self, req, resp_obj, id):
|
def show(self, req, resp_obj, id):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
resp_obj.attach(xml=VolumeImageMetadataTemplate())
|
|
||||||
self._add_image_metadata(context, [resp_obj.obj['volume']])
|
self._add_image_metadata(context, [resp_obj.obj['volume']])
|
||||||
|
|
||||||
@wsgi.extends
|
@wsgi.extends
|
||||||
def detail(self, req, resp_obj):
|
def detail(self, req, resp_obj):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
resp_obj.attach(xml=VolumesImageMetadataTemplate())
|
|
||||||
# Just get the image metadata of those volumes in response.
|
# Just get the image metadata of those volumes in response.
|
||||||
volumes = list(resp_obj.obj.get('volumes', []))
|
volumes = list(resp_obj.obj.get('volumes', []))
|
||||||
if volumes:
|
if volumes:
|
||||||
self._add_image_metadata(context, volumes)
|
self._add_image_metadata(context, volumes)
|
||||||
|
|
||||||
@wsgi.action("os-set_image_metadata")
|
@wsgi.action("os-set_image_metadata")
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
|
||||||
def create(self, req, id, body):
|
def create(self, req, id, body):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
@ -130,7 +125,6 @@ class VolumeImageMetadataController(wsgi.Controller):
|
|||||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||||
|
|
||||||
@wsgi.action("os-show_image_metadata")
|
@wsgi.action("os-show_image_metadata")
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
def index(self, req, id, body):
|
def index(self, req, id, body):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
return {'metadata': self._get_image_metadata(context, id)[1]}
|
return {'metadata': self._get_image_metadata(context, id)[1]}
|
||||||
@ -167,46 +161,9 @@ class Volume_image_metadata(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeImageMetadata"
|
name = "VolumeImageMetadata"
|
||||||
alias = "os-vol-image-meta"
|
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"
|
updated = "2012-12-07T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
controller = VolumeImageMetadataController()
|
controller = VolumeImageMetadataController()
|
||||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||||
return [extension]
|
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 import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.v2.views import volumes as volume_views
|
from cinder.api.v2.views import volumes as volume_views
|
||||||
from cinder.api.v2 import volumes
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
@ -40,8 +39,6 @@ class VolumeManageController(wsgi.Controller):
|
|||||||
self.volume_api = cinder_volume.API()
|
self.volume_api = cinder_volume.API()
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=volumes.VolumeTemplate)
|
|
||||||
@wsgi.deserializers(xml=volumes.CreateDeserializer)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Instruct Cinder to manage a storage object.
|
"""Instruct Cinder to manage a storage object.
|
||||||
|
|
||||||
@ -155,8 +152,6 @@ class Volume_manage(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = 'VolumeManage'
|
name = 'VolumeManage'
|
||||||
alias = 'os-volume-manage'
|
alias = 'os-volume-manage'
|
||||||
namespace = ('http://docs.openstack.org/volume/ext/'
|
|
||||||
'os-volume-manage/api/v1')
|
|
||||||
updated = '2014-02-10T00:00:00+00:00'
|
updated = '2014-02-10T00:00:00+00:00'
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
|
|
||||||
|
|
||||||
authorize = extensions.soft_extension_authorizer('volume',
|
authorize = extensions.soft_extension_authorizer('volume',
|
||||||
'volume_mig_status_attribute')
|
'volume_mig_status_attribute')
|
||||||
@ -33,14 +31,12 @@ class VolumeMigStatusAttributeController(wsgi.Controller):
|
|||||||
def show(self, req, resp_obj, id):
|
def show(self, req, resp_obj, id):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
resp_obj.attach(xml=VolumeMigStatusAttributeTemplate())
|
|
||||||
self._add_volume_mig_status_attribute(req, resp_obj.obj['volume'])
|
self._add_volume_mig_status_attribute(req, resp_obj.obj['volume'])
|
||||||
|
|
||||||
@wsgi.extends
|
@wsgi.extends
|
||||||
def detail(self, req, resp_obj):
|
def detail(self, req, resp_obj):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
resp_obj.attach(xml=VolumeListMigStatusAttributeTemplate())
|
|
||||||
for vol in list(resp_obj.obj['volumes']):
|
for vol in list(resp_obj.obj['volumes']):
|
||||||
self._add_volume_mig_status_attribute(req, vol)
|
self._add_volume_mig_status_attribute(req, vol)
|
||||||
|
|
||||||
@ -50,37 +46,9 @@ class Volume_mig_status_attribute(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeMigStatusAttribute"
|
name = "VolumeMigStatusAttribute"
|
||||||
alias = "os-vol-mig-status-attr"
|
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"
|
updated = "2013-08-08T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
controller = VolumeMigStatusAttributeController()
|
controller = VolumeMigStatusAttributeController()
|
||||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||||
return [extension]
|
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 import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
|
|
||||||
|
|
||||||
authorize = extensions.soft_extension_authorizer('volume',
|
authorize = extensions.soft_extension_authorizer('volume',
|
||||||
@ -31,7 +30,6 @@ class VolumeTenantAttributeController(wsgi.Controller):
|
|||||||
def show(self, req, resp_obj, id):
|
def show(self, req, resp_obj, id):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
resp_obj.attach(xml=VolumeTenantAttributeTemplate())
|
|
||||||
volume = resp_obj.obj['volume']
|
volume = resp_obj.obj['volume']
|
||||||
self._add_volume_tenant_attribute(req, volume)
|
self._add_volume_tenant_attribute(req, volume)
|
||||||
|
|
||||||
@ -39,7 +37,6 @@ class VolumeTenantAttributeController(wsgi.Controller):
|
|||||||
def detail(self, req, resp_obj):
|
def detail(self, req, resp_obj):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if authorize(context):
|
if authorize(context):
|
||||||
resp_obj.attach(xml=VolumeListTenantAttributeTemplate())
|
|
||||||
for vol in list(resp_obj.obj['volumes']):
|
for vol in list(resp_obj.obj['volumes']):
|
||||||
self._add_volume_tenant_attribute(req, vol)
|
self._add_volume_tenant_attribute(req, vol)
|
||||||
|
|
||||||
@ -49,35 +46,9 @@ class Volume_tenant_attribute(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeTenantAttribute"
|
name = "VolumeTenantAttribute"
|
||||||
alias = "os-vol-tenant-attr"
|
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"
|
updated = "2011-11-03T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
controller = VolumeTenantAttributeController()
|
controller = VolumeTenantAttributeController()
|
||||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||||
return [extension]
|
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 import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import transfers as transfer_view
|
from cinder.api.views import transfers as transfer_view
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
from cinder import transfer as transferAPI
|
from cinder import transfer as transferAPI
|
||||||
from cinder import utils
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
class VolumeTransferController(wsgi.Controller):
|
||||||
"""The Volume Transfer API controller for the OpenStack API."""
|
"""The Volume Transfer API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -103,7 +37,6 @@ class VolumeTransferController(wsgi.Controller):
|
|||||||
self.transfer_api = transferAPI.API()
|
self.transfer_api = transferAPI.API()
|
||||||
super(VolumeTransferController, self).__init__()
|
super(VolumeTransferController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=TransferTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about active transfers."""
|
"""Return data about active transfers."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -115,12 +48,10 @@ class VolumeTransferController(wsgi.Controller):
|
|||||||
|
|
||||||
return self._view_builder.detail(req, transfer)
|
return self._view_builder.detail(req, transfer)
|
||||||
|
|
||||||
@wsgi.serializers(xml=TransfersTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns a summary list of transfers."""
|
"""Returns a summary list of transfers."""
|
||||||
return self._get_transfers(req, is_detail=False)
|
return self._get_transfers(req, is_detail=False)
|
||||||
|
|
||||||
@wsgi.serializers(xml=TransfersTemplate)
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of transfers."""
|
"""Returns a detailed list of transfers."""
|
||||||
return self._get_transfers(req, is_detail=True)
|
return self._get_transfers(req, is_detail=True)
|
||||||
@ -144,8 +75,6 @@ class VolumeTransferController(wsgi.Controller):
|
|||||||
return transfers
|
return transfers
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=TransferTemplate)
|
|
||||||
@wsgi.deserializers(xml=CreateDeserializer)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Create a new volume transfer."""
|
"""Create a new volume transfer."""
|
||||||
LOG.debug('Creating new volume transfer %s', body)
|
LOG.debug('Creating new volume transfer %s', body)
|
||||||
@ -183,8 +112,6 @@ class VolumeTransferController(wsgi.Controller):
|
|||||||
return transfer
|
return transfer
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=TransferTemplate)
|
|
||||||
@wsgi.deserializers(xml=AcceptDeserializer)
|
|
||||||
def accept(self, req, id, body):
|
def accept(self, req, id, body):
|
||||||
"""Accept a new volume transfer."""
|
"""Accept a new volume transfer."""
|
||||||
transfer_id = id
|
transfer_id = id
|
||||||
@ -235,8 +162,6 @@ class Volume_transfer(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeTransfer"
|
name = "VolumeTransfer"
|
||||||
alias = "os-volume-transfer"
|
alias = "os-volume-transfer"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/volume-transfer/" + \
|
|
||||||
"api/v1.1"
|
|
||||||
updated = "2013-05-29T00:00:00+00:00"
|
updated = "2013-05-29T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -19,7 +19,6 @@ import webob
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder.volume import volume_types
|
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')
|
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):
|
def _marshall_volume_type_access(vol_type):
|
||||||
rval = []
|
rval = []
|
||||||
for project_id in vol_type['projects']:
|
for project_id in vol_type['projects']:
|
||||||
@ -84,7 +44,6 @@ class VolumeTypeAccessController(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(VolumeTypeAccessController, self).__init__()
|
super(VolumeTypeAccessController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeAccessTemplate)
|
|
||||||
def index(self, req, type_id):
|
def index(self, req, type_id):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
@ -123,8 +82,6 @@ class VolumeTypeActionController(wsgi.Controller):
|
|||||||
def show(self, req, resp_obj, id):
|
def show(self, req, resp_obj, id):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if soft_authorize(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')
|
vol_type = req.cached_resource_by_id(id, name='types')
|
||||||
self._extend_vol_type(resp_obj.obj['volume_type'], vol_type)
|
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):
|
def index(self, req, resp_obj):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if soft_authorize(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']):
|
for vol_type_rval in list(resp_obj.obj['volume_types']):
|
||||||
type_id = vol_type_rval['id']
|
type_id = vol_type_rval['id']
|
||||||
vol_type = req.cached_resource_by_id(type_id, name='types')
|
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):
|
def detail(self, req, resp_obj):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if soft_authorize(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']):
|
for vol_type_rval in list(resp_obj.obj['volume_types']):
|
||||||
type_id = vol_type_rval['id']
|
type_id = vol_type_rval['id']
|
||||||
vol_type = req.cached_resource_by_id(type_id, name='types')
|
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):
|
def create(self, req, body, resp_obj):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
if soft_authorize(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']
|
type_id = resp_obj.obj['volume_type']['id']
|
||||||
vol_type = req.cached_resource_by_id(type_id, name='types')
|
vol_type = req.cached_resource_by_id(type_id, name='types')
|
||||||
self._extend_vol_type(resp_obj.obj['volume_type'], vol_type)
|
self._extend_vol_type(resp_obj.obj['volume_type'], vol_type)
|
||||||
@ -195,8 +146,6 @@ class Volume_type_access(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeTypeAccess"
|
name = "VolumeTypeAccess"
|
||||||
alias = "os-volume-type-access"
|
alias = "os-volume-type-access"
|
||||||
namespace = ("http://docs.openstack.org/volume/"
|
|
||||||
"ext/os-volume-type-access/api/v1")
|
|
||||||
updated = "2014-06-26T00:00:00Z"
|
updated = "2014-06-26T00:00:00Z"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -19,7 +19,6 @@ import webob
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import db
|
from cinder import db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -33,12 +32,6 @@ authorize = extensions.extension_authorizer('volume',
|
|||||||
CONTROL_LOCATION = ['front-end', 'back-end']
|
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):
|
class VolumeTypeEncryptionController(wsgi.Controller):
|
||||||
"""The volume type encryption API controller for the OpenStack API."""
|
"""The volume type encryption API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -89,7 +82,6 @@ class VolumeTypeEncryptionController(wsgi.Controller):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeEncryptionTemplate)
|
|
||||||
def index(self, req, type_id):
|
def index(self, req, type_id):
|
||||||
"""Returns the encryption specs for a given volume type."""
|
"""Returns the encryption specs for a given volume type."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -97,7 +89,6 @@ class VolumeTypeEncryptionController(wsgi.Controller):
|
|||||||
self._check_type(context, type_id)
|
self._check_type(context, type_id)
|
||||||
return self._get_volume_type_encryption(context, type_id)
|
return self._get_volume_type_encryption(context, type_id)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeEncryptionTemplate)
|
|
||||||
def create(self, req, type_id, body=None):
|
def create(self, req, type_id, body=None):
|
||||||
"""Create encryption specs for an existing volume type."""
|
"""Create encryption specs for an existing volume type."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -125,7 +116,6 @@ class VolumeTypeEncryptionController(wsgi.Controller):
|
|||||||
notifier.info(context, 'volume_type_encryption.create', notifier_info)
|
notifier.info(context, 'volume_type_encryption.create', notifier_info)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeEncryptionTemplate)
|
|
||||||
def update(self, req, type_id, id, body=None):
|
def update(self, req, type_id, id, body=None):
|
||||||
"""Update encryption specs for a given volume type."""
|
"""Update encryption specs for a given volume type."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -153,7 +143,6 @@ class VolumeTypeEncryptionController(wsgi.Controller):
|
|||||||
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeEncryptionTemplate)
|
|
||||||
def show(self, req, type_id, id):
|
def show(self, req, type_id, id):
|
||||||
"""Return a single encryption item."""
|
"""Return a single encryption item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -190,8 +179,6 @@ class Volume_type_encryption(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeTypeEncryption"
|
name = "VolumeTypeEncryption"
|
||||||
alias = "encryption"
|
alias = "encryption"
|
||||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
|
||||||
"volume-type-encryption/api/v1")
|
|
||||||
updated = "2013-07-01T00:00:00+00:00"
|
updated = "2013-07-01T00:00:00+00:00"
|
||||||
|
|
||||||
def get_resources(self):
|
def get_resources(self):
|
||||||
|
@ -66,7 +66,6 @@ class Volume_unmanage(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
name = "VolumeUnmanage"
|
name = "VolumeUnmanage"
|
||||||
alias = "os-volume-unmanage"
|
alias = "os-volume-unmanage"
|
||||||
namespace = "http://docs.openstack.org/volume/ext/volume-unmanage/api/v1.1"
|
|
||||||
updated = "2012-05-31T00:00:00+00:00"
|
updated = "2012-05-31T00:00:00+00:00"
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
def get_controller_extensions(self):
|
||||||
|
@ -24,7 +24,6 @@ import webob.exc
|
|||||||
|
|
||||||
import cinder.api.openstack
|
import cinder.api.openstack
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _LE, _LI, _LW
|
from cinder.i18n import _LE, _LI, _LW
|
||||||
import cinder.policy
|
import cinder.policy
|
||||||
@ -49,12 +48,6 @@ class ExtensionDescriptor(object):
|
|||||||
# The alias for the extension, e.g., 'FOXNSOX'
|
# The alias for the extension, e.g., 'FOXNSOX'
|
||||||
alias = None
|
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.,
|
# The timestamp when the extension was last updated, e.g.,
|
||||||
# '2011-01-22T13:25:27-06:00'
|
# '2011-01-22T13:25:27-06:00'
|
||||||
updated = None
|
updated = None
|
||||||
@ -82,55 +75,6 @@ class ExtensionDescriptor(object):
|
|||||||
controller_exts = []
|
controller_exts = []
|
||||||
return 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):
|
class ExtensionsResource(wsgi.Resource):
|
||||||
|
|
||||||
@ -143,19 +87,16 @@ class ExtensionsResource(wsgi.Resource):
|
|||||||
ext_data['name'] = ext.name
|
ext_data['name'] = ext.name
|
||||||
ext_data['alias'] = ext.alias
|
ext_data['alias'] = ext.alias
|
||||||
ext_data['description'] = ext.__doc__
|
ext_data['description'] = ext.__doc__
|
||||||
ext_data['namespace'] = ext.namespace
|
|
||||||
ext_data['updated'] = ext.updated
|
ext_data['updated'] = ext.updated
|
||||||
ext_data['links'] = [] # TODO(dprince): implement extension links
|
ext_data['links'] = [] # TODO(dprince): implement extension links
|
||||||
return ext_data
|
return ext_data
|
||||||
|
|
||||||
@wsgi.serializers(xml=ExtensionsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
extensions = []
|
extensions = []
|
||||||
for _alias, ext in self.extension_manager.extensions.items():
|
for _alias, ext in self.extension_manager.extensions.items():
|
||||||
extensions.append(self._translate(ext))
|
extensions.append(self._translate(ext))
|
||||||
return dict(extensions=extensions)
|
return dict(extensions=extensions)
|
||||||
|
|
||||||
@wsgi.serializers(xml=ExtensionTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
try:
|
try:
|
||||||
# NOTE(dprince): the extensions alias is used as the 'id' for show
|
# 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 alias: %s', extension.alias)
|
||||||
LOG.debug('Ext description: %s',
|
LOG.debug('Ext description: %s',
|
||||||
' '.join(extension.__doc__.strip().split()))
|
' '.join(extension.__doc__.strip().split()))
|
||||||
LOG.debug('Ext namespace: %s', extension.namespace)
|
|
||||||
LOG.debug('Ext updated: %s', extension.updated)
|
LOG.debug('Ext updated: %s', extension.updated)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
LOG.exception(_LE("Exception loading extension."))
|
LOG.exception(_LE("Exception loading extension."))
|
||||||
|
@ -38,11 +38,11 @@ class APIMapper(routes.Mapper):
|
|||||||
|
|
||||||
def connect(self, *args, **kwargs):
|
def connect(self, *args, **kwargs):
|
||||||
# NOTE(inhye): Default the format part of a route to only accept json
|
# 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.
|
# in the url.
|
||||||
kwargs.setdefault('requirements', {})
|
kwargs.setdefault('requirements', {})
|
||||||
if not kwargs['requirements'].get('format'):
|
if not kwargs['requirements'].get('format'):
|
||||||
kwargs['requirements']['format'] = 'json|xml'
|
kwargs['requirements']['format'] = 'json'
|
||||||
return routes.Mapper.connect(self, *args, **kwargs)
|
return routes.Mapper.connect(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,12 +18,8 @@ import functools
|
|||||||
import inspect
|
import inspect
|
||||||
import math
|
import math
|
||||||
import time
|
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 log as logging
|
||||||
from oslo_log import versionutils
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
@ -41,26 +37,16 @@ from cinder import utils
|
|||||||
from cinder.wsgi import common as wsgi
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORTED_CONTENT_TYPES = (
|
SUPPORTED_CONTENT_TYPES = (
|
||||||
'application/json',
|
'application/json',
|
||||||
'application/vnd.openstack.volume+json',
|
'application/vnd.openstack.volume+json',
|
||||||
'application/xml',
|
|
||||||
'application/vnd.openstack.volume+xml',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_MEDIA_TYPE_MAP = {
|
_MEDIA_TYPE_MAP = {
|
||||||
'application/vnd.openstack.volume+json': 'json',
|
'application/vnd.openstack.volume+json': 'json',
|
||||||
'application/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)}
|
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):
|
class DictSerializer(ActionDispatcher):
|
||||||
"""Default request body serialization."""
|
"""Default request body serialization."""
|
||||||
|
|
||||||
@ -478,111 +375,6 @@ class JSONDictSerializer(DictSerializer):
|
|||||||
return jsonutils.dump_as_bytes(data)
|
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):
|
def serializers(**serializers):
|
||||||
"""Attaches serializers to a method.
|
"""Attaches serializers to a method.
|
||||||
|
|
||||||
@ -784,15 +576,6 @@ def action_peek_json(body):
|
|||||||
return list(decoded.keys())[0]
|
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):
|
class ResourceExceptionHandler(object):
|
||||||
"""Context manager to handle Resource exceptions.
|
"""Context manager to handle Resource exceptions.
|
||||||
|
|
||||||
@ -859,16 +642,13 @@ class Resource(wsgi.Application):
|
|||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
default_deserializers = dict(xml=XMLDeserializer,
|
default_deserializers = dict(json=JSONDeserializer)
|
||||||
json=JSONDeserializer)
|
|
||||||
default_deserializers.update(deserializers)
|
default_deserializers.update(deserializers)
|
||||||
|
|
||||||
self.default_deserializers = default_deserializers
|
self.default_deserializers = default_deserializers
|
||||||
self.default_serializers = dict(xml=XMLDictSerializer,
|
self.default_serializers = dict(json=JSONDictSerializer)
|
||||||
json=JSONDictSerializer)
|
|
||||||
|
|
||||||
self.action_peek = dict(xml=action_peek_xml,
|
self.action_peek = dict(json=action_peek_json)
|
||||||
json=action_peek_json)
|
|
||||||
self.action_peek.update(action_peek or {})
|
self.action_peek.update(action_peek or {})
|
||||||
|
|
||||||
# Copy over the actions dictionary
|
# Copy over the actions dictionary
|
||||||
@ -1542,25 +1322,11 @@ class Fault(webob.exc.HTTPException):
|
|||||||
req.api_version_request.get_string())
|
req.api_version_request.get_string())
|
||||||
self.wrapped_exc.headers['Vary'] = API_VERSION_REQUEST_HEADER
|
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()
|
content_type = req.best_match_content_type()
|
||||||
serializer = {
|
serializer = {
|
||||||
'application/xml': xml_serializer,
|
|
||||||
'application/json': JSONDictSerializer(),
|
'application/json': JSONDictSerializer(),
|
||||||
}[content_type]
|
}[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)
|
body = serializer.serialize(fault_data)
|
||||||
if isinstance(body, six.text_type):
|
if isinstance(body, six.text_type):
|
||||||
body = body.encode('utf-8')
|
body = body.encode('utf-8')
|
||||||
@ -1606,7 +1372,6 @@ class OverLimitFault(webob.exc.HTTPException):
|
|||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
"""Serializes the wrapped exception conforming to our error format."""
|
"""Serializes the wrapped exception conforming to our error format."""
|
||||||
content_type = request.best_match_content_type()
|
content_type = request.best_match_content_type()
|
||||||
metadata = {"attributes": {"overLimitFault": "code"}}
|
|
||||||
|
|
||||||
def translate(msg):
|
def translate(msg):
|
||||||
locale = request.best_match_language()
|
locale = request.best_match_language()
|
||||||
@ -1617,9 +1382,7 @@ class OverLimitFault(webob.exc.HTTPException):
|
|||||||
self.content['overLimitFault']['details'] = \
|
self.content['overLimitFault']['details'] = \
|
||||||
translate(self.content['overLimitFault']['details'])
|
translate(self.content['overLimitFault']['details'])
|
||||||
|
|
||||||
xml_serializer = XMLDictSerializer(metadata, XML_NS_V2)
|
|
||||||
serializer = {
|
serializer = {
|
||||||
'application/xml': xml_serializer,
|
|
||||||
'application/json': JSONDictSerializer(),
|
'application/json': JSONDictSerializer(),
|
||||||
}[content_type]
|
}[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:
|
# The MIME type for the response is determined in one of two ways:
|
||||||
# 1) URL path suffix (eg /servers/detail.json)
|
# 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:
|
# The API version is determined in one of three ways:
|
||||||
# 1) URL path prefix (eg /v1.1/tenant/servers/detail)
|
# 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)
|
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:
|
if not app:
|
||||||
app = self._content_type_strategy(host, port, environ)
|
app = self._content_type_strategy(host, port, environ)
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ import webob.exc
|
|||||||
|
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import limits as limits_views
|
from cinder.api.views import limits as limits_views
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import quota
|
from cinder import quota
|
||||||
from cinder.wsgi import common as base_wsgi
|
from cinder.wsgi import common as base_wsgi
|
||||||
@ -47,38 +46,9 @@ PER_HOUR = 60 * 60
|
|||||||
PER_DAY = 60 * 60 * 24
|
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):
|
class LimitsController(wsgi.Controller):
|
||||||
"""Controller for accessing limits in the OpenStack API."""
|
"""Controller for accessing limits in the OpenStack API."""
|
||||||
|
|
||||||
@wsgi.serializers(xml=LimitsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Return all global and rate limit information."""
|
"""Return all global and rate limit information."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
import webob
|
import webob
|
||||||
from webob import exc
|
from webob import exc
|
||||||
|
|
||||||
from cinder.api import common
|
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -39,14 +38,11 @@ class Controller(wsgi.Controller):
|
|||||||
raise exc.HTTPNotFound(explanation=msg)
|
raise exc.HTTPNotFound(explanation=msg)
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
def index(self, req, snapshot_id):
|
def index(self, req, snapshot_id):
|
||||||
"""Returns the list of metadata for a given snapshot."""
|
"""Returns the list of metadata for a given snapshot."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
return {'metadata': self._get_metadata(context, snapshot_id)}
|
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):
|
def create(self, req, snapshot_id, body):
|
||||||
try:
|
try:
|
||||||
metadata = body['metadata']
|
metadata = body['metadata']
|
||||||
@ -63,8 +59,6 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
return {'metadata': new_metadata}
|
return {'metadata': new_metadata}
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
|
||||||
def update(self, req, snapshot_id, id, body):
|
def update(self, req, snapshot_id, id, body):
|
||||||
try:
|
try:
|
||||||
meta_item = body['meta']
|
meta_item = body['meta']
|
||||||
@ -88,8 +82,6 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
return {'meta': meta_item}
|
return {'meta': meta_item}
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
|
||||||
def update_all(self, req, snapshot_id, body):
|
def update_all(self, req, snapshot_id, body):
|
||||||
try:
|
try:
|
||||||
metadata = body['metadata']
|
metadata = body['metadata']
|
||||||
@ -128,7 +120,6 @@ class Controller(wsgi.Controller):
|
|||||||
except exception.InvalidVolumeMetadataSize as error:
|
except exception.InvalidVolumeMetadataSize as error:
|
||||||
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
|
||||||
def show(self, req, snapshot_id, id):
|
def show(self, req, snapshot_id, id):
|
||||||
"""Return a single metadata item."""
|
"""Return a single metadata item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -22,7 +22,6 @@ from webob import exc
|
|||||||
|
|
||||||
from cinder.api import common
|
from cinder.api import common
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
@ -61,33 +60,6 @@ def _translate_snapshot_summary_view(snapshot):
|
|||||||
return d
|
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):
|
class SnapshotsController(wsgi.Controller):
|
||||||
"""The Snapshots API controller for the OpenStack API."""
|
"""The Snapshots API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -96,7 +68,6 @@ class SnapshotsController(wsgi.Controller):
|
|||||||
self.ext_mgr = ext_mgr
|
self.ext_mgr = ext_mgr
|
||||||
super(SnapshotsController, self).__init__()
|
super(SnapshotsController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about the given snapshot."""
|
"""Return data about the given snapshot."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -122,12 +93,10 @@ class SnapshotsController(wsgi.Controller):
|
|||||||
raise exc.HTTPNotFound()
|
raise exc.HTTPNotFound()
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns a summary list of snapshots."""
|
"""Returns a summary list of snapshots."""
|
||||||
return self._items(req, entity_maker=_translate_snapshot_summary_view)
|
return self._items(req, entity_maker=_translate_snapshot_summary_view)
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotsTemplate)
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of snapshots."""
|
"""Returns a detailed list of snapshots."""
|
||||||
return self._items(req, entity_maker=_translate_snapshot_detail_view)
|
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]
|
res = [entity_maker(snapshot) for snapshot in limited_list]
|
||||||
return {'snapshots': res}
|
return {'snapshots': res}
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotTemplate)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Creates a new snapshot."""
|
"""Creates a new snapshot."""
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
@ -204,7 +172,6 @@ class SnapshotsController(wsgi.Controller):
|
|||||||
|
|
||||||
return {'snapshot': retval}
|
return {'snapshot': retval}
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotTemplate)
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Update a snapshot."""
|
"""Update a snapshot."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -19,40 +19,15 @@ from webob import exc
|
|||||||
|
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import types as views_types
|
from cinder.api.views import types as views_types
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.volume import volume_types
|
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):
|
class VolumeTypesController(wsgi.Controller):
|
||||||
"""The volume types API controller for the OpenStack API."""
|
"""The volume types API controller for the OpenStack API."""
|
||||||
|
|
||||||
_view_builder_class = views_types.ViewBuilder
|
_view_builder_class = views_types.ViewBuilder
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypesTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns the list of volume types."""
|
"""Returns the list of volume types."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -61,7 +36,6 @@ class VolumeTypesController(wsgi.Controller):
|
|||||||
req.cache_resource(vol_types, name='types')
|
req.cache_resource(vol_types, name='types')
|
||||||
return self._view_builder.index(req, vol_types)
|
return self._view_builder.index(req, vol_types)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return a single volume type item."""
|
"""Return a single volume type item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
import webob
|
import webob
|
||||||
from webob import exc
|
from webob import exc
|
||||||
|
|
||||||
from cinder.api import common
|
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -39,14 +38,11 @@ class Controller(wsgi.Controller):
|
|||||||
raise exc.HTTPNotFound(explanation=msg)
|
raise exc.HTTPNotFound(explanation=msg)
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
def index(self, req, volume_id):
|
def index(self, req, volume_id):
|
||||||
"""Returns the list of metadata for a given volume."""
|
"""Returns the list of metadata for a given volume."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
return {'metadata': self._get_metadata(context, volume_id)}
|
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):
|
def create(self, req, volume_id, body):
|
||||||
try:
|
try:
|
||||||
metadata = body['metadata']
|
metadata = body['metadata']
|
||||||
@ -63,8 +59,6 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
return {'metadata': new_metadata}
|
return {'metadata': new_metadata}
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
|
||||||
def update(self, req, volume_id, id, body):
|
def update(self, req, volume_id, id, body):
|
||||||
try:
|
try:
|
||||||
meta_item = body['meta']
|
meta_item = body['meta']
|
||||||
@ -88,8 +82,6 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
return {'meta': meta_item}
|
return {'meta': meta_item}
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
|
||||||
def update_all(self, req, volume_id, body):
|
def update_all(self, req, volume_id, body):
|
||||||
try:
|
try:
|
||||||
metadata = body['metadata']
|
metadata = body['metadata']
|
||||||
@ -128,7 +120,6 @@ class Controller(wsgi.Controller):
|
|||||||
except exception.InvalidVolumeMetadataSize as error:
|
except exception.InvalidVolumeMetadataSize as error:
|
||||||
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
|
||||||
def show(self, req, volume_id, id):
|
def show(self, req, volume_id, id):
|
||||||
"""Return a single metadata item."""
|
"""Return a single metadata item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -24,7 +24,6 @@ from webob import exc
|
|||||||
|
|
||||||
from cinder.api import common
|
from cinder.api import common
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
@ -126,97 +125,6 @@ def _translate_volume_summary_view(context, vol, image_id=None):
|
|||||||
return d
|
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):
|
class VolumeController(wsgi.Controller):
|
||||||
"""The Volumes API controller for the OpenStack API."""
|
"""The Volumes API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -225,7 +133,6 @@ class VolumeController(wsgi.Controller):
|
|||||||
self.ext_mgr = ext_mgr
|
self.ext_mgr = ext_mgr
|
||||||
super(VolumeController, self).__init__()
|
super(VolumeController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about the given volume."""
|
"""Return data about the given volume."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -253,12 +160,10 @@ class VolumeController(wsgi.Controller):
|
|||||||
raise exc.HTTPNotFound()
|
raise exc.HTTPNotFound()
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumesTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns a summary list of volumes."""
|
"""Returns a summary list of volumes."""
|
||||||
return self._items(req, entity_maker=_translate_volume_summary_view)
|
return self._items(req, entity_maker=_translate_volume_summary_view)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumesTemplate)
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of volumes."""
|
"""Returns a detailed list of volumes."""
|
||||||
return self._items(req, entity_maker=_translate_volume_detail_view)
|
return self._items(req, entity_maker=_translate_volume_detail_view)
|
||||||
@ -312,8 +217,6 @@ class VolumeController(wsgi.Controller):
|
|||||||
|
|
||||||
return image_uuid
|
return image_uuid
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTemplate)
|
|
||||||
@wsgi.deserializers(xml=CreateDeserializer)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Creates a new volume."""
|
"""Creates a new volume."""
|
||||||
if not self.is_valid_body(body, '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 volume search options allowed by non-admin."""
|
||||||
return ('display_name', 'status', 'metadata')
|
return ('display_name', 'status', 'metadata')
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTemplate)
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Update a volume."""
|
"""Update a volume."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -31,7 +31,6 @@ import webob.exc
|
|||||||
|
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import limits as limits_views
|
from cinder.api.views import limits as limits_views
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import quota
|
from cinder import quota
|
||||||
from cinder.wsgi import common as base_wsgi
|
from cinder.wsgi import common as base_wsgi
|
||||||
@ -47,38 +46,9 @@ PER_HOUR = 60 * 60
|
|||||||
PER_DAY = 60 * 60 * 24
|
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):
|
class LimitsController(wsgi.Controller):
|
||||||
"""Controller for accessing limits in the OpenStack API."""
|
"""Controller for accessing limits in the OpenStack API."""
|
||||||
|
|
||||||
@wsgi.serializers(xml=LimitsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Return all global and rate limit information."""
|
"""Return all global and rate limit information."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
import webob
|
import webob
|
||||||
from webob import exc
|
from webob import exc
|
||||||
|
|
||||||
from cinder.api import common
|
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -39,14 +38,11 @@ class Controller(wsgi.Controller):
|
|||||||
raise exc.HTTPNotFound(explanation=msg)
|
raise exc.HTTPNotFound(explanation=msg)
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
def index(self, req, snapshot_id):
|
def index(self, req, snapshot_id):
|
||||||
"""Returns the list of metadata for a given snapshot."""
|
"""Returns the list of metadata for a given snapshot."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
return {'metadata': self._get_metadata(context, snapshot_id)}
|
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):
|
def create(self, req, snapshot_id, body):
|
||||||
self.assert_valid_body(body, 'metadata')
|
self.assert_valid_body(body, 'metadata')
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -59,8 +55,6 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
return {'metadata': new_metadata}
|
return {'metadata': new_metadata}
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
|
||||||
def update(self, req, snapshot_id, id, body):
|
def update(self, req, snapshot_id, id, body):
|
||||||
self.assert_valid_body(body, 'meta')
|
self.assert_valid_body(body, 'meta')
|
||||||
meta_item = body['meta']
|
meta_item = body['meta']
|
||||||
@ -81,8 +75,6 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
return {'meta': meta_item}
|
return {'meta': meta_item}
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
|
||||||
def update_all(self, req, snapshot_id, body):
|
def update_all(self, req, snapshot_id, body):
|
||||||
self.assert_valid_body(body, 'metadata')
|
self.assert_valid_body(body, 'metadata')
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -118,7 +110,6 @@ class Controller(wsgi.Controller):
|
|||||||
except exception.InvalidVolumeMetadataSize as error:
|
except exception.InvalidVolumeMetadataSize as error:
|
||||||
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
raise exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
|
||||||
def show(self, req, snapshot_id, id):
|
def show(self, req, snapshot_id, id):
|
||||||
"""Return a single metadata item."""
|
"""Return a single metadata item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -24,7 +24,6 @@ from webob import exc
|
|||||||
from cinder.api import common
|
from cinder.api import common
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import snapshots as snapshot_views
|
from cinder.api.views import snapshots as snapshot_views
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
@ -35,33 +34,6 @@ from cinder.volume import utils as volume_utils
|
|||||||
LOG = logging.getLogger(__name__)
|
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):
|
class SnapshotsController(wsgi.Controller):
|
||||||
"""The Snapshots API controller for the OpenStack API."""
|
"""The Snapshots API controller for the OpenStack API."""
|
||||||
|
|
||||||
@ -72,7 +44,6 @@ class SnapshotsController(wsgi.Controller):
|
|||||||
self.ext_mgr = ext_mgr
|
self.ext_mgr = ext_mgr
|
||||||
super(SnapshotsController, self).__init__()
|
super(SnapshotsController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about the given snapshot."""
|
"""Return data about the given snapshot."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -99,12 +70,10 @@ class SnapshotsController(wsgi.Controller):
|
|||||||
|
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotsTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns a summary list of snapshots."""
|
"""Returns a summary list of snapshots."""
|
||||||
return self._items(req, is_detail=False)
|
return self._items(req, is_detail=False)
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotsTemplate)
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of snapshots."""
|
"""Returns a detailed list of snapshots."""
|
||||||
return self._items(req, is_detail=True)
|
return self._items(req, is_detail=True)
|
||||||
@ -145,7 +114,6 @@ class SnapshotsController(wsgi.Controller):
|
|||||||
return snapshots
|
return snapshots
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=SnapshotTemplate)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Creates a new snapshot."""
|
"""Creates a new snapshot."""
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
@ -200,7 +168,6 @@ class SnapshotsController(wsgi.Controller):
|
|||||||
|
|
||||||
return self._view_builder.detail(req, new_snapshot)
|
return self._view_builder.detail(req, new_snapshot)
|
||||||
|
|
||||||
@wsgi.serializers(xml=SnapshotTemplate)
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Update a snapshot."""
|
"""Update a snapshot."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -21,51 +21,23 @@ from webob import exc
|
|||||||
from cinder.api import common
|
from cinder.api import common
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.v2.views import types as views_types
|
from cinder.api.v2.views import types as views_types
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder.volume import volume_types
|
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):
|
class VolumeTypesController(wsgi.Controller):
|
||||||
"""The volume types API controller for the OpenStack API."""
|
"""The volume types API controller for the OpenStack API."""
|
||||||
|
|
||||||
_view_builder_class = views_types.ViewBuilder
|
_view_builder_class = views_types.ViewBuilder
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypesTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns the list of volume types."""
|
"""Returns the list of volume types."""
|
||||||
limited_types = self._get_volume_types(req)
|
limited_types = self._get_volume_types(req)
|
||||||
req.cache_resource(limited_types, name='types')
|
req.cache_resource(limited_types, name='types')
|
||||||
return self._view_builder.index(req, limited_types)
|
return self._view_builder.index(req, limited_types)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTypeTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return a single volume type item."""
|
"""Return a single volume type item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -42,14 +42,11 @@ class Controller(wsgi.Controller):
|
|||||||
raise webob.exc.HTTPNotFound(explanation=error.msg)
|
raise webob.exc.HTTPNotFound(explanation=error.msg)
|
||||||
return (volume, meta)
|
return (volume, meta)
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
def index(self, req, volume_id):
|
def index(self, req, volume_id):
|
||||||
"""Returns the list of metadata for a given volume."""
|
"""Returns the list of metadata for a given volume."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
return {'metadata': self._get_metadata(context, volume_id)}
|
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):
|
def create(self, req, volume_id, body):
|
||||||
self.assert_valid_body(body, 'metadata')
|
self.assert_valid_body(body, 'metadata')
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -62,8 +59,6 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
return {'metadata': new_metadata}
|
return {'metadata': new_metadata}
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
|
||||||
def update(self, req, volume_id, id, body):
|
def update(self, req, volume_id, id, body):
|
||||||
self.assert_valid_body(body, 'meta')
|
self.assert_valid_body(body, 'meta')
|
||||||
meta_item = body['meta']
|
meta_item = body['meta']
|
||||||
@ -84,8 +79,6 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
return {'meta': meta_item}
|
return {'meta': meta_item}
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
|
||||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
|
||||||
def update_all(self, req, volume_id, body):
|
def update_all(self, req, volume_id, body):
|
||||||
self.assert_valid_body(body, 'metadata')
|
self.assert_valid_body(body, 'metadata')
|
||||||
metadata = body['metadata']
|
metadata = body['metadata']
|
||||||
@ -122,7 +115,6 @@ class Controller(wsgi.Controller):
|
|||||||
except exception.InvalidVolumeMetadataSize as error:
|
except exception.InvalidVolumeMetadataSize as error:
|
||||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
||||||
|
|
||||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
|
||||||
def show(self, req, volume_id, id):
|
def show(self, req, volume_id, id):
|
||||||
"""Return a single metadata item."""
|
"""Return a single metadata item."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -25,7 +25,6 @@ from webob import exc
|
|||||||
from cinder.api import common
|
from cinder.api import common
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.v2.views import volumes as volume_views
|
from cinder.api.v2.views import volumes as volume_views
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import consistencygroup as consistencygroupAPI
|
from cinder import consistencygroup as consistencygroupAPI
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
@ -38,122 +37,6 @@ from cinder.volume import volume_types
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
class VolumeController(wsgi.Controller):
|
||||||
@ -167,7 +50,6 @@ class VolumeController(wsgi.Controller):
|
|||||||
self.ext_mgr = ext_mgr
|
self.ext_mgr = ext_mgr
|
||||||
super(VolumeController, self).__init__()
|
super(VolumeController, self).__init__()
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTemplate)
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about the given volume."""
|
"""Return data about the given volume."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
@ -197,12 +79,10 @@ class VolumeController(wsgi.Controller):
|
|||||||
raise exc.HTTPNotFound(explanation=error.msg)
|
raise exc.HTTPNotFound(explanation=error.msg)
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumesTemplate)
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Returns a summary list of volumes."""
|
"""Returns a summary list of volumes."""
|
||||||
return self._get_volumes(req, is_detail=False)
|
return self._get_volumes(req, is_detail=False)
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumesTemplate)
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Returns a detailed list of volumes."""
|
"""Returns a detailed list of volumes."""
|
||||||
return self._get_volumes(req, is_detail=True)
|
return self._get_volumes(req, is_detail=True)
|
||||||
@ -292,8 +172,6 @@ class VolumeController(wsgi.Controller):
|
|||||||
raise exc.HTTPBadRequest(explanation=msg)
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
@wsgi.response(202)
|
@wsgi.response(202)
|
||||||
@wsgi.serializers(xml=VolumeTemplate)
|
|
||||||
@wsgi.deserializers(xml=CreateDeserializer)
|
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Creates a new volume."""
|
"""Creates a new volume."""
|
||||||
self.assert_valid_body(body, 'volume')
|
self.assert_valid_body(body, 'volume')
|
||||||
@ -415,7 +293,6 @@ class VolumeController(wsgi.Controller):
|
|||||||
"""Return volume search options allowed by non-admin."""
|
"""Return volume search options allowed by non-admin."""
|
||||||
return CONF.query_volume_filters
|
return CONF.query_volume_filters
|
||||||
|
|
||||||
@wsgi.serializers(xml=VolumeTemplate)
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Update a volume."""
|
"""Update a volume."""
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
@ -16,9 +16,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from cinder.api import extensions
|
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 api_version_request
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.api.views import versions as views_versions
|
from cinder.api.views import versions as views_versions
|
||||||
from cinder.api import xmlutil
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -43,11 +40,6 @@ _MEDIA_TYPES = [{
|
|||||||
"type":
|
"type":
|
||||||
"application/vnd.openstack.volume+json;version=1",
|
"application/vnd.openstack.volume+json;version=1",
|
||||||
},
|
},
|
||||||
{"base":
|
|
||||||
"application/xml",
|
|
||||||
"type":
|
|
||||||
"application/vnd.openstack.volume+xml;version=1",
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
_KNOWN_VERSIONS = {
|
_KNOWN_VERSIONS = {
|
||||||
@ -138,149 +130,5 @@ class VersionsController(wsgi.Controller):
|
|||||||
return builder.build_versions(known_versions)
|
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():
|
def create_resource():
|
||||||
return wsgi.Resource(VersionsController())
|
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
|
@wsgi.extends
|
||||||
def show(self, req, resp_obj, id):
|
def show(self, req, resp_obj, id):
|
||||||
# NOTE: This only handles JSON responses.
|
# 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')
|
resp_obj.obj['flavor']['googoose'] = req.GET.get('chewing')
|
||||||
|
|
||||||
|
|
||||||
@ -54,7 +53,6 @@ class FoxInSocksFlavorBandsControllerExtension(wsgi.Controller):
|
|||||||
@wsgi.extends
|
@wsgi.extends
|
||||||
def show(self, req, resp_obj, id):
|
def show(self, req, resp_obj, id):
|
||||||
# NOTE: This only handles JSON responses.
|
# NOTE: This only handles JSON responses.
|
||||||
# You can use content type header to test for XML.
|
|
||||||
resp_obj.obj['big_bands'] = 'Pig Bands!'
|
resp_obj.obj['big_bands'] = 'Pig Bands!'
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,14 +15,12 @@
|
|||||||
|
|
||||||
|
|
||||||
import iso8601
|
import iso8601
|
||||||
from lxml import etree
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.v1 import router
|
from cinder.api.v1 import router
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder.tests.functional import functional_helpers
|
from cinder.tests.functional import functional_helpers
|
||||||
|
|
||||||
|
|
||||||
@ -75,8 +73,7 @@ class ExtensionControllerTest(ExtensionTestCase):
|
|||||||
(fox_ext, ) = [
|
(fox_ext, ) = [
|
||||||
x for x in data['extensions'] if x['alias'] == 'FOXNSOX']
|
x for x in data['extensions'] if x['alias'] == 'FOXNSOX']
|
||||||
self.assertEqual(
|
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',
|
'updated': '2011-01-22T13:25:27-06:00',
|
||||||
'description': 'The Fox In Socks Extension.',
|
'description': 'The Fox In Socks Extension.',
|
||||||
'alias': 'FOXNSOX',
|
'alias': 'FOXNSOX',
|
||||||
@ -98,8 +95,7 @@ class ExtensionControllerTest(ExtensionTestCase):
|
|||||||
|
|
||||||
data = jsonutils.loads(response.body)
|
data = jsonutils.loads(response.body)
|
||||||
self.assertEqual(
|
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",
|
"updated": "2011-01-22T13:25:27-06:00",
|
||||||
"description": "The Fox In Socks Extension.",
|
"description": "The Fox In Socks Extension.",
|
||||||
"alias": "FOXNSOX",
|
"alias": "FOXNSOX",
|
||||||
@ -111,55 +107,6 @@ class ExtensionControllerTest(ExtensionTestCase):
|
|||||||
response = request.get_response(app)
|
response = request.get_response(app)
|
||||||
self.assertEqual(404, response.status_int)
|
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):
|
class StubExtensionManager(object):
|
||||||
"""Provides access to Tweedle Beetles."""
|
"""Provides access to Tweedle Beetles."""
|
||||||
@ -203,10 +150,6 @@ class ExtensionControllerIdFormatTest(ExtensionTestCase):
|
|||||||
response = request.get_response(app)
|
response = request.get_response(app)
|
||||||
return response.body
|
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):
|
def test_id_with_json_format(self):
|
||||||
result = self._bounce_id('foo.json')
|
result = self._bounce_id('foo.json')
|
||||||
self.assertEqual(b'foo', result)
|
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
|
import datetime
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
import cinder.api.contrib.availability_zones
|
import cinder.api.contrib.availability_zones
|
||||||
@ -60,31 +59,3 @@ class ControllerTestCase(cinder.test.TestCase):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
self.assertEqual(expected, actual)
|
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.
|
Tests for Backup code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import six
|
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
# needed for stubs to work
|
# needed for stubs to work
|
||||||
@ -141,25 +138,6 @@ class BackupsAPITestCase(test.TestCase):
|
|||||||
db.backup_destroy(context.get_admin_context(), backup_id)
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
||||||
db.volume_destroy(context.get_admin_context(), volume_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):
|
def test_show_backup_with_backup_NotFound(self):
|
||||||
req = webob.Request.blank('/v2/fake/backups/9999')
|
req = webob.Request.blank('/v2/fake/backups/9999')
|
||||||
req.method = 'GET'
|
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_id2)
|
||||||
db.backup_destroy(context.get_admin_context(), backup_id1)
|
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):
|
def test_list_backups_with_limit(self):
|
||||||
backup_id1 = self._create_backup()
|
backup_id1 = self._create_backup()
|
||||||
backup_id2 = 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_id2)
|
||||||
db.backup_destroy(context.get_admin_context(), backup_id1)
|
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):
|
def test_list_backups_detail_with_limit_and_sort_args(self):
|
||||||
backup_id1 = self._create_backup()
|
backup_id1 = self._create_backup()
|
||||||
backup_id2 = self._create_backup()
|
backup_id2 = self._create_backup()
|
||||||
@ -736,39 +601,6 @@ class BackupsAPITestCase(test.TestCase):
|
|||||||
self.assertEqual(400, res.status_int)
|
self.assertEqual(400, res.status_int)
|
||||||
self.assertIsNotNone(res_dict['badRequest']['message'])
|
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):
|
def test_create_backup_with_invalid_snapshot(self):
|
||||||
volume_id = utils.create_volume(self.context, size=5,
|
volume_id = utils.create_volume(self.context, size=5,
|
||||||
status='available')['id']
|
status='available')['id']
|
||||||
@ -1311,37 +1143,6 @@ class BackupsAPITestCase(test.TestCase):
|
|||||||
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
|
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
|
||||||
self.assertEqual(volume_name, res_dict['restore']['volume_name'])
|
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):
|
def test_restore_backup_with_no_body(self):
|
||||||
# omit body from the request
|
# omit body from the request
|
||||||
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
||||||
@ -1774,36 +1575,6 @@ class BackupsAPITestCase(test.TestCase):
|
|||||||
res_dict['backup-record']['backup_url'])
|
res_dict['backup-record']['backup_url'])
|
||||||
db.backup_destroy(context.get_admin_context(), backup_id)
|
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):
|
def test_export_record_with_bad_backup_id(self):
|
||||||
|
|
||||||
ctx = context.RequestContext('admin', 'fake', is_admin=True)
|
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)
|
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')
|
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
||||||
def test_import_record_with_no_backup_services(self,
|
def test_import_record_with_no_backup_services(self,
|
||||||
_mock_list_services):
|
_mock_list_services):
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
Tests for cgsnapshot code.
|
Tests for cgsnapshot code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import webob
|
import webob
|
||||||
@ -71,30 +69,6 @@ class CgsnapshotsAPITestCase(test.TestCase):
|
|||||||
volume_id)
|
volume_id)
|
||||||
consistencygroup.destroy()
|
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):
|
def test_show_cgsnapshot_with_cgsnapshot_NotFound(self):
|
||||||
req = webob.Request.blank('/v2/fake/cgsnapshots/9999')
|
req = webob.Request.blank('/v2/fake/cgsnapshots/9999')
|
||||||
req.method = 'GET'
|
req.method = 'GET'
|
||||||
@ -146,42 +120,6 @@ class CgsnapshotsAPITestCase(test.TestCase):
|
|||||||
volume_id)
|
volume_id)
|
||||||
consistencygroup.destroy()
|
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):
|
def test_list_cgsnapshots_detail_json(self):
|
||||||
consistencygroup = utils.create_consistencygroup(self.context)
|
consistencygroup = utils.create_consistencygroup(self.context)
|
||||||
volume_id = utils.create_volume(self.context,
|
volume_id = utils.create_volume(self.context,
|
||||||
@ -236,71 +174,6 @@ class CgsnapshotsAPITestCase(test.TestCase):
|
|||||||
volume_id)
|
volume_id)
|
||||||
consistencygroup.destroy()
|
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(
|
@mock.patch(
|
||||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||||
def test_create_cgsnapshot_json(self, mock_validate):
|
def test_create_cgsnapshot_json(self, mock_validate):
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
Tests for consistency group code.
|
Tests for consistency group code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
@ -94,21 +92,6 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
|
|||||||
|
|
||||||
consistencygroup.destroy()
|
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):
|
def test_show_consistencygroup_with_consistencygroup_NotFound(self):
|
||||||
req = webob.Request.blank('/v2/fake/consistencygroups/9999')
|
req = webob.Request.blank('/v2/fake/consistencygroups/9999')
|
||||||
req.method = 'GET'
|
req.method = 'GET'
|
||||||
@ -172,32 +155,6 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
|
|||||||
consistencygroup2.destroy()
|
consistencygroup2.destroy()
|
||||||
consistencygroup3.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)
|
@ddt.data(False, True)
|
||||||
def test_list_consistencygroups_with_limit(self, is_detail):
|
def test_list_consistencygroups_with_limit(self, is_detail):
|
||||||
consistencygroup1 = self._create_consistencygroup()
|
consistencygroup1 = self._create_consistencygroup()
|
||||||
@ -395,73 +352,6 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
|
|||||||
consistencygroup2.destroy()
|
consistencygroup2.destroy()
|
||||||
consistencygroup3.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(
|
@mock.patch(
|
||||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||||
def test_create_consistencygroup_json(self, mock_validate):
|
def test_create_consistencygroup_json(self, mock_validate):
|
||||||
|
@ -14,12 +14,10 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api.contrib import extended_snapshot_attributes
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder.tests.unit.api import fakes
|
from cinder.tests.unit.api import fakes
|
||||||
@ -107,15 +105,3 @@ class ExtendedSnapshotAttributesTest(test.TestCase):
|
|||||||
self.assertSnapshotAttributes(snapshot,
|
self.assertSnapshotAttributes(snapshot,
|
||||||
project_id='fake',
|
project_id='fake',
|
||||||
progress='0%')
|
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
|
import datetime
|
||||||
|
|
||||||
from iso8601 import iso8601
|
from iso8601 import iso8601
|
||||||
from lxml import etree
|
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
@ -161,51 +160,3 @@ class HostTestCase(test.TestCase):
|
|||||||
self.assertRaises(webob.exc.HTTPNotFound,
|
self.assertRaises(webob.exc.HTTPNotFound,
|
||||||
self.controller.show,
|
self.controller.show,
|
||||||
self.req, dest)
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import mock
|
import mock
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api.contrib import qos_specs_manage
|
from cinder.api.contrib import qos_specs_manage
|
||||||
from cinder.api import xmlutil
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import db
|
from cinder import db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
@ -186,28 +182,6 @@ class QoSSpecManageApiTest(test.TestCase):
|
|||||||
expected_names = ['qos_specs_1', 'qos_specs_2', 'qos_specs_3']
|
expected_names = ['qos_specs_1', 'qos_specs_2', 'qos_specs_3']
|
||||||
self.assertEqual(set(expected_names), names)
|
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):
|
def test_index_with_limit(self):
|
||||||
url = '/v2/%s/qos-specs?limit=2' % fake.project_id
|
url = '/v2/%s/qos-specs?limit=2' % fake.project_id
|
||||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
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('1', res_dict['qos_specs']['id'])
|
||||||
self.assertEqual('qos_specs_1', res_dict['qos_specs']['name'])
|
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',
|
@mock.patch('cinder.volume.qos_specs.get_associations',
|
||||||
side_effect=return_get_qos_associations)
|
side_effect=return_get_qos_associations)
|
||||||
def test_get_associations(self, mock_get_assciations):
|
def test_get_associations(self, mock_get_assciations):
|
||||||
@ -567,29 +518,6 @@ class QoSSpecManageApiTest(test.TestCase):
|
|||||||
self.assertEqual('FakeVolTypeID',
|
self.assertEqual('FakeVolTypeID',
|
||||||
res['qos_associations'][0]['id'])
|
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',
|
@mock.patch('cinder.volume.qos_specs.get_associations',
|
||||||
side_effect=return_get_qos_associations)
|
side_effect=return_get_qos_associations)
|
||||||
def test_get_associations_not_found(self, mock_get_assciations):
|
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')
|
'/v2/fake/qos-specs/222/disassociate_all')
|
||||||
self.assertRaises(webob.exc.HTTPInternalServerError,
|
self.assertRaises(webob.exc.HTTPInternalServerError,
|
||||||
self.controller.disassociate_all, req, '222')
|
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
|
import mock
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
@ -1039,23 +1037,3 @@ class QuotaSetsControllerNestedQuotasTest(QuotaSetsControllerTestBase):
|
|||||||
quota_limit['volumes'] = 5
|
quota_limit['volumes'] = 5
|
||||||
self.controller.update(self.req, self.B.id, body)
|
self.controller.update(self.req, self.B.id, body)
|
||||||
self._assert_quota_show(self.A.id, res, allocated=6, in_use=1, limit=7)
|
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
|
import mock
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
|
|
||||||
@ -152,23 +151,3 @@ class QuotaClassSetsControllerTest(test.TestCase):
|
|||||||
request_body=body,
|
request_body=body,
|
||||||
tenant_id=None),
|
tenant_id=None),
|
||||||
result)
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import mock
|
import mock
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
@ -266,35 +265,3 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
def test_create_invalid_too_many_key(self):
|
def test_create_invalid_too_many_key(self):
|
||||||
body = {"key1": "value1", "ke/y2": "value2", "key3": "value3"}
|
body = {"key1": "value1", "ke/y2": "value2", "key3": "value3"}
|
||||||
self._extra_specs_create_bad_body(body=body)
|
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
|
import uuid
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
@ -119,27 +118,3 @@ class VolumeHostAttributeTest(test.TestCase):
|
|||||||
res = req.get_response(app())
|
res = req.get_response(app())
|
||||||
vol = jsonutils.loads(res.body)['volumes']
|
vol = jsonutils.loads(res.body)['volumes']
|
||||||
self.assertNotIn('os-vol-host-attr:host', vol[0])
|
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.
|
# under the License.
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api import common
|
|
||||||
from cinder.api.contrib import volume_image_metadata
|
from cinder.api.contrib import volume_image_metadata
|
||||||
from cinder.api.openstack import wsgi
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import db
|
from cinder import db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
@ -330,34 +327,3 @@ class VolumeImageMetadataTest(test.TestCase):
|
|||||||
self.assertEqual(200, res.status_int)
|
self.assertEqual(200, res.status_int)
|
||||||
self.assertEqual(fake_image_metadata,
|
self.assertEqual(fake_image_metadata,
|
||||||
jsonutils.loads(res.body)["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
|
import uuid
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
@ -121,33 +120,3 @@ class VolumeMigStatusAttributeTest(test.TestCase):
|
|||||||
vol = jsonutils.loads(res.body)['volumes']
|
vol = jsonutils.loads(res.body)['volumes']
|
||||||
self.assertNotIn('os-vol-mig-status-attr:migstat', vol[0])
|
self.assertNotIn('os-vol-mig-status-attr:migstat', vol[0])
|
||||||
self.assertNotIn('os-vol-mig-status-attr:name_id', 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
|
import uuid
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
@ -102,27 +101,3 @@ class VolumeTenantAttributeTest(test.TestCase):
|
|||||||
res = req.get_response(app())
|
res = req.get_response(app())
|
||||||
vol = jsonutils.loads(res.body)['volumes']
|
vol = jsonutils.loads(res.body)['volumes']
|
||||||
self.assertNotIn('os-vol-tenant-attr:tenant_id', vol[0])
|
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
|
import mock
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import six
|
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api.contrib import volume_transfer
|
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.transfer_destroy(context.get_admin_context(), transfer['id'])
|
||||||
db.volume_destroy(context.get_admin_context(), volume_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):
|
def test_show_transfer_with_transfer_NotFound(self):
|
||||||
req = webob.Request.blank('/v2/fake/os-volume-transfer/1234')
|
req = webob.Request.blank('/v2/fake/os-volume-transfer/1234')
|
||||||
req.method = 'GET'
|
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_1)
|
||||||
db.volume_destroy(context.get_admin_context(), volume_id_2)
|
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):
|
def test_list_transfers_detail_json(self):
|
||||||
volume_id_1 = self._create_volume(size=5)
|
volume_id_1 = self._create_volume(size=5)
|
||||||
volume_id_2 = 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_2)
|
||||||
db.volume_destroy(context.get_admin_context(), volume_id_1)
|
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):
|
def test_list_transfers_with_all_tenants(self):
|
||||||
volume_id_1 = self._create_volume(size=5)
|
volume_id_1 = self._create_volume(size=5)
|
||||||
volume_id_2 = self._create_volume(size=5, project_id='fake1')
|
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)
|
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):
|
def test_create_transfer_with_no_body(self):
|
||||||
req = webob.Request.blank('/v2/fake/os-volume-transfer')
|
req = webob.Request.blank('/v2/fake/os-volume-transfer')
|
||||||
req.body = jsonutils.dump_as_bytes(None)
|
req.body = jsonutils.dump_as_bytes(None)
|
||||||
@ -433,34 +320,6 @@ class VolumeTransferAPITestCase(test.TestCase):
|
|||||||
# cleanup
|
# cleanup
|
||||||
svc.stop()
|
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):
|
def test_accept_transfer_with_no_body(self):
|
||||||
volume_id = self._create_volume(size=5)
|
volume_id = self._create_volume(size=5)
|
||||||
transfer = self._create_transfer(volume_id)
|
transfer = self._create_transfer(volume_id)
|
||||||
|
@ -205,25 +205,6 @@ class VolumeTypeEncryptionTest(test.TestCase):
|
|||||||
self._create('fake_cipher', 'front-end', 128, 'fake_encryptor')
|
self._create('fake_cipher', 'front-end', 128, 'fake_encryptor')
|
||||||
self.assertTrue(mock_validate_integer.called)
|
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):
|
def test_create_invalid_volume_type(self):
|
||||||
volume_type = self._default_volume_type
|
volume_type = self._default_volume_type
|
||||||
body = {"encryption": stub_volume_type_encryption()}
|
body = {"encryption": stub_volume_type_encryption()}
|
||||||
|
@ -12,33 +12,15 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo_i18n import fixture as i18n_fixture
|
from oslo_i18n import fixture as i18n_fixture
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import six
|
|
||||||
import webob.dec
|
import webob.dec
|
||||||
|
|
||||||
from cinder.api import common
|
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.i18n import _
|
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
|
||||||
|
|
||||||
class TestCase(test.TestCase):
|
class TestFaults(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):
|
|
||||||
"""Tests covering `cinder.api.openstack.faults:Fault` class."""
|
"""Tests covering `cinder.api.openstack.faults:Fault` class."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -92,219 +74,7 @@ class TestFaults(TestCase):
|
|||||||
self.assertEqual("application/json", response.content_type)
|
self.assertEqual("application/json", response.content_type)
|
||||||
self.assertEqual(expected, actual)
|
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):
|
def test_fault_has_status_int(self):
|
||||||
"""Ensure the status_int is set correctly on faults."""
|
"""Ensure the status_int is set correctly on faults."""
|
||||||
fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='what?'))
|
fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='what?'))
|
||||||
self.assertEqual(400, fault.status_int)
|
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)
|
self.assertEqual("application/json", result)
|
||||||
|
|
||||||
def test_content_type_from_accept(self):
|
def test_content_type_from_accept(self):
|
||||||
for content_type in ('application/xml',
|
for content_type in ('application/json',
|
||||||
'application/vnd.openstack.volume+xml',
|
|
||||||
'application/json',
|
|
||||||
'application/vnd.openstack.volume+json'):
|
'application/vnd.openstack.volume+json'):
|
||||||
request = wsgi.Request.blank('/tests/123')
|
request = wsgi.Request.blank('/tests/123')
|
||||||
request.headers["Accept"] = content_type
|
request.headers["Accept"] = content_type
|
||||||
@ -51,21 +49,11 @@ class RequestTest(test.TestCase):
|
|||||||
|
|
||||||
def test_content_type_from_accept_best(self):
|
def test_content_type_from_accept_best(self):
|
||||||
request = wsgi.Request.blank('/tests/123')
|
request = wsgi.Request.blank('/tests/123')
|
||||||
request.headers["Accept"] = "application/xml, application/json"
|
request.headers["Accept"] = "application/json"
|
||||||
result = request.best_match_content_type()
|
result = request.best_match_content_type()
|
||||||
self.assertEqual("application/json", result)
|
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):
|
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')
|
request = wsgi.Request.blank('/tests/123.json')
|
||||||
result = request.best_match_content_type()
|
result = request.best_match_content_type()
|
||||||
self.assertEqual("application/json", result)
|
self.assertEqual("application/json", result)
|
||||||
@ -74,12 +62,6 @@ class RequestTest(test.TestCase):
|
|||||||
result = request.best_match_content_type()
|
result = request.best_match_content_type()
|
||||||
self.assertEqual("application/json", result)
|
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):
|
def test_content_type_accept_default(self):
|
||||||
request = wsgi.Request.blank('/tests/123.unsupported')
|
request = wsgi.Request.blank('/tests/123.unsupported')
|
||||||
request.headers["Accept"] = "application/unsupported1"
|
request.headers["Accept"] = "application/unsupported1"
|
||||||
@ -203,16 +185,6 @@ class DictSerializerTest(test.TestCase):
|
|||||||
self.assertEqual('', serializer.serialize({}, 'update'))
|
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):
|
class JSONDictSerializerTest(test.TestCase):
|
||||||
def test_json(self):
|
def test_json(self):
|
||||||
input_dict = dict(servers=dict(a=(2, 3)))
|
input_dict = dict(servers=dict(a=(2, 3)))
|
||||||
@ -252,62 +224,6 @@ class JSONDeserializerTest(test.TestCase):
|
|||||||
self.assertEqual(as_dict, deserializer.deserialize(data))
|
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):
|
class ResourceTest(test.TestCase):
|
||||||
def test_resource_call(self):
|
def test_resource_call(self):
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
@ -365,18 +281,6 @@ class ResourceTest(test.TestCase):
|
|||||||
'{"fooAction": true}')
|
'{"fooAction": true}')
|
||||||
self.assertEqual(controller._action_foo, method)
|
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):
|
def test_get_method_action_bad_body(self):
|
||||||
class Controller(wsgi.Controller):
|
class Controller(wsgi.Controller):
|
||||||
@wsgi.action('fooAction')
|
@wsgi.action('fooAction')
|
||||||
@ -400,18 +304,6 @@ class ResourceTest(test.TestCase):
|
|||||||
None, 'action', 'application/json',
|
None, 'action', 'application/json',
|
||||||
'{"barAction": true}')
|
'{"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):
|
def test_get_action_args(self):
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
def index(self, req, pants=None):
|
def index(self, req, pants=None):
|
||||||
@ -512,12 +404,7 @@ class ResourceTest(test.TestCase):
|
|||||||
def deserialize(self, body):
|
def deserialize(self, body):
|
||||||
return 'json'
|
return 'json'
|
||||||
|
|
||||||
class XMLDeserializer(object):
|
|
||||||
def deserialize(self, body):
|
|
||||||
return 'xml'
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
@wsgi.deserializers(xml=XMLDeserializer)
|
|
||||||
def index(self, req, pants=None):
|
def index(self, req, pants=None):
|
||||||
return pants
|
return pants
|
||||||
|
|
||||||
@ -527,26 +414,6 @@ class ResourceTest(test.TestCase):
|
|||||||
obj = resource.deserialize(controller.index, 'application/json', 'foo')
|
obj = resource.deserialize(controller.index, 'application/json', 'foo')
|
||||||
self.assertEqual('json', obj)
|
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):
|
def test_register_actions(self):
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
def index(self, req, pants=None):
|
def index(self, req, pants=None):
|
||||||
@ -925,57 +792,6 @@ class ResponseObjectTest(test.TestCase):
|
|||||||
robj = wsgi.ResponseObject({})
|
robj = wsgi.ResponseObject({})
|
||||||
self.assertEqual({}, robj.serializers)
|
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):
|
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.
|
Tests dealing with HTTP rate-limiting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import six
|
import six
|
||||||
from six.moves import http_client
|
from six.moves import http_client
|
||||||
@ -28,7 +25,6 @@ import webob
|
|||||||
|
|
||||||
from cinder.api.v1 import limits
|
from cinder.api.v1 import limits
|
||||||
from cinder.api import views
|
from cinder.api import views
|
||||||
from cinder.api import xmlutil
|
|
||||||
import cinder.context
|
import cinder.context
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder.tests.unit import fake_constants as fake
|
from cinder.tests.unit import fake_constants as fake
|
||||||
@ -276,26 +272,6 @@ class LimitMiddlewareTest(BaseLimitTestSuite):
|
|||||||
value = body["overLimitFault"]["details"].strip()
|
value = body["overLimitFault"]["details"].strip()
|
||||||
self.assertEqual(expected, value)
|
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):
|
class LimitTest(BaseLimitTestSuite):
|
||||||
"""Tests for the `limits.Limit` class."""
|
"""Tests for the `limits.Limit` class."""
|
||||||
@ -820,89 +796,3 @@ class LimitsViewBuilderTest(test.TestCase):
|
|||||||
rate_limits = []
|
rate_limits = []
|
||||||
output = self.view_builder.build(rate_limits, abs_limits)
|
output = self.view_builder.build(rate_limits, abs_limits)
|
||||||
self.assertDictMatch(expected_limits, output)
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_utils import timeutils
|
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api.v1 import snapshots
|
from cinder.api.v1 import snapshots
|
||||||
@ -379,56 +377,6 @@ class SnapshotApiTest(test.TestCase):
|
|||||||
self.assertEqual(1, len(res['snapshots']))
|
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):
|
class SnapshotsUnprocessableEntityTestCase(test.TestCase):
|
||||||
|
|
||||||
"""Tests of places we throw 422 Unprocessable Entity."""
|
"""Tests of places we throw 422 Unprocessable Entity."""
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
@ -166,45 +165,3 @@ class VolumeTypesApiTest(test.TestCase):
|
|||||||
description=None)
|
description=None)
|
||||||
self.assertDictMatch(expected_volume_type,
|
self.assertDictMatch(expected_volume_type,
|
||||||
output['volume_types'][i])
|
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 datetime
|
||||||
import iso8601
|
import iso8601
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import timeutils
|
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
@ -861,285 +859,6 @@ class VolumeApiTest(test.TestCase):
|
|||||||
marker=None)
|
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):
|
class VolumesUnprocessableEntityTestCase(test.TestCase):
|
||||||
|
|
||||||
"""Tests of places we throw 422 Unprocessable Entity from."""
|
"""Tests of places we throw 422 Unprocessable Entity from."""
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
Tests dealing with HTTP rate-limiting.
|
Tests dealing with HTTP rate-limiting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from xml.dom import minidom
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import six
|
import six
|
||||||
from six.moves import http_client
|
from six.moves import http_client
|
||||||
@ -28,7 +25,6 @@ import webob
|
|||||||
|
|
||||||
from cinder.api.v2 import limits
|
from cinder.api.v2 import limits
|
||||||
from cinder.api import views
|
from cinder.api import views
|
||||||
from cinder.api import xmlutil
|
|
||||||
import cinder.context
|
import cinder.context
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
|
||||||
@ -277,26 +273,6 @@ class LimitMiddlewareTest(BaseLimitTestSuite):
|
|||||||
value = body["overLimitFault"]["details"].strip()
|
value = body["overLimitFault"]["details"].strip()
|
||||||
self.assertEqual(expected, value)
|
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):
|
class LimitTest(BaseLimitTestSuite):
|
||||||
|
|
||||||
@ -826,85 +802,3 @@ class LimitsViewBuilderTest(test.TestCase):
|
|||||||
rate_limits = []
|
rate_limits = []
|
||||||
output = self.view_builder.build(rate_limits, abs_limits)
|
output = self.view_builder.build(rate_limits, abs_limits)
|
||||||
self.assertDictMatch(expected_limits, output)
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import timeutils
|
|
||||||
from six.moves.urllib import parse as urllib
|
from six.moves.urllib import parse as urllib
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
@ -582,61 +580,3 @@ class SnapshotApiTest(test.TestCase):
|
|||||||
def test_create_malformed_entity(self):
|
def test_create_malformed_entity(self):
|
||||||
body = {'snapshot': 'string'}
|
body = {'snapshot': 'string'}
|
||||||
self._create_snapshot_bad_body(body=body)
|
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
|
import uuid
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import six
|
import six
|
||||||
@ -506,46 +505,3 @@ class VolumeTypesApiTest(test.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertDictMatch(expected_volume_type,
|
self.assertDictMatch(expected_volume_type,
|
||||||
output['volume_types'][i])
|
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 datetime
|
||||||
import iso8601
|
import iso8601
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import timeutils
|
|
||||||
import six
|
import six
|
||||||
from six.moves import range
|
from six.moves import range
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
@ -1592,300 +1590,3 @@ class VolumeApiTest(test.TestCase):
|
|||||||
self.override_config('query_volume_filters', filter_list)
|
self.override_config('query_volume_filters', filter_list)
|
||||||
self.assertEqual(filter_list,
|
self.assertEqual(filter_list,
|
||||||
self.controller._get_volume_filter_options())
|
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",
|
"base": "application/json",
|
||||||
"type": "application/vnd.openstack.share+json;version=1",
|
"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)
|
accept = urlmap.Accept(arg)
|
||||||
self.assertEqual(('application/json', {'q': '0.7'}),
|
self.assertEqual(('application/json', {'q': '0.7'}),
|
||||||
accept.best_match(['application/json',
|
accept.best_match(['application/json',
|
||||||
'application/xml', 'text/html']))
|
'text/html']))
|
||||||
|
|
||||||
def test_match_mask_one_asterisk(self):
|
def test_match_mask_one_asterisk(self):
|
||||||
arg = 'text/*; q=0.7'
|
arg = 'text/*; q=0.7'
|
||||||
@ -77,14 +77,14 @@ class TestAccept(test.TestCase):
|
|||||||
self.assertEqual((None, {}), accept.best_match(['text/html']))
|
self.assertEqual((None, {}), accept.best_match(['text/html']))
|
||||||
|
|
||||||
def test_content_type_params(self):
|
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"
|
" text/html; q=0.3"
|
||||||
accept = urlmap.Accept(arg)
|
accept = urlmap.Accept(arg)
|
||||||
self.assertEqual({'q': '0.2'},
|
self.assertEqual({'q': '0.2'},
|
||||||
accept.content_type_params('application/json'))
|
accept.content_type_params('application/json'))
|
||||||
|
|
||||||
def test_content_type_params_wrong_content_type(self):
|
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)
|
accept = urlmap.Accept(arg)
|
||||||
self.assertEqual({}, accept.content_type_params('application/json'))
|
self.assertEqual({}, accept.content_type_params('application/json'))
|
||||||
|
|
||||||
@ -142,16 +142,6 @@ class TestURLMap(test.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestURLMap, self).setUp()
|
super(TestURLMap, self).setUp()
|
||||||
self.urlmap = urlmap.URLMap()
|
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):
|
def test_match_with_applications(self):
|
||||||
self.urlmap[('http://10.20.30.40:50', '/path/somepath')] = 'app'
|
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',
|
self.urlmap._match('http://20.30.40.50', '60',
|
||||||
'/path/somepath/elsepath'))
|
'/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):
|
def test_path_strategy_wrong_path_info(self):
|
||||||
self.assertEqual((None, None, None),
|
self.assertEqual((None, None, None),
|
||||||
self.urlmap._path_strategy('http://10.20.30.40', '50',
|
self.urlmap._path_strategy('http://10.20.30.40', '50',
|
||||||
'/resource'))
|
'/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):
|
def test_path_strategy_wrong_mime_type(self):
|
||||||
self.urlmap[('http://10.20.30.40:50', '/path/elsepath/')] = 'app'
|
self.urlmap[('http://10.20.30.40:50', '/path/elsepath/')] = 'app'
|
||||||
self.mox.StubOutWithMock(self.urlmap, '_munge_path')
|
self.mox.StubOutWithMock(self.urlmap, '_munge_path')
|
||||||
@ -243,29 +183,3 @@ class TestURLMap(test.TestCase):
|
|||||||
(None, 'value', '/path/elsepath'),
|
(None, 'value', '/path/elsepath'),
|
||||||
self.urlmap._path_strategy('http://10.20.30.40', '50',
|
self.urlmap._path_strategy('http://10.20.30.40', '50',
|
||||||
'/path/elsepath/resource.abc'))
|
'/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)
|
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):
|
def last_completed_audit_period(unit=None):
|
||||||
"""This method gives you the most recently *completed* audit period.
|
"""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…
x
Reference in New Issue
Block a user