--- /dev/null
+# Copyright 2012 OpenStack, LLC.
+#
+# 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.
+
+"""The Volume Image Metadata API extension."""
+
+from cinder.api import extensions
+from cinder.api.openstack import wsgi
+from cinder.api import xmlutil
+from cinder import volume
+
+
+authorize = extensions.soft_extension_authorizer('volume',
+ 'volume_image_metadata')
+
+
+class VolumeImageMetadataController(wsgi.Controller):
+ def __init__(self, *args, **kwargs):
+ super(VolumeImageMetadataController, self).__init__(*args, **kwargs)
+ self.volume_api = volume.API()
+
+ def _add_image_metadata(self, context, resp_volume):
+ try:
+ image_meta = self.volume_api.get_volume_image_metadata(
+ context, resp_volume)
+ except Exception:
+ return
+ else:
+ if image_meta:
+ resp_volume['volume_image_metadata'] = dict(
+ image_meta.iteritems())
+
+ @wsgi.extends
+ def show(self, req, resp_obj, id):
+ context = req.environ['cinder.context']
+ if authorize(context):
+ resp_obj.attach(xml=VolumeImageMetadataTemplate())
+ self._add_image_metadata(context, resp_obj.obj['volume'])
+
+ @wsgi.extends
+ def detail(self, req, resp_obj):
+ context = req.environ['cinder.context']
+ if authorize(context):
+ resp_obj.attach(xml=VolumesImageMetadataTemplate())
+ for volume in list(resp_obj.obj.get('volumes', [])):
+ self._add_image_metadata(context, volume)
+
+
+class Volume_image_metadata(extensions.ExtensionDescriptor):
+ """Show image metadata associated with the volume"""
+
+ name = "VolumeImageMetadata"
+ alias = "os-vol-image-meta"
+ namespace = ("http://docs.openstack.org/volume/ext/"
+ "volume_image_metadata/api/v1")
+ updated = "2012-12-07T00:00:00+00:00"
+
+ def get_controller_extensions(self):
+ controller = VolumeImageMetadataController()
+ extension = extensions.ControllerExtension(self, 'volumes', controller)
+ return [extension]
+
+
+class VolumeImageMetadataMetadataTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('volume_image_metadata',
+ selector='volume_image_metadata')
+ elem = xmlutil.SubTemplateElement(root, 'meta',
+ selector=xmlutil.get_items)
+ elem.set('key', 0)
+ elem.text = 1
+
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class VolumeImageMetadataTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('volume', selector='volume')
+ root.append(VolumeImageMetadataMetadataTemplate())
+
+ alias = Volume_image_metadata.alias
+ namespace = Volume_image_metadata.namespace
+
+ return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
+
+
+class VolumesImageMetadataTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('volumes')
+ elem = xmlutil.SubTemplateElement(root, 'volume', selector='volume')
+ elem.append(VolumeImageMetadataMetadataTemplate())
+
+ alias = Volume_image_metadata.alias
+ namespace = Volume_image_metadata.namespace
+
+ return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC.
+#
+# 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 datetime
+import json
+import uuid
+from xml.dom import minidom
+
+import webob
+
+from cinder.api import common
+from cinder.api.openstack.wsgi import MetadataXMLDeserializer
+from cinder.api.openstack.wsgi import XMLDeserializer
+from cinder import test
+from cinder.tests.api import fakes
+from cinder import volume
+
+
+def fake_volume_get(*args, **kwargs):
+ return {
+ 'id': 'fake',
+ 'host': 'host001',
+ 'status': 'available',
+ 'size': 5,
+ 'availability_zone': 'somewhere',
+ 'created_at': datetime.datetime.now(),
+ 'attach_status': None,
+ 'display_name': 'anothervolume',
+ 'display_description': 'Just another volume!',
+ 'volume_type_id': None,
+ 'snapshot_id': None,
+ 'project_id': 'fake',
+ }
+
+
+def fake_volume_get_all(*args, **kwargs):
+ return [fake_volume_get()]
+
+
+fake_image_metadata = {
+ 'image_id': 'someid',
+ 'image_name': 'fake',
+ 'kernel_id': 'somekernel',
+ 'ramdisk_id': 'someramdisk',
+}
+
+
+def fake_get_volume_image_metadata(*args, **kwargs):
+ return fake_image_metadata
+
+
+class VolumeImageMetadataTest(test.TestCase):
+ content_type = 'application/json'
+
+ def setUp(self):
+ super(VolumeImageMetadataTest, self).setUp()
+ self.stubs.Set(volume.API, 'get', fake_volume_get)
+ self.stubs.Set(volume.API, 'get_all', fake_volume_get_all)
+ self.stubs.Set(volume.API, 'get_volume_image_metadata',
+ fake_get_volume_image_metadata)
+ self.UUID = uuid.uuid4()
+
+ def _make_request(self, url):
+ req = webob.Request.blank(url)
+ req.accept = self.content_type
+ res = req.get_response(fakes.wsgi_app())
+ return res
+
+ def _get_image_metadata(self, body):
+ return json.loads(body)['volume']['volume_image_metadata']
+
+ def _get_image_metadata_list(self, body):
+ return [
+ volume['volume_image_metadata']
+ for volume in json.loads(body)['volumes']
+ ]
+
+ def test_get_volume(self):
+ res = self._make_request('/v2/fake/volumes/%s' % self.UUID)
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(self._get_image_metadata(res.body),
+ fake_image_metadata)
+
+ def test_list_detail_volumes(self):
+ res = self._make_request('/v2/fake/volumes/detail')
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(self._get_image_metadata_list(res.body)[0],
+ fake_image_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 = XMLDeserializer()
+ volume = deserializer.find_first_child_named(
+ minidom.parseString(body), 'volume')
+ image_metadata = deserializer.find_first_child_named(
+ volume, 'volume_image_metadata')
+ return MetadataXMLDeserializer().extract_metadata(image_metadata)
+
+ def _get_image_metadata_list(self, body):
+ deserializer = 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]
+ return map(MetadataXMLDeserializer().extract_metadata,
+ image_metadata_list)
"volume_extension:types_manage": [],
"volume_extension:types_extra_specs": [],
"volume_extension:extended_snapshot_attributes": [],
+ "volume_extension:volume_image_metadata": [],
"volume_extension:volume_host_attribute": [["rule:admin_api"]],
"volume_extension:volume_tenant_attribute": [["rule:admin_api"]],
"volume_extension:hosts": [["rule:admin_api"]]
return i['value']
return None
+ @wrap_check_policy
+ def get_volume_image_metadata(self, context, volume):
+ db_data = self.db.volume_glance_metadata_get(context, volume['id'])
+ return dict(
+ (meta_entry.key, meta_entry.value) for meta_entry in db_data
+ )
+
def _check_volume_availability(self, context, volume, force):
"""Check if the volume can be used."""
if volume['status'] not in ['available', 'in-use']:
"volume_extension:types_manage": [["rule:admin_api"]],
"volume_extension:types_extra_specs": [["rule:admin_api"]],
"volume_extension:extended_snapshot_attributes": [],
+ "volume_extension:volume_image_metadata": [],
"volume_extension:quotas:show": [],
"volume_extension:quotas:update_for_project": [["rule:admin_api"]],