From a9c8212f6925b9f4e0feb25621266265968ee1df Mon Sep 17 00:00:00 2001 From: John Griffith Date: Wed, 13 Jun 2012 12:38:35 -0600 Subject: [PATCH] Add action extensions to support nova integration. * Adds VolumeActions extension to support key functions described in blueprint volume-decoupling * Adds snapshot translations to APIs * Should be back in sync with 7992 * blueprint compat-extensions * sleepsonthefloor: Moved added snapshot attributes to extension, added tests * sleepsonthefloor: Lock pep8==1.1 in tox.ini Change-Id: I9c6118cd434ca8b275d2386546923f932420b975 --- cinder/api/openstack/volume/__init__.py | 3 +- .../contrib/extended_snapshot_attributes.py | 125 +++++++++++++++++ .../volume/contrib/volume_actions.py | 114 ++++++++++++++++ cinder/api/openstack/volume/snapshots.py | 23 ++-- cinder/tests/api/openstack/fakes.py | 24 ++-- .../test_extended_snapshot_attributes.py | 126 ++++++++++++++++++ .../volume/contrib/test_volume_actions.py | 107 +++++++++++++++ .../api/openstack/volume/test_snapshots.py | 31 +++-- cinder/tests/policy.json | 3 +- cinder/volume/manager.py | 6 +- etc/cinder/policy.json | 3 +- tox.ini | 2 +- 12 files changed, 523 insertions(+), 44 deletions(-) create mode 100644 cinder/api/openstack/volume/contrib/extended_snapshot_attributes.py create mode 100644 cinder/api/openstack/volume/contrib/volume_actions.py create mode 100644 cinder/tests/api/openstack/volume/contrib/test_extended_snapshot_attributes.py create mode 100644 cinder/tests/api/openstack/volume/contrib/test_volume_actions.py diff --git a/cinder/api/openstack/volume/__init__.py b/cinder/api/openstack/volume/__init__.py index 2d9ac302b..1b85ed906 100644 --- a/cinder/api/openstack/volume/__init__.py +++ b/cinder/api/openstack/volume/__init__.py @@ -50,7 +50,8 @@ class APIRouter(cinder.api.openstack.APIRouter): self.resources['volumes'] = volumes.create_resource() mapper.resource("volume", "volumes", controller=self.resources['volumes'], - collection={'detail': 'GET'}) + collection={'detail': 'GET'}, + member={'action': 'POST'}) self.resources['types'] = types.create_resource() mapper.resource("type", "types", diff --git a/cinder/api/openstack/volume/contrib/extended_snapshot_attributes.py b/cinder/api/openstack/volume/contrib/extended_snapshot_attributes.py new file mode 100644 index 000000000..ba5ec7606 --- /dev/null +++ b/cinder/api/openstack/volume/contrib/extended_snapshot_attributes.py @@ -0,0 +1,125 @@ +# 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 Extended Snapshot Attributes API extension.""" + +from webob import exc + +from cinder.api.openstack import extensions +from cinder.api.openstack import wsgi +from cinder.api.openstack import xmlutil +from cinder import volume +from cinder import db +from cinder import exception +from cinder import flags +from cinder import log as logging + + +FLAGS = flags.FLAGS +LOG = logging.getLogger(__name__) +authorize = extensions.soft_extension_authorizer('volume', + 'extended_snapshot_attributes') + + +class ExtendedSnapshotAttributesController(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(ExtendedSnapshotAttributesController, self).__init__(*args, + **kwargs) + self.volume_api = volume.API() + + def _get_snapshots(self, context): + snapshots = self.volume_api.get_all_snapshots(context) + rval = dict((snapshot['id'], snapshot) for snapshot in snapshots) + return rval + + def _extend_snapshot(self, context, snapshot, data): + for attr in ['project_id', 'progress']: + key = "%s:%s" % (Extended_snapshot_attributes.alias, attr) + snapshot[key] = data[attr] + + @wsgi.extends + def show(self, req, resp_obj, id): + context = req.environ['cinder.context'] + if authorize(context): + # Attach our slave template to the response object + resp_obj.attach(xml=ExtendedSnapshotAttributeTemplate()) + + try: + snapshot = self.volume_api.get_snapshot(context, id) + except exception.NotFound: + explanation = _("Snapshot not found.") + raise exc.HTTPNotFound(explanation=explanation) + + self._extend_snapshot(context, resp_obj.obj['snapshot'], snapshot) + + @wsgi.extends + def detail(self, req, resp_obj): + context = req.environ['cinder.context'] + if authorize(context): + # Attach our slave template to the response object + resp_obj.attach(xml=ExtendedSnapshotAttributesTemplate()) + + snapshots = list(resp_obj.obj.get('snapshots', [])) + db_snapshots = self._get_snapshots(context) + + for snapshot_object in snapshots: + try: + snapshot_data = db_snapshots[snapshot_object['id']] + except KeyError: + continue + + self._extend_snapshot(context, snapshot_object, snapshot_data) + + +class Extended_snapshot_attributes(extensions.ExtensionDescriptor): + """Extended SnapshotAttributes support.""" + + name = "ExtendedSnapshotAttributes" + alias = "os-extended-snapshot-attributes" + namespace = ("http://docs.openstack.org/volume/ext/" + "extended_snapshot_attributes/api/v1") + updated = "2012-06-19T00:00:00+00:00" + + def get_controller_extensions(self): + controller = ExtendedSnapshotAttributesController() + extension = extensions.ControllerExtension(self, 'snapshots', + controller) + return [extension] + + +def make_snapshot(elem): + elem.set('{%s}project_id' % Extended_snapshot_attributes.namespace, + '%s:project_id' % Extended_snapshot_attributes.alias) + elem.set('{%s}progress' % Extended_snapshot_attributes.namespace, + '%s:progress' % Extended_snapshot_attributes.alias) + + +class ExtendedSnapshotAttributeTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('snapshot', selector='snapshot') + make_snapshot(root) + alias = Extended_snapshot_attributes.alias + namespace = Extended_snapshot_attributes.namespace + return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace}) + + +class ExtendedSnapshotAttributesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('snapshots') + elem = xmlutil.SubTemplateElement(root, 'snapshot', + selector='snapshots') + make_snapshot(elem) + alias = Extended_snapshot_attributes.alias + namespace = Extended_snapshot_attributes.namespace + return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace}) diff --git a/cinder/api/openstack/volume/contrib/volume_actions.py b/cinder/api/openstack/volume/contrib/volume_actions.py new file mode 100644 index 000000000..161000263 --- /dev/null +++ b/cinder/api/openstack/volume/contrib/volume_actions.py @@ -0,0 +1,114 @@ +# 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 os.path +import traceback + +import webob +from webob import exc + +from cinder.api.openstack import common +from cinder.api.openstack import extensions +from cinder.api.openstack import wsgi +from cinder import volume +from cinder import exception +from cinder import flags +from cinder import log as logging + + +FLAGS = flags.FLAGS +LOG = logging.getLogger(__name__) + + +def authorize(context, action_name): + action = 'volume_actions:%s' % action_name + extensions.extension_authorizer('volume', action)(context) + + +class VolumeActionsController(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(VolumeActionsController, self).__init__(*args, **kwargs) + self.volume_api = volume.API() + + @wsgi.action('os-attach') + def _attach(self, req, id, body): + """Add attachment metadata.""" + context = req.environ['cinder.context'] + volume = self.volume_api.get(context, id) + + instance_uuid = body['os-attach']['instance_uuid'] + mountpoint = body['os-attach']['mountpoint'] + + self.volume_api.attach(context, volume, + instance_uuid, mountpoint) + return webob.Response(status_int=202) + + @wsgi.action('os-detach') + def _detach(self, req, id, body): + """Clear attachment metadata.""" + context = req.environ['cinder.context'] + volume = self.volume_api.get(context, id) + self.volume_api.detach(context, volume) + return webob.Response(status_int=202) + + @wsgi.action('os-reserve') + def _reserve(self, req, id, body): + """Mark volume as reserved.""" + context = req.environ['cinder.context'] + volume = self.volume_api.get(context, id) + self.volume_api.reserve_volume(context, volume) + return webob.Response(status_int=202) + + @wsgi.action('os-unreserve') + def _unreserve(self, req, id, body): + """Unmark volume as reserved.""" + context = req.environ['cinder.context'] + volume = self.volume_api.get(context, id) + self.volume_api.unreserve_volume(context, volume) + return webob.Response(status_int=202) + + @wsgi.action('os-initialize_connection') + def _initialize_connection(self, req, id, body): + """Initialize volume attachment.""" + context = req.environ['cinder.context'] + volume = self.volume_api.get(context, id) + connector = body['os-initialize_connection']['connector'] + info = self.volume_api.initialize_connection(context, + volume, + connector) + return {'connection_info': info} + + @wsgi.action('os-terminate_connection') + def _terminate_connection(self, req, id, body): + """Terminate volume attachment.""" + context = req.environ['cinder.context'] + volume = self.volume_api.get(context, id) + connector = body['os-terminate_connection']['connector'] + self.volume_api.terminate_connection(context, volume, connector) + return webob.Response(status_int=202) + + +class Volume_actions(extensions.ExtensionDescriptor): + """Enable volume actions + """ + + name = "VolumeActions" + alias = "os-volume-actions" + namespace = "http://docs.openstack.org/volume/ext/volume-actions/api/v1.1" + updated = "2012-05-31T00:00:00+00:00" + + def get_controller_extensions(self): + controller = VolumeActionsController() + extension = extensions.ControllerExtension(self, 'volumes', controller) + return [extension] diff --git a/cinder/api/openstack/volume/snapshots.py b/cinder/api/openstack/volume/snapshots.py index f6d5304ec..22454e991 100644 --- a/cinder/api/openstack/volume/snapshots.py +++ b/cinder/api/openstack/volume/snapshots.py @@ -33,28 +33,27 @@ LOG = logging.getLogger(__name__) FLAGS = flags.FLAGS -def _translate_snapshot_detail_view(context, vol): +def _translate_snapshot_detail_view(context, snapshot): """Maps keys for snapshots details view.""" - d = _translate_snapshot_summary_view(context, vol) + d = _translate_snapshot_summary_view(context, snapshot) # NOTE(gagupta): No additional data / lookups at the moment return d -def _translate_snapshot_summary_view(context, vol): +def _translate_snapshot_summary_view(context, snapshot): """Maps keys for snapshots summary view.""" d = {} - # TODO(bcwaldon): remove str cast once we use uuids - d['id'] = str(vol['id']) - d['volume_id'] = str(vol['volume_id']) - d['status'] = vol['status'] - # NOTE(gagupta): We map volume_size as the snapshot size - d['size'] = vol['volume_size'] - d['created_at'] = vol['created_at'] - d['display_name'] = vol['display_name'] - d['display_description'] = vol['display_description'] + d['id'] = snapshot['id'] + d['created_at'] = snapshot['created_at'] + d['display_name'] = snapshot['display_name'] + d['display_description'] = snapshot['display_description'] + d['volume_id'] = snapshot['volume_id'] + d['status'] = snapshot['status'] + d['size'] = snapshot['volume_size'] + return d diff --git a/cinder/tests/api/openstack/fakes.py b/cinder/tests/api/openstack/fakes.py index 019acf62e..959606ffe 100644 --- a/cinder/tests/api/openstack/fakes.py +++ b/cinder/tests/api/openstack/fakes.py @@ -26,6 +26,7 @@ from cinder.api import auth as api_auth from cinder.api import openstack as openstack_api from cinder.api.openstack import auth from cinder.api.openstack import urlmap +from cinder.api.openstack import volume from cinder.api.openstack.volume import versions from cinder.api.openstack import wsgi as os_wsgi from cinder import context @@ -60,28 +61,27 @@ def fake_wsgi(self, req): return self.application -def wsgi_app(inner_app_v2=None, fake_auth=True, fake_auth_context=None, +def wsgi_app(inner_app_v1=None, fake_auth=True, fake_auth_context=None, use_no_auth=False, ext_mgr=None): - if not inner_app_v2: - inner_app_v2 = compute.APIRouter(ext_mgr) + if not inner_app_v1: + inner_app_v1 = volume.APIRouter(ext_mgr) if fake_auth: if fake_auth_context is not None: ctxt = fake_auth_context else: ctxt = context.RequestContext('fake', 'fake', auth_token=True) - api_v2 = openstack_api.FaultWrapper(api_auth.InjectContext(ctxt, - limits.RateLimitingMiddleware(inner_app_v2))) + api_v1 = openstack_api.FaultWrapper(api_auth.InjectContext(ctxt, + inner_app_v1)) elif use_no_auth: - api_v2 = openstack_api.FaultWrapper(auth.NoAuthMiddleware( - limits.RateLimitingMiddleware(inner_app_v2))) + api_v1 = openstack_api.FaultWrapper(auth.NoAuthMiddleware( + limits.RateLimitingMiddleware(inner_app_v1))) else: - api_v2 = openstack_api.FaultWrapper(auth.AuthMiddleware( - limits.RateLimitingMiddleware(inner_app_v2))) + api_v1 = openstack_api.FaultWrapper(auth.AuthMiddleware( + limits.RateLimitingMiddleware(inner_app_v1))) mapper = urlmap.URLMap() - mapper['/v2'] = api_v2 - mapper['/v1.1'] = api_v2 + mapper['/v1'] = api_v1 mapper['/'] = openstack_api.FaultWrapper(versions.Versions()) return mapper @@ -122,7 +122,7 @@ class HTTPRequest(webob.Request): @classmethod def blank(cls, *args, **kwargs): - kwargs['base_url'] = 'http://localhost/v2' + kwargs['base_url'] = 'http://localhost/v1' use_admin_context = kwargs.pop('use_admin_context', False) out = webob.Request.blank(*args, **kwargs) out.environ['cinder.context'] = FakeRequestContext('fake_user', 'fake', diff --git a/cinder/tests/api/openstack/volume/contrib/test_extended_snapshot_attributes.py b/cinder/tests/api/openstack/volume/contrib/test_extended_snapshot_attributes.py new file mode 100644 index 000000000..953965a61 --- /dev/null +++ b/cinder/tests/api/openstack/volume/contrib/test_extended_snapshot_attributes.py @@ -0,0 +1,126 @@ +# Copyright 2012 OpenStack LLC. +# 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 +import webob +import json + +from cinder.api.openstack.volume.contrib import extended_snapshot_attributes +from cinder import volume +from cinder import exception +from cinder import flags +from cinder import test +from cinder.tests.api.openstack import fakes + + +FLAGS = flags.FLAGS + + +UUID1 = '00000000-0000-0000-0000-000000000001' +UUID2 = '00000000-0000-0000-0000-000000000002' + + +def _get_default_snapshot_param(): + return { + 'id': UUID1, + 'volume_id': 12, + 'status': 'available', + 'volume_size': 100, + 'created_at': None, + 'display_name': 'Default name', + 'display_description': 'Default description', + 'project_id': 'fake', + 'progress': '0%' + } + + +def fake_snapshot_get(self, context, snapshot_id): + param = _get_default_snapshot_param() + return param + + +def fake_snapshot_get_all(self, context): + param = _get_default_snapshot_param() + return [param] + + +class ExtendedSnapshotAttributesTest(test.TestCase): + content_type = 'application/json' + prefix = 'os-extended-snapshot-attributes:' + + def setUp(self): + super(ExtendedSnapshotAttributesTest, self).setUp() + self.stubs.Set(volume.api.API, 'get_snapshot', fake_snapshot_get) + self.stubs.Set(volume.api.API, 'get_all_snapshots', + fake_snapshot_get_all) + + def _make_request(self, url): + req = webob.Request.blank(url) + req.headers['Accept'] = self.content_type + res = req.get_response(fakes.wsgi_app()) + return res + + def _get_snapshot(self, body): + return json.loads(body).get('snapshot') + + def _get_snapshots(self, body): + return json.loads(body).get('snapshots') + + def assertSnapshotAttributes(self, snapshot, project_id, progress): + self.assertEqual(snapshot.get('%sproject_id' % self.prefix), + project_id) + self.assertEqual(snapshot.get('%sprogress' % self.prefix), progress) + + def test_show(self): + url = '/v1/fake/snapshots/%s' % UUID2 + res = self._make_request(url) + + self.assertEqual(res.status_int, 200) + self.assertSnapshotAttributes(self._get_snapshot(res.body), + project_id='fake', + progress='0%') + + def test_detail(self): + url = '/v1/fake/snapshots/detail' + res = self._make_request(url) + + self.assertEqual(res.status_int, 200) + for i, snapshot in enumerate(self._get_snapshots(res.body)): + self.assertSnapshotAttributes(snapshot, + project_id='fake', + progress='0%') + + def test_no_instance_passthrough_404(self): + + def fake_snapshot_get(*args, **kwargs): + raise exception.InstanceNotFound() + + self.stubs.Set(volume.api.API, 'get_snapshot', fake_snapshot_get) + url = '/v1/fake/snapshots/70f6db34-de8d-4fbd-aafb-4065bdfa6115' + res = self._make_request(url) + + self.assertEqual(res.status_int, 404) + + +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() diff --git a/cinder/tests/api/openstack/volume/contrib/test_volume_actions.py b/cinder/tests/api/openstack/volume/contrib/test_volume_actions.py new file mode 100644 index 000000000..579022013 --- /dev/null +++ b/cinder/tests/api/openstack/volume/contrib/test_volume_actions.py @@ -0,0 +1,107 @@ +# 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 webob + +from cinder.api.openstack import volume as volume_api +from cinder import volume +from cinder import context +from cinder import exception +from cinder import flags +from cinder import test +from cinder.tests.api.openstack import fakes +from cinder import utils + + +FLAGS = flags.FLAGS + + +def fake_volume_api(*args, **kwargs): + return True + + +def fake_volume_get(*args, **kwargs): + return {'id': 'fake', 'host': 'fake'} + + +class VolumeActionsTest(test.TestCase): + + _actions = ('os-detach', 'os-reserve', 'os-unreserve') + + _methods = ('attach', 'detach', 'reserve_volume', 'unreserve_volume') + + def setUp(self): + super(VolumeActionsTest, self).setUp() + self.stubs.Set(volume.API, 'get', fake_volume_api) + self.UUID = utils.gen_uuid() + for _method in self._methods: + self.stubs.Set(volume.API, _method, fake_volume_api) + + self.stubs.Set(volume.API, 'get', fake_volume_get) + + def test_simple_api_actions(self): + app = fakes.wsgi_app() + for _action in self._actions: + req = webob.Request.blank('/v1/fake/volumes/%s/action' % + self.UUID) + req.method = 'POST' + req.body = json.dumps({_action: None}) + req.content_type = 'application/json' + res = req.get_response(app) + self.assertEqual(res.status_int, 202) + + def test_initialize_connection(self): + def fake_initialize_connection(*args, **kwargs): + return {} + self.stubs.Set(volume.API, 'initialize_connection', + fake_initialize_connection) + + body = {'os-initialize_connection': {'connector': 'fake'}} + req = webob.Request.blank('/v1/fake/volumes/1/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + output = json.loads(res.body) + self.assertEqual(res.status_int, 200) + + def test_terminate_connection(self): + def fake_terminate_connection(*args, **kwargs): + return {} + self.stubs.Set(volume.API, 'terminate_connection', + fake_terminate_connection) + + body = {'os-terminate_connection': {'connector': 'fake'}} + req = webob.Request.blank('/v1/fake/volumes/1/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_attach(self): + body = {'os-attach': {'instance_uuid': 'fake', + 'mountpoint': '/dev/vdc'}} + req = webob.Request.blank('/v1/fake/volumes/1/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) diff --git a/cinder/tests/api/openstack/volume/test_snapshots.py b/cinder/tests/api/openstack/volume/test_snapshots.py index 8c87da514..bc60e289a 100644 --- a/cinder/tests/api/openstack/volume/test_snapshots.py +++ b/cinder/tests/api/openstack/volume/test_snapshots.py @@ -26,14 +26,17 @@ from cinder import test from cinder import volume from cinder.tests.api.openstack import fakes -FLAGS = flags.FLAGS +FLAGS = flags.FLAGS LOG = logging.getLogger(__name__) +UUID = '00000000-0000-0000-0000-000000000001' +INVALID_UUID = '00000000-0000-0000-0000-000000000002' + def _get_default_snapshot_param(): return { - 'id': 123, + 'id': UUID, 'volume_id': 12, 'status': 'available', 'volume_size': 100, @@ -52,12 +55,12 @@ def stub_snapshot_create(self, context, volume_id, name, description): def stub_snapshot_delete(self, context, snapshot): - if snapshot['id'] != 123: + if snapshot['id'] != UUID: raise exception.NotFound def stub_snapshot_get(self, context, snapshot_id): - if snapshot_id != 123: + if snapshot_id != UUID: raise exception.NotFound param = _get_default_snapshot_param() @@ -116,30 +119,30 @@ class SnapshotApiTest(test.TestCase): def test_snapshot_delete(self): self.stubs.Set(volume.api.API, "delete_snapshot", stub_snapshot_delete) - snapshot_id = 123 - req = fakes.HTTPRequest.blank('/v1/snapshots/%d' % snapshot_id) + snapshot_id = UUID + req = fakes.HTTPRequest.blank('/v1/snapshots/%s' % snapshot_id) resp = self.controller.delete(req, snapshot_id) self.assertEqual(resp.status_int, 202) def test_snapshot_delete_invalid_id(self): self.stubs.Set(volume.api.API, "delete_snapshot", stub_snapshot_delete) - snapshot_id = 234 - req = fakes.HTTPRequest.blank('/v1/snapshots/%d' % snapshot_id) + snapshot_id = INVALID_UUID + req = fakes.HTTPRequest.blank('/v1/snapshots/%s' % snapshot_id) self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, req, snapshot_id) def test_snapshot_show(self): - req = fakes.HTTPRequest.blank('/v1/snapshots/123') - resp_dict = self.controller.show(req, 123) + req = fakes.HTTPRequest.blank('/v1/snapshots/%s' % UUID) + resp_dict = self.controller.show(req, UUID) self.assertTrue('snapshot' in resp_dict) - self.assertEqual(resp_dict['snapshot']['id'], '123') + self.assertEqual(resp_dict['snapshot']['id'], UUID) def test_snapshot_show_invalid_id(self): - snapshot_id = 234 - req = fakes.HTTPRequest.blank('/v1/snapshots/%d' % snapshot_id) + snapshot_id = INVALID_UUID + req = fakes.HTTPRequest.blank('/v1/snapshots/%s' % snapshot_id) self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req, @@ -154,7 +157,7 @@ class SnapshotApiTest(test.TestCase): self.assertEqual(len(resp_snapshots), 1) resp_snapshot = resp_snapshots.pop() - self.assertEqual(resp_snapshot['id'], '123') + self.assertEqual(resp_snapshot['id'], UUID) class SnapshotSerializerTest(test.TestCase): diff --git a/cinder/tests/policy.json b/cinder/tests/policy.json index 940dedb45..1d4eff39e 100644 --- a/cinder/tests/policy.json +++ b/cinder/tests/policy.json @@ -21,5 +21,6 @@ "volume:get_all_snapshots": [], "volume_extension:types_manage": [], - "volume_extension:types_extra_specs": [] + "volume_extension:types_extra_specs": [], + "volume_extension:extended_snapshot_attributes": [] } diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py index b53eb5b14..d4f6ab592 100644 --- a/cinder/volume/manager.py +++ b/cinder/volume/manager.py @@ -230,10 +230,11 @@ class VolumeManager(manager.SchedulerDependentManager): def attach_volume(self, context, volume_id, instance_uuid, mountpoint): """Updates db to show volume is attached""" # TODO(vish): refactor this into a more general "reserve" + # TODO(sleepsonthefloor): Is this 'elevated' appropriate? if not utils.is_uuid_like(instance_uuid): raise exception.InvalidUUID(instance_uuid) - self.db.volume_attached(context, + self.db.volume_attached(context.elevated(), volume_id, instance_uuid, mountpoint) @@ -241,7 +242,8 @@ class VolumeManager(manager.SchedulerDependentManager): def detach_volume(self, context, volume_id): """Updates db to show volume is detached""" # TODO(vish): refactor this into a more general "unreserve" - self.db.volume_detached(context, volume_id) + # TODO(sleepsonthefloor): Is this 'elevated' appropriate? + self.db.volume_detached(context.elevated(), volume_id) def initialize_connection(self, context, volume_id, connector): """Prepare volume for connection from host represented by connector. diff --git a/etc/cinder/policy.json b/etc/cinder/policy.json index 556a5b7c7..660f29356 100644 --- a/etc/cinder/policy.json +++ b/etc/cinder/policy.json @@ -11,5 +11,6 @@ "volume:get_all_snapshots": [], "volume_extension:types_manage": [["rule:admin_api"]], - "volume_extension:types_extra_specs": [["rule:admin_api"]] + "volume_extension:types_extra_specs": [["rule:admin_api"]], + "volume_extension:extended_snapshot_attributes": [] } diff --git a/tox.ini b/tox.ini index 84cbb975a..a9edab8a9 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = -r{toxinidir}/tools/pip-requires commands = /bin/bash run_tests.sh -N -P {posargs} [testenv:pep8] -deps = pep8 +deps = pep8==1.1 commands = pep8 --repeat --show-source cinder setup.py [testenv:venv] -- 2.45.2