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
This commit is contained in:
parent
2c6e273259
commit
a9c8212f69
@ -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",
|
||||
|
@ -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})
|
114
cinder/api/openstack/volume/contrib/volume_actions.py
Normal file
114
cinder/api/openstack/volume/contrib/volume_actions.py
Normal file
@ -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]
|
@ -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
|
||||
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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()
|
107
cinder/tests/api/openstack/volume/contrib/test_volume_actions.py
Normal file
107
cinder/tests/api/openstack/volume/contrib/test_volume_actions.py
Normal file
@ -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)
|
@ -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):
|
||||
|
@ -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": []
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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": []
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user