Merge "Update initialize_connection to use versionedobjects"

This commit is contained in:
Jenkins 2016-07-18 10:23:17 +00:00 committed by Gerrit Code Review
commit 6156a45591
7 changed files with 115 additions and 106 deletions

View File

@ -739,24 +739,22 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
attachment = self.volume_api.attach(self.ctx, volume, fake.INSTANCE_ID,
None, mountpoint, 'rw')
# volume is attached
volume = db.volume_get(self.ctx, volume['id'])
self.assertEqual('in-use', volume['status'])
volume = objects.Volume.get_by_id(self.ctx, volume.id)
self.assertEqual('in-use', volume.status)
self.assertEqual(fake.INSTANCE_ID, attachment['instance_uuid'])
self.assertEqual(mountpoint, attachment['mountpoint'])
self.assertEqual('attached', attachment['attach_status'])
admin_metadata = volume['volume_admin_metadata']
admin_metadata = volume.admin_metadata
self.assertEqual(2, len(admin_metadata))
self.assertEqual('readonly', admin_metadata[0]['key'])
self.assertEqual('False', admin_metadata[0]['value'])
self.assertEqual('attached_mode', admin_metadata[1]['key'])
self.assertEqual('rw', admin_metadata[1]['value'])
self.assertEqual('False', admin_metadata['readonly'])
self.assertEqual('rw', admin_metadata['attached_mode'])
conn_info = self.volume_api.initialize_connection(self.ctx,
volume,
connector)
self.assertEqual('rw', conn_info['data']['access_mode'])
# build request to force detach
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# request status of 'error'
@ -769,17 +767,16 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
resp = req.get_response(app())
# request is accepted
self.assertEqual(202, resp.status_int)
volume = db.volume_get(self.ctx, volume['id'])
volume.refresh()
self.assertRaises(exception.VolumeAttachmentNotFound,
db.volume_attachment_get,
self.ctx, attachment['id'])
# status changed to 'available'
self.assertEqual('available', volume['status'])
admin_metadata = volume['volume_admin_metadata']
self.assertEqual('available', volume.status)
admin_metadata = volume.admin_metadata
self.assertEqual(1, len(admin_metadata))
self.assertEqual('readonly', admin_metadata[0]['key'], 'readonly')
self.assertEqual('False', admin_metadata[0]['value'])
self.assertEqual('False', admin_metadata['readonly'])
def test_force_detach_host_attached_volume(self):
# current status is available
@ -793,24 +790,22 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
attachment = self.volume_api.attach(self.ctx, volume, None, host_name,
mountpoint, 'ro')
# volume is attached
volume = db.volume_get(self.ctx, volume['id'])
self.assertEqual('in-use', volume['status'])
volume.refresh()
self.assertEqual('in-use', volume.status)
self.assertIsNone(attachment['instance_uuid'])
self.assertEqual(host_name, attachment['attached_host'])
self.assertEqual(mountpoint, attachment['mountpoint'])
self.assertEqual('attached', attachment['attach_status'])
admin_metadata = volume['volume_admin_metadata']
admin_metadata = volume.admin_metadata
self.assertEqual(2, len(admin_metadata))
self.assertEqual('readonly', admin_metadata[0]['key'])
self.assertEqual('False', admin_metadata[0]['value'])
self.assertEqual('attached_mode', admin_metadata[1]['key'])
self.assertEqual('ro', admin_metadata[1]['value'])
self.assertEqual('False', admin_metadata['readonly'])
self.assertEqual('ro', admin_metadata['attached_mode'])
conn_info = self.volume_api.initialize_connection(self.ctx,
volume, connector)
self.assertEqual('ro', conn_info['data']['access_mode'])
# build request to force detach
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# request status of 'error'
@ -823,16 +818,15 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
resp = req.get_response(app())
# request is accepted
self.assertEqual(202, resp.status_int)
volume = db.volume_get(self.ctx, volume['id'])
volume.refresh()
self.assertRaises(exception.VolumeAttachmentNotFound,
db.volume_attachment_get,
self.ctx, attachment['id'])
# status changed to 'available'
self.assertEqual('available', volume['status'])
admin_metadata = volume['volume_admin_metadata']
admin_metadata = volume['admin_metadata']
self.assertEqual(1, len(admin_metadata))
self.assertEqual('readonly', admin_metadata[0]['key'])
self.assertEqual('False', admin_metadata[0]['value'])
self.assertEqual('False', admin_metadata['readonly'])
def test_volume_force_detach_raises_remote_error(self):
# current status is available
@ -845,17 +839,15 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
attachment = self.volume_api.attach(self.ctx, volume, fake.INSTANCE_ID,
None, mountpoint, 'rw')
# volume is attached
volume = db.volume_get(self.ctx, volume['id'])
self.assertEqual('in-use', volume['status'])
volume.refresh()
self.assertEqual('in-use', volume.status)
self.assertEqual(fake.INSTANCE_ID, attachment['instance_uuid'])
self.assertEqual(mountpoint, attachment['mountpoint'])
self.assertEqual('attached', attachment['attach_status'])
admin_metadata = volume['volume_admin_metadata']
admin_metadata = volume.admin_metadata
self.assertEqual(2, len(admin_metadata))
self.assertEqual('readonly', admin_metadata[0]['key'])
self.assertEqual('False', admin_metadata[0]['value'])
self.assertEqual('attached_mode', admin_metadata[1]['key'])
self.assertEqual('rw', admin_metadata[1]['value'])
self.assertEqual('False', admin_metadata['readonly'])
self.assertEqual('rw', admin_metadata['attached_mode'])
conn_info = self.volume_api.initialize_connection(self.ctx,
volume,
connector)
@ -866,7 +858,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
with mock.patch.object(volume_api.API, 'detach',
side_effect=volume_remote_error):
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
body = {'os-force_detach': {'attachment_id': fake.ATTACHMENT_ID}}
@ -883,7 +875,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
with mock.patch.object(volume_api.API, 'detach',
side_effect=volume_remote_error):
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
body = {'os-force_detach': {'attachment_id': fake.ATTACHMENT_ID}}
@ -901,7 +893,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
with mock.patch.object(volume_api.API, 'detach',
side_effect=volume_remote_error):
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
body = {'os-force_detach': {'attachment_id': fake.ATTACHMENT_ID,
@ -927,17 +919,15 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
attachment = self.volume_api.attach(self.ctx, volume, fake.INSTANCE_ID,
None, mountpoint, 'rw')
# volume is attached
volume = db.volume_get(self.ctx, volume['id'])
self.assertEqual('in-use', volume['status'])
volume.refresh()
self.assertEqual('in-use', volume.status)
self.assertEqual(fake.INSTANCE_ID, attachment['instance_uuid'])
self.assertEqual(mountpoint, attachment['mountpoint'])
self.assertEqual('attached', attachment['attach_status'])
admin_metadata = volume['volume_admin_metadata']
admin_metadata = volume.admin_metadata
self.assertEqual(2, len(admin_metadata))
self.assertEqual('readonly', admin_metadata[0]['key'])
self.assertEqual('False', admin_metadata[0]['value'])
self.assertEqual('attached_mode', admin_metadata[1]['key'])
self.assertEqual('rw', admin_metadata[1]['value'])
self.assertEqual('False', admin_metadata['readonly'])
self.assertEqual('rw', admin_metadata['attached_mode'])
conn_info = self.volume_api.initialize_connection(self.ctx,
volume,
connector)
@ -947,7 +937,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
with mock.patch.object(volume_api.API, 'detach',
side_effect=volume_remote_error):
req = webob.Request.blank('/v2/%s/volumes/%s/action' %
(fake.PROJECT_ID, volume['id']))
(fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
body = {'os-force_detach': {'attachment_id': fake.ATTACHMENT_ID,

View File

@ -15,6 +15,7 @@
from oslo_utils import timeutils
from cinder import exception
from cinder import objects
from cinder.objects import fields
from cinder.tests.unit.brick import fake_lvm
from cinder.volume import driver
@ -39,10 +40,16 @@ class FakeISCSIDriver(lvm.LVMVolumeDriver):
pass
def initialize_connection(self, volume, connector):
volume_metadata = {}
for metadata in volume['volume_admin_metadata']:
volume_metadata[metadata['key']] = metadata['value']
# NOTE(thangp): There are several places in the core cinder code where
# the volume passed through is a dict and not an oslo_versionedobject.
# We need to react appropriately to what type of volume is passed in,
# until the switch over to oslo_versionedobjects is complete.
if isinstance(volume, objects.Volume):
volume_metadata = volume.admin_metadata
else:
volume_metadata = {}
for metadata in volume['volume_admin_metadata']:
volume_metadata[metadata['key']] = metadata['value']
access_mode = volume_metadata.get('attached_mode')
if access_mode is None:

View File

@ -2166,16 +2166,19 @@ class VolumeTestCase(BaseVolumeTestCase):
_mock_volume_admin_metadata_get,
mock_get_target):
"""Make sure initialize_connection returns correct information."""
_fake_admin_meta = {'fake-key': 'fake-value'}
_fake_admin_meta = [{'key': 'fake-key', 'value': 'fake-value'}]
_fake_volume = {'volume_type_id': fake.VOLUME_TYPE_ID,
'name': 'fake_name',
'host': 'fake_host',
'id': fake.VOLUME_ID,
'volume_admin_metadata': _fake_admin_meta}
fake_volume_obj = fake_volume.fake_volume_obj(self.context,
**_fake_volume)
_mock_volume_get.return_value = _fake_volume
_mock_volume_update.return_value = _fake_volume
_mock_volume_admin_metadata_get.return_value = _fake_admin_meta
_mock_volume_admin_metadata_get.return_value = {
'fake-key': 'fake-value'}
connector = {'ip': 'IP', 'initiator': 'INITIATOR'}
qos_values = {'consumer': 'front-end',
@ -2195,53 +2198,42 @@ class VolumeTestCase(BaseVolumeTestCase):
'key2': 'value2'}
# initialize_connection() passes qos_specs that is designated to
# be consumed by front-end or both front-end and back-end
conn_info = self.volume.initialize_connection(self.context,
fake.VOLUME_ID,
connector)
conn_info = self.volume.initialize_connection(
self.context, fake.VOLUME_ID, connector,
volume=fake_volume_obj)
self.assertDictMatch(qos_specs_expected,
conn_info['data']['qos_specs'])
qos_values.update({'consumer': 'both'})
conn_info = self.volume.initialize_connection(self.context,
fake.VOLUME_ID,
connector)
conn_info = self.volume.initialize_connection(
self.context, fake.VOLUME_ID, connector,
volume=fake_volume_obj)
self.assertDictMatch(qos_specs_expected,
conn_info['data']['qos_specs'])
# initialize_connection() skips qos_specs that is designated to be
# consumed by back-end only
qos_values.update({'consumer': 'back-end'})
type_qos.return_value = dict(qos_specs=qos_values)
conn_info = self.volume.initialize_connection(self.context,
fake.VOLUME_ID,
connector)
conn_info = self.volume.initialize_connection(
self.context, fake.VOLUME_ID, connector,
volume=fake_volume_obj)
self.assertIsNone(conn_info['data']['qos_specs'])
@mock.patch.object(fake_driver.FakeISCSIDriver, 'create_export')
@mock.patch.object(db.sqlalchemy.api, 'volume_get')
@mock.patch.object(db, 'volume_update')
def test_initialize_connection_export_failure(self,
_mock_volume_update,
_mock_volume_get,
_mock_create_export):
"""Test exception path for create_export failure."""
_fake_admin_meta = {'fake-key': 'fake-value'}
_fake_volume = {'volume_type_id': fake.VOLUME_TYPE_ID,
'name': 'fake_name',
'host': 'fake_host',
'id': fake.VOLUME_ID,
'volume_admin_metadata': _fake_admin_meta}
_mock_volume_get.return_value = _fake_volume
_mock_volume_update.return_value = _fake_volume
volume = tests_utils.create_volume(
self.context, admin_metadata={'fake-key': 'fake-value'},
volume_type_id=fake.VOLUME_TYPE_ID, **self.volume_params)
_mock_create_export.side_effect = exception.CinderException
connector = {'ip': 'IP', 'initiator': 'INITIATOR'}
self.assertRaises(exception.VolumeBackendAPIException,
self.volume.initialize_connection,
self.context,
fake.VOLUME_ID,
connector)
self.context, fake.VOLUME_ID, connector,
volume=volume)
def test_run_attach_detach_volume_for_instance(self):
"""Make sure volume can be attached and detached from instance."""
@ -6384,8 +6376,6 @@ class DiscardFlagTestCase(BaseVolumeTestCase):
def setUp(self):
super(DiscardFlagTestCase, self).setUp()
self.volume.driver = mock.MagicMock()
self.mock_db = mock.MagicMock()
self.volume.db = self.mock_db
@ddt.data(dict(config_discard_flag=True,
driver_discard_flag=None,
@ -6413,15 +6403,6 @@ class DiscardFlagTestCase(BaseVolumeTestCase):
config_discard_flag,
driver_discard_flag,
expected_flag):
volume_properties = {'volume_type_id': None}
def _get_item(key):
return volume_properties[key]
mock_volume = mock.MagicMock()
mock_volume.__getitem__.side_effect = _get_item
self.mock_db.volume_get.return_value = mock_volume
self.mock_db.volume_update.return_value = mock_volume
self.volume.driver.create_export.return_value = None
connector = {'ip': 'IP', 'initiator': 'INITIATOR'}
@ -6444,7 +6425,13 @@ class DiscardFlagTestCase(BaseVolumeTestCase):
self.volume.driver.configuration.safe_get.side_effect = _safe_get
conn_info = self.volume.initialize_connection(self.context, 'id',
connector)
with mock.patch.object(objects, 'Volume') as mock_vol:
volume = tests_utils.create_volume(self.context)
volume.volume_type_id = None
mock_vol.get_by_id.return_value = volume
conn_info = self.volume.initialize_connection(self.context,
volume.id,
connector)
self.assertEqual(expected_flag, conn_info['data'].get('discard'))

View File

@ -341,10 +341,18 @@ class VolumeRpcAPITestCase(test.TestCase):
'disk_format': 'fake_type'},
version='2.0')
def test_initialize_connection(self):
@mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
def test_initialize_connection(self, mock_can_send_version):
self._test_volume_api('initialize_connection',
rpc_method='call',
volume=self.fake_volume,
volume=self.fake_volume_obj,
connector='fake_connector',
version='2.3')
mock_can_send_version.return_value = False
self._test_volume_api('initialize_connection',
rpc_method='call',
volume=self.fake_volume_obj,
connector='fake_connector',
version='2.0')

View File

@ -660,7 +660,7 @@ class API(base.Base):
@wrap_check_policy
def initialize_connection(self, context, volume, connector):
if volume['status'] == 'maintenance':
if volume.status == 'maintenance':
LOG.info(_LI('Unable to initialize the connection for '
'volume, because it is in '
'maintenance.'), resource=volume)

View File

@ -157,7 +157,7 @@ MAPPING = {
class VolumeManager(manager.SchedulerDependentManager):
"""Manages attachable block storage devices."""
RPC_API_VERSION = '2.2'
RPC_API_VERSION = '2.3'
target = messaging.Target(version=RPC_API_VERSION)
@ -1288,7 +1288,8 @@ class VolumeManager(manager.SchedulerDependentManager):
exc_info=True, resource={'type': 'image',
'id': image_id})
def initialize_connection(self, context, volume_id, connector):
def initialize_connection(self, context, volume_id, connector,
volume=None):
"""Prepare volume for connection from host represented by connector.
This method calls the driver initialize_connection and returns
@ -1325,12 +1326,16 @@ class VolumeManager(manager.SchedulerDependentManager):
json in various places, so it should not contain any non-json
data types.
"""
# FIXME(bluex): Remove this in v3.0 of RPC API.
if volume is None:
# For older clients, mimic the old behavior and look up the volume
# by its volume_id.
volume = objects.Volume.get_by_id(context, volume_id)
# NOTE(flaper87): Verify the driver is enabled
# before going forward. The exception will be caught
# and the volume status updated.
utils.require_driver_initialized(self.driver)
volume = self.db.volume_get(context, volume_id)
model_update = None
try:
self.driver.validate_connector(connector)
except exception.InvalidConnectorException as err:
@ -1351,9 +1356,8 @@ class VolumeManager(manager.SchedulerDependentManager):
try:
if model_update:
volume = self.db.volume_update(context,
volume_id,
model_update)
volume.update(model_update)
volume.save()
except exception.CinderException as ex:
LOG.exception(_LE("Model update failed."), resource=volume)
raise exception.ExportFailure(reason=six.text_type(ex))
@ -1370,7 +1374,7 @@ class VolumeManager(manager.SchedulerDependentManager):
raise exception.VolumeBackendAPIException(data=err_msg)
# Add qos_specs to connection info
typeid = volume['volume_type_id']
typeid = volume.volume_type_id
specs = None
if typeid:
res = volume_types.get_volume_type_qos_specs(typeid)
@ -1384,8 +1388,7 @@ class VolumeManager(manager.SchedulerDependentManager):
conn_info['data'].update(qos_spec)
# Add access_mode to connection info
volume_metadata = self.db.volume_admin_metadata_get(context.elevated(),
volume_id)
volume_metadata = volume.admin_metadata
access_mode = volume_metadata.get('attached_mode')
if access_mode is None:
# NOTE(zhiyan): client didn't call 'os-attach' before
@ -1396,7 +1399,7 @@ class VolumeManager(manager.SchedulerDependentManager):
# Add encrypted flag to connection_info if not set in the driver.
if conn_info['data'].get('encrypted') is None:
encrypted = bool(volume.get('encryption_key_id'))
encrypted = bool(volume.encryption_key_id)
conn_info['data']['encrypted'] = encrypted
# Add discard flag to connection_info if not set in the driver and

View File

@ -101,12 +101,21 @@ class VolumeAPI(rpc.RPCAPI):
2.0 - Remove 1.x compatibility
2.1 - Add get_manageable_volumes() and get_manageable_snapshots().
2.2 - Adds support for sending objects over RPC in manage_existing().
2.3 - Adds support for sending objects over RPC in
initialize_connection().
"""
RPC_API_VERSION = '2.2'
RPC_API_VERSION = '2.3'
TOPIC = CONF.volume_topic
BINARY = 'cinder-volume'
def _compat_ver(self, current, *legacy):
versions = (current,) + legacy
for version in versions[:-1]:
if self.client.can_send_version(version):
return version
return versions[-1]
def _get_cctxt(self, host, version):
new_host = utils.get_volume_rpc_host(host)
return self.client.prepare(server=new_host, version=version)
@ -190,10 +199,15 @@ class VolumeAPI(rpc.RPCAPI):
image_meta=image_meta)
def initialize_connection(self, ctxt, volume, connector):
cctxt = self._get_cctxt(volume['host'], '2.0')
return cctxt.call(ctxt, 'initialize_connection',
volume_id=volume['id'],
connector=connector)
version = self._compat_ver('2.3', '2.0')
msg_args = {'volume_id': volume.id, 'connector': connector,
'volume': volume}
if version == '2.0':
del msg_args['volume']
cctxt = self._get_cctxt(volume['host'], version=version)
return cctxt.call(ctxt, 'initialize_connection', **msg_args)
def terminate_connection(self, ctxt, volume, connector, force=False):
cctxt = self._get_cctxt(volume['host'], '2.0')