Fix live-migrations usage of the wrong connector information

During the post_live_migration step for the Nova libvirt driver
an incorrect assumption is being made about the connector
information being sent to _disconnect_volume. It is assumed that
the connection information on the source and destination is the
same but that is not always the case. The BDM, where the
connector information is being retrieved from only contains the
connection information for the destination. This will not work
when trying to disconnect volumes from the source during live
migration as the properties such as the target_lun and
initiator_target_map could be different. This ends up leaving
behind dangling LUNs and possibly removing the incorrect
volume's LUNs.

The solution proposed here utilizes the connection_info that
can be retrieved for a host from Cinder's initialize_connection
API. This connection information contains the correct data for
the source host and allows volume LUNs to be removed properly.

Change-Id: I3dfb75eb58dfbc66b218bcee473af4c2ac282eb6
Closes-Bug: #1475411
Closes-Bug: #1288039
Closes-Bug: #1423772
This commit is contained in:
Anthony Lee 2015-07-16 13:02:00 -07:00
parent bfec231f80
commit 8b649aa86f
2 changed files with 47 additions and 7 deletions

View File

@ -6975,10 +6975,22 @@ class LibvirtConnTestCase(test.NoDBTestCase):
def test_post_live_migration(self):
vol = {'block_device_mapping': [
{'connection_info': 'dummy1', 'mount_device': '/dev/sda'},
{'connection_info': 'dummy2', 'mount_device': '/dev/sdb'}]}
{'connection_info': {
'data': {'multipath_id': 'dummy1'},
'serial': 'fake_serial1'},
'mount_device': '/dev/sda',
},
{'connection_info': {
'data': {},
'serial': 'fake_serial2'},
'mount_device': '/dev/sdb', }]}
def fake_initialize_connection(context, volume_id, connector):
return {'data': {}}
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
fake_connector = {'host': 'fake'}
inst_ref = {'id': 'foo'}
cntx = context.get_admin_context()
@ -6986,16 +6998,22 @@ class LibvirtConnTestCase(test.NoDBTestCase):
with contextlib.nested(
mock.patch.object(driver, 'block_device_info_get_mapping',
return_value=vol['block_device_mapping']),
mock.patch.object(drvr, "get_volume_connector",
return_value=fake_connector),
mock.patch.object(drvr._volume_api, "initialize_connection",
side_effect=fake_initialize_connection),
mock.patch.object(drvr, '_disconnect_volume')
) as (block_device_info_get_mapping, _disconnect_volume):
) as (block_device_info_get_mapping, get_volume_connector,
initialize_connection, _disconnect_volume):
drvr.post_live_migration(cntx, inst_ref, vol)
block_device_info_get_mapping.assert_has_calls([
mock.call(vol)])
get_volume_connector.assert_has_calls([
mock.call(inst_ref)])
_disconnect_volume.assert_has_calls([
mock.call(v['connection_info'],
v['mount_device'].rpartition("/")[2])
for v in vol['block_device_mapping']])
mock.call({'data': {'multipath_id': 'dummy1'}}, 'sda'),
mock.call({'data': {}}, 'sdb')])
def test_get_instance_disk_info_excludes_volumes(self):
# Test data

View File

@ -6063,8 +6063,30 @@ class LibvirtDriver(driver.ComputeDriver):
# Disconnect from volume server
block_device_mapping = driver.block_device_info_get_mapping(
block_device_info)
connector = self.get_volume_connector(instance)
volume_api = self._volume_api
for vol in block_device_mapping:
connection_info = vol['connection_info']
# Retrieve connection info from Cinder's initialize_connection API.
# The info returned will be accurate for the source server.
volume_id = vol['connection_info']['serial']
connection_info = volume_api.initialize_connection(context,
volume_id,
connector)
# TODO(leeantho) The following multipath_id logic is temporary
# and will be removed in the future once os-brick is updated
# to handle multipath for drivers in a more efficient way.
# For now this logic is needed to ensure the connection info
# data is correct.
# Pull out multipath_id from the bdm information. The
# multipath_id can be placed into the connection info
# because it is based off of the volume and will be the
# same on the source and destination hosts.
if 'multipath_id' in vol['connection_info']['data']:
multipath_id = vol['connection_info']['data']['multipath_id']
connection_info['data']['multipath_id'] = multipath_id
disk_dev = vol['mount_device'].rpartition("/")[2]
self._disconnect_volume(connection_info, disk_dev)