From a105733cc970eaac2f4bc28f04b48f9149a2c801 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 30 Oct 2012 15:06:12 -0700 Subject: [PATCH] Add VolumeTenantAttribute API extension Expose the ID of the project to which a volume belongs through a new API extension. This is only presented to Admins by default. Visibility can be controlled with the 'volume_tenant_attribute' policy rule. Change-Id: If85d9759af6a9457bf3230375cc64af9efe4a088 --- .../volume/contrib/volume_tenant_attribute.py | 91 ++++++++++++ .../contrib/test_volume_host_attribute.py | 1 + .../contrib/test_volume_tenant_attribute.py | 134 ++++++++++++++++++ cinder/tests/policy.json | 3 +- etc/cinder/policy.json | 3 +- 5 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 cinder/api/openstack/volume/contrib/volume_tenant_attribute.py create mode 100644 cinder/tests/api/openstack/volume/contrib/test_volume_tenant_attribute.py diff --git a/cinder/api/openstack/volume/contrib/volume_tenant_attribute.py b/cinder/api/openstack/volume/contrib/volume_tenant_attribute.py new file mode 100644 index 000000000..193f65bde --- /dev/null +++ b/cinder/api/openstack/volume/contrib/volume_tenant_attribute.py @@ -0,0 +1,91 @@ +# 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. + +from cinder.api.openstack import extensions +from cinder.api.openstack import wsgi +from cinder.api.openstack import xmlutil +from cinder import volume + + +authorize = extensions.soft_extension_authorizer('volume', + 'volume_tenant_attribute') + + +class VolumeTenantAttributeController(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(VolumeTenantAttributeController, self).__init__(*args, **kwargs) + self.volume_api = volume.API() + + def _add_volume_tenant_attribute(self, context, resp_volume): + try: + db_volume = self.volume_api.get(context, resp_volume['id']) + except Exception: + return + else: + key = "%s:tenant_id" % Volume_tenant_attribute.alias + resp_volume[key] = db_volume['project_id'] + + @wsgi.extends + def show(self, req, resp_obj, id): + context = req.environ['cinder.context'] + if authorize(context): + resp_obj.attach(xml=VolumeTenantAttributeTemplate()) + self._add_volume_tenant_attribute(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=VolumeListTenantAttributeTemplate()) + for volume in list(resp_obj.obj['volumes']): + self._add_volume_tenant_attribute(context, volume) + + +class Volume_tenant_attribute(extensions.ExtensionDescriptor): + """Expose the internal project_id as an attribute of a volume.""" + + name = "VolumeTenantAttribute" + alias = "os-vol-tenant-attr" + namespace = ("http://docs.openstack.org/volume/ext/" + "volume_tenant_attribute/api/v1") + updated = "2011-11-03T00:00:00+00:00" + + def get_controller_extensions(self): + controller = VolumeTenantAttributeController() + extension = extensions.ControllerExtension(self, 'volumes', controller) + return [extension] + + +def make_volume(elem): + elem.set('{%s}tenant_id' % Volume_tenant_attribute.namespace, + '%s:tenant_id' % Volume_tenant_attribute.alias) + + +class VolumeTenantAttributeTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volume', selector='volume') + make_volume(root) + alias = Volume_tenant_attribute.alias + namespace = Volume_tenant_attribute.namespace + return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace}) + + +class VolumeListTenantAttributeTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volumes') + elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes') + make_volume(elem) + alias = Volume_tenant_attribute.alias + namespace = Volume_tenant_attribute.namespace + return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace}) diff --git a/cinder/tests/api/openstack/volume/contrib/test_volume_host_attribute.py b/cinder/tests/api/openstack/volume/contrib/test_volume_host_attribute.py index ed878b415..2acec1033 100644 --- a/cinder/tests/api/openstack/volume/contrib/test_volume_host_attribute.py +++ b/cinder/tests/api/openstack/volume/contrib/test_volume_host_attribute.py @@ -38,6 +38,7 @@ def fake_volume_get(*args, **kwargs): 'display_description': 'Just another volume!', 'volume_type_id': None, 'snapshot_id': None, + 'project_id': 'fake', } diff --git a/cinder/tests/api/openstack/volume/contrib/test_volume_tenant_attribute.py b/cinder/tests/api/openstack/volume/contrib/test_volume_tenant_attribute.py new file mode 100644 index 000000000..5949b21d8 --- /dev/null +++ b/cinder/tests/api/openstack/volume/contrib/test_volume_tenant_attribute.py @@ -0,0 +1,134 @@ +# 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 json +import datetime + +from lxml import etree +import webob + +from cinder import context +from cinder import test +from cinder import utils +from cinder import volume +from cinder.tests.api.openstack import fakes + +PROJECT_ID = '88fd1da4-f464-4a87-9ce5-26f2f40743b9' + + +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': PROJECT_ID, + } + + +def fake_volume_get_all(*args, **kwargs): + return [fake_volume_get()] + + +def app(): + # no auth, just let environ['cinder.context'] pass through + api = fakes.volume.APIRouter() + mapper = fakes.urlmap.URLMap() + mapper['/v1'] = api + return mapper + + +class VolumeTenantAttributeTest(test.TestCase): + + def setUp(self): + super(VolumeTenantAttributeTest, self).setUp() + self.stubs.Set(volume.API, 'get', fake_volume_get) + self.stubs.Set(volume.API, 'get_all', fake_volume_get_all) + self.UUID = utils.gen_uuid() + + def test_get_volume_allowed(self): + ctx = context.RequestContext('admin', 'fake', True) + req = webob.Request.blank('/v1/fake/volumes/%s' % self.UUID) + req.method = 'GET' + req.environ['cinder.context'] = ctx + res = req.get_response(app()) + vol = json.loads(res.body)['volume'] + self.assertEqual(vol['os-vol-tenant-attr:tenant_id'], PROJECT_ID) + + def test_get_volume_unallowed(self): + ctx = context.RequestContext('non-admin', 'fake', False) + req = webob.Request.blank('/v1/fake/volumes/%s' % self.UUID) + req.method = 'GET' + req.environ['cinder.context'] = ctx + res = req.get_response(app()) + vol = json.loads(res.body)['volume'] + self.assertFalse('os-vol-tenant-attr:tenant_id' in vol) + + def test_list_detail_volumes_allowed(self): + ctx = context.RequestContext('admin', 'fake', True) + req = webob.Request.blank('/v1/fake/volumes/detail') + req.method = 'GET' + req.environ['cinder.context'] = ctx + res = req.get_response(app()) + vol = json.loads(res.body)['volumes'] + self.assertEqual(vol[0]['os-vol-tenant-attr:tenant_id'], PROJECT_ID) + + def test_list_detail_volumes_unallowed(self): + ctx = context.RequestContext('non-admin', 'fake', False) + req = webob.Request.blank('/v1/fake/volumes/detail') + req.method = 'GET' + req.environ['cinder.context'] = ctx + res = req.get_response(app()) + vol = json.loads(res.body)['volumes'] + self.assertFalse('os-vol-tenant-attr:tenant_id' in vol[0]) + + def test_list_simple_volumes_no_tenant_id(self): + ctx = context.RequestContext('admin', 'fake', True) + req = webob.Request.blank('/v1/fake/volumes') + req.method = 'GET' + req.environ['cinder.context'] = ctx + res = req.get_response(app()) + vol = json.loads(res.body)['volumes'] + self.assertFalse('os-vol-tenant-attr:tenant_id' in vol[0]) + + def test_get_volume_xml(self): + ctx = context.RequestContext('admin', 'fake', True) + req = webob.Request.blank('/v1/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/v1}tenant_id') + self.assertEqual(vol.get(tenant_key), PROJECT_ID) + + def test_list_volumes_detail_xml(self): + ctx = context.RequestContext('admin', 'fake', True) + req = webob.Request.blank('/v1/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/v1}tenant_id') + self.assertEqual(vol.get(tenant_key), PROJECT_ID) diff --git a/cinder/tests/policy.json b/cinder/tests/policy.json index dd0f80eef..3ee529c92 100644 --- a/cinder/tests/policy.json +++ b/cinder/tests/policy.json @@ -34,5 +34,6 @@ "volume_extension:types_manage": [], "volume_extension:types_extra_specs": [], "volume_extension:extended_snapshot_attributes": [], - "volume_extension:volume_host_attribute": [["rule:admin_api"]] + "volume_extension:volume_host_attribute": [["rule:admin_api"]], + "volume_extension:volume_tenant_attribute": [["rule:admin_api"]] } diff --git a/etc/cinder/policy.json b/etc/cinder/policy.json index 96c01a554..a49931607 100644 --- a/etc/cinder/policy.json +++ b/etc/cinder/policy.json @@ -25,5 +25,6 @@ "volume_extension:volume_admin_actions:force_delete": [["rule:admin_api"]], "volume_extension:snapshot_admin_actions:force_delete": [["rule:admin_api"]], - "volume_extension:volume_host_attribute": [["rule:admin_api"]] + "volume_extension:volume_host_attribute": [["rule:admin_api"]], + "volume_extension:volume_tenant_attribute": [["rule:admin_api"]] } -- 2.45.2