Enable multi host live migration with NPIV Volumes
When the migration occurs the order of the WWPNs is important. The first WWPN is the client adapter that is logged in. This gets obfusicated when a live migration is occurring. The source is primary and logged in, and the target is secondary (also logged in). This code adds a post migration call for the volume drivers. It will handle saving the updated WWPN order in the system metadata (as the instance gets saved on the target host, post live migration). It also introduces a mig_vol_stor concept into post migration so that the volume adapters can pass data from one connection to another. This is important for NPIV as it is concerned about fabrics rather than the specific volumes within the fabric. Change-Id: Ia4f3d0c35b10f6f106ba27b0fb9f408fbeb670fb
This commit is contained in:
parent
5135a3fa7f
commit
5e1a83ced4
@ -293,7 +293,7 @@ BODY{
|
||||
<PortName kxe="false" kb="CUR">fcs0</PortName>
|
||||
<UniqueDeviceID kb="ROR" kxe="false">1aU78CB.001.WZS007Y-P1-C3-T1</UniqueDeviceID>
|
||||
<WWPN kb="CUR" kxe="false">21000024FF649104</WWPN>
|
||||
<AvailablePorts kb="ROR" kxe="true">64</AvailablePorts>
|
||||
<AvailablePorts kb="ROR" kxe="true">63</AvailablePorts>
|
||||
<TotalPorts kb="ROR" kxe="true">64</TotalPorts>
|
||||
</PhysicalFibreChannelPort>
|
||||
<PhysicalFibreChannelPort schemaVersion="V1_2_0">
|
||||
|
@ -1136,4 +1136,4 @@ class TestPowerVMDriver(test.TestCase):
|
||||
self.drv.post_live_migration_at_destination(
|
||||
'context', self.lpm_inst, 'network_info')
|
||||
self.lpm.post_live_migration_at_destination.assert_called_once_with(
|
||||
'network_info')
|
||||
'network_info', [])
|
||||
|
@ -141,7 +141,7 @@ class TestLPM(test.TestCase):
|
||||
self.lpmsrc.post_live_migration_at_source('network_info')
|
||||
|
||||
def test_post_live_mig_dest(self):
|
||||
self.lpmdst.post_live_migration_at_destination('network_info')
|
||||
self.lpmdst.post_live_migration_at_destination('network_info', [])
|
||||
|
||||
@mock.patch('pypowervm.tasks.migration.migrate_recover')
|
||||
def test_rollback(self, mock_migr):
|
||||
|
@ -345,7 +345,7 @@ class TestNPIVAdapter(test.TestCase):
|
||||
wwpns = self.vol_drv.wwpns()
|
||||
|
||||
# Check
|
||||
self.assertListEqual(['AA', 'BB', 'CC', 'DD'], wwpns)
|
||||
self.assertListEqual(['AA', 'CC'], wwpns)
|
||||
self.assertEqual('21000024FF649104,AA,BB,21000024FF649105,CC,DD',
|
||||
self.vol_drv.instance.system_metadata[meta_key])
|
||||
self.assertEqual(1, mock_add_port.call_count)
|
||||
@ -368,7 +368,7 @@ class TestNPIVAdapter(test.TestCase):
|
||||
self.vol_drv._sys_meta_fabric_key('A'): 'phys1,a,b,phys2,c,d'}
|
||||
|
||||
# Invoke and Verify
|
||||
self.assertListEqual(['a', 'b', 'c', 'd'], self.vol_drv.wwpns())
|
||||
self.assertListEqual(['a', 'c'], self.vol_drv.wwpns())
|
||||
|
||||
@mock.patch('nova_powervm.virt.powervm.volume.npiv.NPIVVolumeAdapter.'
|
||||
'_get_fabric_state')
|
||||
@ -382,4 +382,32 @@ class TestNPIVAdapter(test.TestCase):
|
||||
# Invoke and Verify
|
||||
for state in [task_states.DELETING, task_states.MIGRATING]:
|
||||
self.vol_drv.instance.task_state = state
|
||||
self.assertListEqual(['a', 'b', 'c', 'd'], self.vol_drv.wwpns())
|
||||
self.assertListEqual(['a', 'c'], self.vol_drv.wwpns())
|
||||
|
||||
@mock.patch('nova_powervm.virt.powervm.volume.npiv.NPIVVolumeAdapter.'
|
||||
'_set_fabric_meta')
|
||||
@mock.patch('nova_powervm.virt.powervm.volume.npiv.NPIVVolumeAdapter.'
|
||||
'_get_fabric_meta')
|
||||
@mock.patch('nova_powervm.virt.powervm.volume.npiv.NPIVVolumeAdapter.'
|
||||
'_fabric_names')
|
||||
def test_post_live_migration_at_destination(
|
||||
self, mock_fabric_names, mock_get_fabric_meta,
|
||||
mock_set_fabric_meta):
|
||||
mock_fabric_names.return_value = ['A', 'B']
|
||||
mock_get_fabric_meta.side_effect = [
|
||||
[('11', 'AA BB'), ('22', 'CC DD')],
|
||||
[('33', 'EE FF')]]
|
||||
|
||||
# Execute the test
|
||||
mig_vol_stor = {}
|
||||
self.vol_drv.post_live_migration_at_destination(mig_vol_stor)
|
||||
|
||||
mock_set_fabric_meta.assert_any_call(
|
||||
'A', [('11', 'BB AA'), ('22', 'DD CC')])
|
||||
mock_set_fabric_meta.assert_any_call(
|
||||
'B', [('33', 'FF EE')])
|
||||
|
||||
# Invoke a second time. Should not 're-flip' or even call set.
|
||||
mock_set_fabric_meta.reset_mock()
|
||||
self.vol_drv.post_live_migration_at_destination(mig_vol_stor)
|
||||
self.assertFalse(mock_set_fabric_meta.called)
|
||||
|
@ -993,13 +993,10 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
mig = self.live_migrations[instance.uuid]
|
||||
|
||||
# Get a volume driver for each volume
|
||||
vol_drvs = []
|
||||
bdms = self._extract_bdm(block_device_info)
|
||||
for bdm in bdms or []:
|
||||
vol_drvs.append(
|
||||
self._get_inst_vol_adpt(
|
||||
context, instance, conn_info=bdm.get('connection_info')))
|
||||
vol_drvs = self._build_vol_drivers(context, instance,
|
||||
block_device_info)
|
||||
|
||||
# Run pre-live migration
|
||||
mig.pre_live_migration(context, block_device_info, network_info,
|
||||
disk_info, migrate_data, vol_drvs)
|
||||
|
||||
@ -1133,9 +1130,26 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
instance=instance)
|
||||
mig = self.live_migrations[instance.uuid]
|
||||
mig.instance = instance
|
||||
mig.post_live_migration_at_destination(network_info)
|
||||
|
||||
# Build the volume drivers
|
||||
vol_drvs = self._build_vol_drivers(context, instance,
|
||||
block_device_info)
|
||||
|
||||
# Run post live migration
|
||||
mig.post_live_migration_at_destination(network_info, vol_drvs)
|
||||
del self.live_migrations[instance.uuid]
|
||||
|
||||
def _build_vol_drivers(self, context, instance, block_device_info):
|
||||
"""Builds the volume connector drivers for a block device info."""
|
||||
# Get a volume driver for each volume
|
||||
vol_drvs = []
|
||||
bdms = self._extract_bdm(block_device_info)
|
||||
for bdm in bdms or []:
|
||||
vol_drvs.append(
|
||||
self._get_inst_vol_adpt(
|
||||
context, instance, conn_info=bdm.get('connection_info')))
|
||||
return vol_drvs
|
||||
|
||||
def unfilter_instance(self, instance, network_info):
|
||||
"""Stop filtering instance."""
|
||||
# No op for PowerVM
|
||||
|
@ -181,15 +181,33 @@ class LiveMigrationDest(LiveMigration):
|
||||
host=self.drvr.host_wrapper.system_name,
|
||||
name=self.instance.name, volume=vol_drv.volume_id)
|
||||
|
||||
def post_live_migration_at_destination(self, network_info):
|
||||
def post_live_migration_at_destination(self, network_info, vol_drvs):
|
||||
"""Do post migration cleanup on destination host.
|
||||
|
||||
:param network_info: instance network information
|
||||
:param vol_drvs: volume drivers for the attached volumes
|
||||
"""
|
||||
# The LPAR should be on this host now.
|
||||
LOG.debug("Post live migration at destination.",
|
||||
instance=self.instance)
|
||||
|
||||
# An unbounded dictionary that each volume adapter can use to persist
|
||||
# data from one call to the next.
|
||||
mig_vol_stor = {}
|
||||
|
||||
# For each volume, make sure it's ready to migrate
|
||||
for vol_drv in vol_drvs:
|
||||
LOG.info(_LI('Performing post migration for volume %(volume)s'),
|
||||
dict(volume=vol_drv.volume_id))
|
||||
try:
|
||||
vol_drv.post_live_migration_at_destination(mig_vol_stor)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
# It failed.
|
||||
raise LiveMigrationVolume(
|
||||
host=self.drvr.host_wrapper.system_name,
|
||||
name=self.instance.name, volume=vol_drv.volume_id)
|
||||
|
||||
|
||||
class LiveMigrationSrc(LiveMigration):
|
||||
|
||||
|
@ -111,10 +111,22 @@ class PowerVMVolumeAdapter(object):
|
||||
"""Perform pre live migration steps for the volume on the target host.
|
||||
|
||||
This method performs any pre live migration that is needed.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def post_live_migration_at_destination(self, mig_vol_stor):
|
||||
"""Perform post live migration steps for the volume on the target host.
|
||||
|
||||
This method performs any post live migration that is needed. Is not
|
||||
required to be implemented.
|
||||
|
||||
:param mig_vol_stor: An unbounded dictionary that will be passed to
|
||||
each volume adapter during the post live migration
|
||||
call. Adapters can store data in here that may
|
||||
be used by subsequent volume adapters.
|
||||
"""
|
||||
pass
|
||||
|
||||
def connect_volume(self):
|
||||
"""Connects the volume."""
|
||||
self._connect_volume()
|
||||
|
@ -109,6 +109,45 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter):
|
||||
# happen to be no fabric changes.
|
||||
self.stg_ftsk.execute()
|
||||
|
||||
def post_live_migration_at_destination(self, mig_vol_stor):
|
||||
"""Perform post live migration steps for the volume on the target host.
|
||||
|
||||
This method performs any post live migration that is needed. Is not
|
||||
required to be implemented.
|
||||
|
||||
:param mig_vol_stor: An unbounded dictionary that will be passed to
|
||||
each volume adapter during the post live migration
|
||||
call. Adapters can store data in here that may
|
||||
be used by subsequent volume adapters.
|
||||
"""
|
||||
# This method will run on the target host after the migration is
|
||||
# completed. Right after this the instance.save is invoked from the
|
||||
# manager. Given that, we need to update the order of the WWPNs.
|
||||
# The first WWPN is the one that is logged into the fabric and this
|
||||
# will now indicate that our WWPN is logged in.
|
||||
#
|
||||
# TODO(thorst) Rather than just flipping...we should query the LPAR
|
||||
# VFC's and find the logged in WWPN. That guarantees accuracy.
|
||||
for fabric in self._fabric_names():
|
||||
# We check the mig_vol_stor to see if this fabric has already been
|
||||
# flipped. If so, we can continue.
|
||||
fabric_key = '%s_flipped' % fabric
|
||||
if mig_vol_stor.get(fabric_key, False):
|
||||
continue
|
||||
|
||||
# Must not be flipped, so execute the flip
|
||||
npiv_port_maps = self._get_fabric_meta(fabric)
|
||||
new_port_maps = []
|
||||
for port_map in npiv_port_maps:
|
||||
c_wwpns = port_map[1].split()
|
||||
c_wwpns.reverse()
|
||||
new_map = (port_map[0], " ".join(c_wwpns))
|
||||
new_port_maps.append(new_map)
|
||||
self._set_fabric_meta(fabric, new_port_maps)
|
||||
|
||||
# Store that this fabric is now flipped.
|
||||
mig_vol_stor[fabric_key] = True
|
||||
|
||||
def _is_initial_wwpn(self, fc_state, fabric):
|
||||
"""Determines if the invocation to wwpns is for a general method.
|
||||
|
||||
@ -134,6 +173,8 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter):
|
||||
def _is_migration_wwpn(self, fc_state):
|
||||
"""Determines if the WWPN call is occurring during a migration.
|
||||
|
||||
This determines if it is on the target host.
|
||||
|
||||
:param fc_state: The fabrics state.
|
||||
:return: True if the instance appears to be migrating to this host.
|
||||
False otherwise.
|
||||
@ -272,7 +313,9 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter):
|
||||
# If not None, then add the WWPNs to the response.
|
||||
if port_maps is not None:
|
||||
for mapping in port_maps:
|
||||
resp_wwpns.extend(mapping[1].split())
|
||||
# Only add the first WWPN. That is the one that will be
|
||||
# logged into the fabric.
|
||||
resp_wwpns.append(mapping[1].split()[0])
|
||||
|
||||
# The return object needs to be a list for the volume connector.
|
||||
return resp_wwpns
|
||||
@ -345,7 +388,7 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter):
|
||||
{'inst': self.instance.name, 'fabric': fabric}]
|
||||
vios_w = pvm_vfcm.find_vios_for_port_map(vios_wraps, npiv_port_map)
|
||||
|
||||
# Add the subtask to remove the specific map.
|
||||
# Add the subtask to remove the specific map
|
||||
self.stg_ftsk.wrapper_tasks[vios_w.uuid].add_functor_subtask(
|
||||
pvm_vfcm.remove_maps, self.vm_uuid, port_map=npiv_port_map,
|
||||
logspec=ls)
|
||||
@ -355,7 +398,8 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter):
|
||||
|
||||
:return: The host name.
|
||||
"""
|
||||
return self.instance.name
|
||||
host = CONF.host if len(CONF.host) < 20 else CONF.host[:20]
|
||||
return host + '_' + self.instance.name
|
||||
|
||||
def _set_fabric_state(self, fabric, state):
|
||||
"""Sets the fabric state into the instance's system metadata.
|
||||
|
Loading…
x
Reference in New Issue
Block a user