]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add action extensions to support nova integration.
authorJohn Griffith <john.griffith@solidfire.com>
Wed, 13 Jun 2012 18:38:35 +0000 (12:38 -0600)
committerAnthony Young <sleepsonthefloor@gmail.com>
Tue, 26 Jun 2012 21:35:56 +0000 (14:35 -0700)
 * 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

12 files changed:
cinder/api/openstack/volume/__init__.py
cinder/api/openstack/volume/contrib/extended_snapshot_attributes.py [new file with mode: 0644]
cinder/api/openstack/volume/contrib/volume_actions.py [new file with mode: 0644]
cinder/api/openstack/volume/snapshots.py
cinder/tests/api/openstack/fakes.py
cinder/tests/api/openstack/volume/contrib/test_extended_snapshot_attributes.py [new file with mode: 0644]
cinder/tests/api/openstack/volume/contrib/test_volume_actions.py [new file with mode: 0644]
cinder/tests/api/openstack/volume/test_snapshots.py
cinder/tests/policy.json
cinder/volume/manager.py
etc/cinder/policy.json
tox.ini

index 2d9ac302bd83181cf93451cae6563b24ddbe61be..1b85ed906d03c95130f6ade2742cf02b9b145161 100644 (file)
@@ -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 (file)
index 0000000..ba5ec76
--- /dev/null
@@ -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 (file)
index 0000000..1610002
--- /dev/null
@@ -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]
index f6d5304ec1881ad151be5267b5045b515ef8bc96..22454e991ce8047774a75f63243cc5c5adc55c0d 100644 (file)
@@ -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
 
 
index 019acf62ea69028bb1cfd4b0511aab52f5cf714a..959606ffe801042fecaed408652ead79ac41b205 100644 (file)
@@ -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 (file)
index 0000000..953965a
--- /dev/null
@@ -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 (file)
index 0000000..5790220
--- /dev/null
@@ -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)
index 8c87da514613d6eb51cc7aa80c18f86073e9fce2..bc60e289a7eb6c2df6aba34b74eafae6cc637f61 100644 (file)
@@ -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):
index 940dedb455d41e6cd2a39d3799f6ccd67a9b08c3..1d4eff39e28786818763d2442a8fdebf12c1b075 100644 (file)
@@ -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": []
 }
index b53eb5b14e82d38a69af8bc6170bb061cce7aa33..d4f6ab592cf05e5adc3a3089cc08fd6a4207e03a 100644 (file)
@@ -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.
index 556a5b7c79d3b7084653e5f30382b22ddb00a1fd..660f29356e7e350be9fec9219b9b967521c20c24 100644 (file)
@@ -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 84cbb975a859ba12c9cf38ae65613deb0f81ffb8..a9edab8a923e2714cdbbfae0d83974f29ae54b54 100644 (file)
--- 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]