diff --git a/nova/api/openstack/v2/contrib/virtual_storage_arrays.py b/nova/api/openstack/v2/contrib/virtual_storage_arrays.py index f3a7831d546..cd87e346270 100644 --- a/nova/api/openstack/v2/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/v2/contrib/virtual_storage_arrays.py @@ -26,6 +26,7 @@ from nova.api.openstack.v2 import extensions from nova.api.openstack.v2 import servers from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil from nova import compute from nova.compute import instance_types from nova import network @@ -84,22 +85,6 @@ def _vsa_view(context, vsa, details=False, instances=None): class VsaController(object): """The Virtual Storage Array API controller for the OpenStack API.""" - _serialization_metadata = { - 'application/xml': { - "attributes": { - "vsa": [ - "id", - "name", - "displayName", - "displayDescription", - "createTime", - "status", - "vcType", - "vcCount", - "driveCount", - "ipAddress", - ]}}} - def __init__(self): self.vsa_api = vsa.API() self.compute_api = compute.API() @@ -230,6 +215,45 @@ def disassociate_address(self, req, id, body): # Placeholder +def make_vsa(elem): + elem.set('id') + elem.set('name') + elem.set('displayName') + elem.set('displayDescription') + elem.set('createTime') + elem.set('status') + elem.set('vcType') + elem.set('vcCount') + elem.set('driveCount') + elem.set('ipAddress') + + +class VsaTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('vsa', selector='vsa') + make_vsa(root) + return xmlutil.MasterTemplate(root, 1) + + +class VsaSetTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('vsaSet') + elem = xmlutil.SubTemplateElement(root, 'vsa', selector='vsaSet') + make_vsa(elem) + return xmlutil.MasterTemplate(root, 1) + + +class VsaSerializer(xmlutil.XMLTemplateSerializer): + def default(self): + return VsaTemplate() + + def index(self): + return VsaSetTemplate() + + def detail(self): + return VsaSetTemplate() + + class VsaVolumeDriveController(volumes.VolumeController): """The base class for VSA volumes & drives. @@ -238,21 +262,6 @@ class VsaVolumeDriveController(volumes.VolumeController): """ - _serialization_metadata = { - 'application/xml': { - "attributes": { - "volume": [ - "id", - "name", - "status", - "size", - "availabilityZone", - "createdAt", - "displayName", - "displayDescription", - "vsaId", - ]}}} - def __init__(self): self.volume_api = volume.API() self.vsa_api = vsa.API() @@ -401,6 +410,12 @@ def show(self, req, vsa_id, id): return super(VsaVolumeDriveController, self).show(req, id) +def make_volume(elem): + volumes.make_volume(elem) + elem.set('name') + elem.set('vsaId') + + class VsaVolumeController(VsaVolumeDriveController): """The VSA volume API controller for the Openstack API. @@ -416,6 +431,32 @@ def __init__(self): super(VsaVolumeController, self).__init__() +class VsaVolumeTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volume', selector='volume') + make_volume(root) + return xmlutil.MasterTemplate(root, 1) + + +class VsaVolumesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volumes') + elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes') + make_volume(elem) + return xmlutil.MasterTemplate(root, 1) + + +class VsaVolumeSerializer(xmlutil.XMLTemplateSerializer): + def default(self): + return VsaVolumeTemplate() + + def index(self): + return VsaVolumesTemplate() + + def detail(self): + return VsaVolumesTemplate() + + class VsaDriveController(VsaVolumeDriveController): """The VSA Drive API controller for the Openstack API. @@ -443,27 +484,35 @@ def delete(self, req, vsa_id, id): raise exc.HTTPBadRequest() +class VsaDriveTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('drive', selector='drive') + make_volume(root) + return xmlutil.MasterTemplate(root, 1) + + +class VsaDrivesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('drives') + elem = xmlutil.SubTemplateElement(root, 'drive', selector='drives') + make_volume(elem) + return xmlutil.MasterTemplate(root, 1) + + +class VsaDriveSerializer(xmlutil.XMLTemplateSerializer): + def default(self): + return VsaDriveTemplate() + + def index(self): + return VsaDrivesTemplate() + + def detail(self): + return VsaDrivesTemplate() + + class VsaVPoolController(object): """The vPool VSA API controller for the OpenStack API.""" - _serialization_metadata = { - 'application/xml': { - "attributes": { - "vpool": [ - "id", - "vsaId", - "name", - "displayName", - "displayDescription", - "driveCount", - "driveIds", - "protection", - "stripeSize", - "stripeWidth", - "createTime", - "status", - ]}}} - def __init__(self): self.vsa_api = vsa.API() super(VsaVPoolController, self).__init__() @@ -489,6 +538,48 @@ def show(self, req, vsa_id, id): raise exc.HTTPBadRequest() +def make_vpool(elem): + elem.set('id') + elem.set('vsaId') + elem.set('name') + elem.set('displayName') + elem.set('displayDescription') + elem.set('driveCount') + elem.set('protection') + elem.set('stripeSize') + elem.set('stripeWidth') + elem.set('createTime') + elem.set('status') + + drive_ids = xmlutil.SubTemplateElement(elem, 'driveIds') + drive_id = xmlutil.SubTemplateElement(drive_ids, 'driveId', + selector='driveIds') + drive_id.text = xmlutil.Selector() + + +class VsaVPoolTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('vpool', selector='vpool') + make_vpool(root) + return xmlutil.MasterTemplate(root, 1) + + +class VsaVPoolsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('vpools') + elem = xmlutil.SubTemplateElement(root, 'vpool', selector='vpools') + make_vpool(elem) + return xmlutil.MasterTemplate(root, 1) + + +class VsaVPoolSerializer(xmlutil.XMLTemplateSerializer): + def default(self): + return VsaVPoolTemplate() + + def index(self): + return VsaVPoolsTemplate() + + class VsaVCController(servers.Controller): """The VSA Virtual Controller API controller for the OpenStack API.""" @@ -554,9 +645,16 @@ class Virtual_storage_arrays(extensions.ExtensionDescriptor): def get_resources(self): resources = [] + + body_serializers = { + 'application/xml': VsaSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers) + res = extensions.ResourceExtension( 'zadr-vsa', VsaController(), + serializer=serializer, collection_actions={'detail': 'GET'}, member_actions={'add_capacity': 'POST', 'remove_capacity': 'POST', @@ -564,31 +662,63 @@ def get_resources(self): 'disassociate_address': 'POST'}) resources.append(res) + body_serializers = { + 'application/xml': VsaVolumeSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers) + res = extensions.ResourceExtension('volumes', VsaVolumeController(), + serializer=serializer, collection_actions={'detail': 'GET'}, parent=dict( member_name='vsa', collection_name='zadr-vsa')) resources.append(res) + body_serializers = { + 'application/xml': VsaDriveSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers) + res = extensions.ResourceExtension('drives', VsaDriveController(), + serializer=serializer, collection_actions={'detail': 'GET'}, parent=dict( member_name='vsa', collection_name='zadr-vsa')) resources.append(res) + body_serializers = { + 'application/xml': VsaVPoolSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers) + res = extensions.ResourceExtension('vpools', VsaVPoolController(), + serializer=serializer, parent=dict( member_name='vsa', collection_name='zadr-vsa')) resources.append(res) + headers_serializer = servers.HeadersSerializer() + body_serializers = { + 'application/xml': servers.ServerXMLSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers, + headers_serializer) + + body_deserializers = { + 'application/xml': servers.ServerXMLDeserializer(), + } + deserializer = wsgi.RequestDeserializer(body_deserializers) + res = extensions.ResourceExtension('instances', VsaVCController(), + serializer=serializer, + deserializer=deserializer, parent=dict( member_name='vsa', collection_name='zadr-vsa')) diff --git a/nova/tests/api/openstack/v2/contrib/test_vsa.py b/nova/tests/api/openstack/v2/contrib/test_vsa.py index 7b3ea181065..03c9d4449e5 100644 --- a/nova/tests/api/openstack/v2/contrib/test_vsa.py +++ b/nova/tests/api/openstack/v2/contrib/test_vsa.py @@ -13,11 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import json +from lxml import etree import stubout import webob +from nova.api.openstack.v2.contrib import virtual_storage_arrays as vsa_ext from nova import context import nova.db from nova import exception @@ -445,3 +448,265 @@ def setUp(self): def tearDown(self): self.stubs.UnsetAll() super(VSADriveApiTest, self).tearDown() + + +class SerializerTestCommon(test.TestCase): + def setUp(self): + super(SerializerTestCommon, self).setUp() + self.serializer = self.serializer_class() + + def _verify_attrs(self, obj, tree, attrs): + for attr in attrs: + self.assertEqual(str(obj[attr]), tree.get(attr)) + + +class VsaSerializerTest(SerializerTestCommon): + serializer_class = vsa_ext.VsaSerializer + + def test_serialize_show_create(self): + exemplar = dict( + id='vsa_id', + name='vsa_name', + displayName='vsa_display_name', + displayDescription='vsa_display_desc', + createTime=datetime.datetime.now(), + status='active', + vcType='vsa_instance_type', + vcCount=24, + driveCount=48, + ipAddress='10.11.12.13') + text = self.serializer.serialize(dict(vsa=exemplar), 'show') + + print text + tree = etree.fromstring(text) + + self.assertEqual('vsa', tree.tag) + self._verify_attrs(exemplar, tree, exemplar.keys()) + + def test_serialize_index_detail(self): + exemplar = [dict( + id='vsa1_id', + name='vsa1_name', + displayName='vsa1_display_name', + displayDescription='vsa1_display_desc', + createTime=datetime.datetime.now(), + status='active', + vcType='vsa1_instance_type', + vcCount=24, + driveCount=48, + ipAddress='10.11.12.13'), + dict( + id='vsa2_id', + name='vsa2_name', + displayName='vsa2_display_name', + displayDescription='vsa2_display_desc', + createTime=datetime.datetime.now(), + status='active', + vcType='vsa2_instance_type', + vcCount=42, + driveCount=84, + ipAddress='11.12.13.14')] + text = self.serializer.serialize(dict(vsaSet=exemplar), 'index') + + print text + tree = etree.fromstring(text) + + self.assertEqual('vsaSet', tree.tag) + self.assertEqual(len(exemplar), len(tree)) + for idx, child in enumerate(tree): + self.assertEqual('vsa', child.tag) + self._verify_attrs(exemplar[idx], child, exemplar[idx].keys()) + + +class VsaVolumeSerializerTest(SerializerTestCommon): + serializer_class = vsa_ext.VsaVolumeSerializer + object = 'volume' + objects = 'volumes' + + def _verify_voldrive(self, vol, tree): + self.assertEqual(self.object, tree.tag) + + self._verify_attrs(vol, tree, ('id', 'status', 'size', + 'availabilityZone', 'createdAt', + 'displayName', 'displayDescription', + 'volumeType', 'vsaId', 'name')) + + for child in tree: + self.assertTrue(child.tag in ('attachments', 'metadata')) + if child.tag == 'attachments': + self.assertEqual(1, len(child)) + self.assertEqual('attachment', child[0].tag) + self._verify_attrs(vol['attachments'][0], child[0], + ('id', 'volumeId', 'serverId', 'device')) + elif child.tag == 'metadata': + not_seen = set(vol['metadata'].keys()) + for gr_child in child: + self.assertTrue(gr_child.tag in not_seen) + self.assertEqual(str(vol['metadata'][gr_child.tag]), + gr_child.text) + not_seen.remove(gr_child.tag) + self.assertEqual(0, len(not_seen)) + + def test_show_create_serializer(self): + raw_volume = dict( + id='vol_id', + status='vol_status', + size=1024, + availabilityZone='vol_availability', + createdAt=datetime.datetime.now(), + attachments=[dict( + id='vol_id', + volumeId='vol_id', + serverId='instance_uuid', + device='/foo')], + displayName='vol_name', + displayDescription='vol_desc', + volumeType='vol_type', + metadata=dict( + foo='bar', + baz='quux', + ), + vsaId='vol_vsa_id', + name='vol_vsa_name', + ) + text = self.serializer.serialize({self.object: raw_volume}, 'show') + + print text + tree = etree.fromstring(text) + + self._verify_voldrive(raw_volume, tree) + + def test_index_detail_serializer(self): + raw_volumes = [dict( + id='vol1_id', + status='vol1_status', + size=1024, + availabilityZone='vol1_availability', + createdAt=datetime.datetime.now(), + attachments=[dict( + id='vol1_id', + volumeId='vol1_id', + serverId='instance_uuid', + device='/foo1')], + displayName='vol1_name', + displayDescription='vol1_desc', + volumeType='vol1_type', + metadata=dict( + foo='vol1_foo', + bar='vol1_bar', + ), + vsaId='vol1_vsa_id', + name='vol1_vsa_name', + ), + dict( + id='vol2_id', + status='vol2_status', + size=1024, + availabilityZone='vol2_availability', + createdAt=datetime.datetime.now(), + attachments=[dict( + id='vol2_id', + volumeId='vol2_id', + serverId='instance_uuid', + device='/foo2')], + displayName='vol2_name', + displayDescription='vol2_desc', + volumeType='vol2_type', + metadata=dict( + foo='vol2_foo', + bar='vol2_bar', + ), + vsaId='vol2_vsa_id', + name='vol2_vsa_name', + )] + text = self.serializer.serialize({self.objects: raw_volumes}, 'index') + + print text + tree = etree.fromstring(text) + + self.assertEqual(self.objects, tree.tag) + self.assertEqual(len(raw_volumes), len(tree)) + for idx, child in enumerate(tree): + self._verify_voldrive(raw_volumes[idx], child) + + +class VsaDriveSerializerTest(VsaVolumeSerializerTest): + serializer_class = vsa_ext.VsaDriveSerializer + object = 'drive' + objects = 'drives' + + +class VsaVPoolSerializerTest(SerializerTestCommon): + serializer_class = vsa_ext.VsaVPoolSerializer + + def _verify_vpool(self, vpool, tree): + self._verify_attrs(vpool, tree, ('id', 'vsaId', 'name', 'displayName', + 'displayDescription', 'driveCount', + 'protection', 'stripeSize', + 'stripeWidth', 'createTime', + 'status')) + + self.assertEqual(1, len(tree)) + self.assertEqual('driveIds', tree[0].tag) + self.assertEqual(len(vpool['driveIds']), len(tree[0])) + for idx, gr_child in enumerate(tree[0]): + self.assertEqual('driveId', gr_child.tag) + self.assertEqual(str(vpool['driveIds'][idx]), gr_child.text) + + def test_vpool_create_show_serializer(self): + exemplar = dict( + id='vpool_id', + vsaId='vpool_vsa_id', + name='vpool_vsa_name', + displayName='vpool_display_name', + displayDescription='vpool_display_desc', + driveCount=24, + driveIds=['drive1', 'drive2', 'drive3'], + protection='protected', + stripeSize=1024, + stripeWidth=2048, + createTime=datetime.datetime.now(), + status='available') + text = self.serializer.serialize(dict(vpool=exemplar), 'show') + + print text + tree = etree.fromstring(text) + + self._verify_vpool(exemplar, tree) + + def test_vpool_index_serializer(self): + exemplar = [dict( + id='vpool1_id', + vsaId='vpool1_vsa_id', + name='vpool1_vsa_name', + displayName='vpool1_display_name', + displayDescription='vpool1_display_desc', + driveCount=24, + driveIds=['drive1', 'drive2', 'drive3'], + protection='protected', + stripeSize=1024, + stripeWidth=2048, + createTime=datetime.datetime.now(), + status='available'), + dict( + id='vpool2_id', + vsaId='vpool2_vsa_id', + name='vpool2_vsa_name', + displayName='vpool2_display_name', + displayDescription='vpool2_display_desc', + driveCount=42, + driveIds=['drive4', 'drive5', 'drive6'], + protection='protected', + stripeSize=512, + stripeWidth=256, + createTime=datetime.datetime.now(), + status='available')] + text = self.serializer.serialize(dict(vpools=exemplar), 'index') + + print text + tree = etree.fromstring(text) + + self.assertEqual('vpools', tree.tag) + self.assertEqual(len(exemplar), len(tree)) + for idx, child in enumerate(tree): + self._verify_vpool(exemplar[idx], child)