From 15e400c8a4b0a5c129c1d173aa621774934b64dc Mon Sep 17 00:00:00 2001 From: Drew Thorstensen Date: Tue, 13 Oct 2015 23:51:22 -0400 Subject: [PATCH] Do not map to management partition for NPIV NPIV based deploys attempted to put the client vFC adapters on to the fabric by creating the WWPNs ahead of time and mapping them to the management partition. This created several issues and led to poor performance. There were issues around migration: - Querying for a host could leave a stale adapter on the mgmt partition There were issues with speed: - Deploys could take over 120 seconds - Migrations had additional delays There were issues with some standard operations: - Attaching a volume on a powered off VM required logging in the WWPNs Subsequent analysis in Cinder showed that only some Cinder drivers had this requirement. Many could work without this. Given the challenges the code is removing the need to log in the WWPNs on the mgmt partition. This resolves all of the above issues. While it does limit some of the Cinder drivers when paired with NPIV, ongoing work will be done to meet with the Cinder driver owners to see if the requirements in their drivers can be relaxed. For those that can not, the vSCSI Cinder Volume attach is also an option and should provide the use cases that they need. Change-Id: Id77650545ca4e0def9977f1c2572b9f13f451e0d --- README.rst | 5 - doc/source/devref/usage.rst | 7 + .../tests/virt/powervm/volume/test_npiv.py | 182 +++++----------- nova_powervm/virt/powervm/volume/npiv.py | 206 ++++++------------ 4 files changed, 119 insertions(+), 281 deletions(-) diff --git a/README.rst b/README.rst index 2a23aed3..e9e167f1 100644 --- a/README.rst +++ b/README.rst @@ -115,11 +115,6 @@ as the libvirt driver within OpenStack. Most operations are comparable in speed. Deployment, attach/detach volumes, lifecycle, etc... are quick. -The one exception is if the operator configures the system to use N-Port ID -Virtualization for storage (NPIV). This technology provides significant speed -increases for instance disk performance, but may increase the deployment time -by several seconds. - The driver is written to support concurrent operations. It has been tested performing 10 concurrent deploys to a given compute node. diff --git a/doc/source/devref/usage.rst b/doc/source/devref/usage.rst index 5e242462..3d82d69f 100644 --- a/doc/source/devref/usage.rst +++ b/doc/source/devref/usage.rst @@ -117,6 +117,13 @@ Volume Options | fc_attach_strategy = vscsi | (StrOpt) The Fibre Channel Volume Strategy defines how FC | | | Cinder volumes should be attached to the Virtual Machine. | | | The options are: npiv or vscsi. | +| | | +| | It should be noted that if NPIV is chosen, the WWPNs will | +| | not be active on the backing fabric during the deploy. | +| | Some Cinder drivers will operate without issue. Others | +| | may query the fabric and thus will fail attachment. It is | +| | advised that if an issue occurs using NPIV, the operator | +| | fall back to vscsi based deploys. | +--------------------------------------+------------------------------------------------------------+ | vscsi_vios_connections_required = 1 | (IntOpt) Indicates a minimum number of Virtual I/O Servers | | | that are required to support a Cinder volume attach with | diff --git a/nova_powervm/tests/virt/powervm/volume/test_npiv.py b/nova_powervm/tests/virt/powervm/volume/test_npiv.py index 56ad4fc0..7378d2c8 100644 --- a/nova_powervm/tests/virt/powervm/volume/test_npiv.py +++ b/nova_powervm/tests/virt/powervm/volume/test_npiv.py @@ -90,73 +90,8 @@ class TestNPIVAdapter(test_vol.TestVolumeAdapter): self.mock_fabric_names_p.stop() self.mock_fabric_ports_p.stop() - @mock.patch('nova_powervm.virt.powervm.mgmt.get_mgmt_partition') @mock.patch('pypowervm.tasks.vfc_mapper.add_map') - @mock.patch('pypowervm.tasks.vfc_mapper.remove_maps') - def test_connect_volume(self, mock_remove_maps, mock_add_map, - mock_mgmt_lpar_id): - # Mock - self._basic_system_metadata(npiv.FS_MGMT_MAPPED) - mock_mgmt_lpar_id.return_value = mock.Mock(uuid='1') - - def validate_remove_maps(vios_w, lpar_uuid, client_adpt=None, - port_map=None, **kwargs): - self.assertEqual('1', lpar_uuid) - self.assertIsInstance(vios_w, pvm_vios.VIOS) - self.assertIsNone(client_adpt) - self.assertEqual(('21000024FF649104', 'AA BB'), port_map) - return 'removed' - mock_remove_maps.side_effect = validate_remove_maps - - def add_map(vios_w, host_uuid, vm_uuid, port_map, **kwargs): - self.assertIsInstance(vios_w, pvm_vios.VIOS) - self.assertEqual('host_uuid', host_uuid) - self.assertEqual('1234', vm_uuid) - self.assertEqual(('21000024FF649104', 'AA BB'), port_map) - return 'good' - mock_add_map.side_effect = add_map - - # Test connect volume when the fabric is mapped with mgmt partition - self.vol_drv.connect_volume() - - # Verify. Mgmt mapping should be removed - self.assertEqual(1, mock_remove_maps.call_count) - self.assertEqual(1, mock_add_map.call_count) - self.assertEqual(1, self.ft_fx.patchers['update'].mock.call_count) - self.assertEqual(npiv.FS_INST_MAPPED, - self.vol_drv._get_fabric_state('A')) - - def test_connect_volume_not_valid(self): - """Validates that a connect will fail if in a bad state.""" - self.mock_inst_wrap.can_modify_io.return_value = False, 'Bleh' - self.assertRaises(exc.VolumeAttachFailed, self.vol_drv.connect_volume) - - @mock.patch('pypowervm.tasks.vfc_mapper.add_map') - @mock.patch('pypowervm.tasks.vfc_mapper.remove_npiv_port_mappings') - def test_connect_volume_inst_mapped(self, mock_remove_p_maps, - mock_add_map): - """Test if already connected to an instance, don't do anything""" - self._basic_system_metadata(npiv.FS_INST_MAPPED) - mock_add_map.return_value = None - - # Test subsequent connect volume calls when the fabric is mapped with - # inst partition - self.vol_drv.connect_volume() - - # Verify - # Remove mapping should not be called - self.assertEqual(0, mock_remove_p_maps.call_count) - self.assertEqual(1, mock_add_map.call_count) - self.assertEqual(0, self.ft_fx.patchers['update'].mock.call_count) - - # Check the fabric state remains mapped to instance - self.assertEqual(npiv.FS_INST_MAPPED, - self.vol_drv._get_fabric_state('A')) - - @mock.patch('pypowervm.tasks.vfc_mapper.add_map') - @mock.patch('pypowervm.tasks.vfc_mapper.remove_npiv_port_mappings') - def test_connect_volume_fc_unmap(self, mock_remove_p_maps, - mock_add_map): + def test_connect_volume(self, mock_add_map): # Mock self._basic_system_metadata(npiv.FS_UNMAPPED) @@ -168,13 +103,37 @@ class TestNPIVAdapter(test_vol.TestVolumeAdapter): return 'good' mock_add_map.side_effect = add_map - # TestCase when there is no mapping + # Test connect volume self.vol_drv.connect_volume() - # Remove mapping should not be called - self.assertEqual(0, mock_remove_p_maps.call_count) + # Verify that the appropriate connections were made. self.assertEqual(1, mock_add_map.call_count) self.assertEqual(1, self.ft_fx.patchers['update'].mock.call_count) + self.assertEqual(npiv.FS_INST_MAPPED, + self.vol_drv._get_fabric_state('A')) + + def test_connect_volume_not_valid(self): + """Validates that a connect will fail if in a bad state.""" + self.mock_inst_wrap.can_modify_io.return_value = False, 'Invalid I/O' + self.assertRaises(exc.VolumeAttachFailed, self.vol_drv.connect_volume) + + @mock.patch('pypowervm.tasks.vfc_mapper.add_map') + def test_connect_volume_inst_mapped(self, mock_add_map): + """Test if already connected to an instance, don't do anything""" + self._basic_system_metadata(npiv.FS_INST_MAPPED) + mock_add_map.return_value = None + + # Test subsequent connect volume calls when the fabric is mapped with + # inst partition + self.vol_drv.connect_volume() + + # Verify + self.assertEqual(1, mock_add_map.call_count) + self.assertEqual(0, self.ft_fx.patchers['update'].mock.call_count) + + # Check the fabric state remains mapped to instance + self.assertEqual(npiv.FS_INST_MAPPED, + self.vol_drv._get_fabric_state('A')) def _basic_system_metadata(self, fabric_state): meta_fb_key = self.vol_drv._sys_meta_fabric_key('A') @@ -300,19 +259,13 @@ class TestNPIVAdapter(test_vol.TestVolumeAdapter): inst.host = CONF.host self.assertFalse(self.vol_drv._is_migration_wwpn(npiv.FS_INST_MAPPED)) - @mock.patch('nova_powervm.virt.powervm.mgmt.get_mgmt_partition') @mock.patch('pypowervm.tasks.vfc_mapper.derive_npiv_map') - @mock.patch('pypowervm.tasks.vfc_mapper.add_npiv_port_mappings') - def test_configure_wwpns_for_migration( - self, mock_add_npiv_port, mock_derive, mock_mgmt_lpar): + def test_configure_wwpns_for_migration(self, mock_derive): # Mock out the fabric meta_fb_key = self.vol_drv._sys_meta_fabric_key('A') meta_fb_map = '21000024FF649104,AA,BB,21000024FF649105,CC,DD' self.vol_drv.instance.system_metadata = {meta_fb_key: meta_fb_map} - # Mock the mgmt partition - mock_mgmt_lpar.return_value = mock.Mock(uuid=0) - # Mock out what the derive returns expected_map = [('21000024FF649104', 'BB AA'), ('21000024FF649105', 'DD CC')] @@ -321,46 +274,40 @@ class TestNPIVAdapter(test_vol.TestVolumeAdapter): # Invoke resp_maps = self.vol_drv._configure_wwpns_for_migration('A') - # Make sure the add port was done properly - mock_add_npiv_port.assert_called_once_with( - self.vol_drv.adapter, self.vol_drv.host_uuid, 0, expected_map) - # Make sure the updated maps are returned expected = [('21000024FF649104', 'BB AA'), ('21000024FF649105', 'DD CC')] self.assertEqual(expected, resp_maps) + mock_derive.assert_called_with( + mock.ANY, ['21000024FF649104', '21000024FF649107'], + ['BB', 'AA', 'DD', 'CC']) - @mock.patch('nova_powervm.virt.powervm.mgmt.get_mgmt_partition') - @mock.patch('pypowervm.tasks.vfc_mapper.add_npiv_port_mappings') - def test_configure_wwpns_for_migration_existing( - self, mock_add_npiv_port, mock_mgmt_lpar): - """Validates nothing is done if WWPNs are already mapped.""" + @mock.patch('pypowervm.tasks.vfc_mapper.derive_npiv_map') + def test_configure_wwpns_for_migration_existing(self, mock_derive): + """Validates nothing is done if WWPNs are already flipped.""" # Mock out the fabric meta_fb_key = self.vol_drv._sys_meta_fabric_key('A') - meta_fb_map = '21000024FF649104,c05076079cff0fa0,c05076079cff0fa1' - self.vol_drv.instance.system_metadata = {meta_fb_key: meta_fb_map} - - # Mock the mgmt partition - mock_mgmt_lpar.return_value = mock.Mock(uuid=0) + meta_fb_map = '21000024FF649104,C05076079CFF0FA0,C05076079CFF0FA1' + meta_fb_st_key = self.vol_drv._sys_fabric_state_key('A') + meta_fb_st_val = npiv.FS_MIGRATING + self.vol_drv.instance.system_metadata = { + meta_fb_key: meta_fb_map, meta_fb_st_key: meta_fb_st_val} # Invoke resp_maps = self.vol_drv._configure_wwpns_for_migration('A') - # Make sure invocations were not made to do any adds - self.assertFalse(mock_add_npiv_port.called) + # Make sure that the order of the client WWPNs is not changed. expected = [('21000024FF649104', 'C05076079CFF0FA0 C05076079CFF0FA1')] self.assertEqual(expected, resp_maps) + self.assertFalse(mock_derive.called) - @mock.patch('nova_powervm.virt.powervm.mgmt.get_mgmt_partition') - @mock.patch('pypowervm.tasks.vfc_mapper.add_npiv_port_mappings') - def test_wwpns(self, mock_add_port, mock_mgmt_part): + @mock.patch('pypowervm.tasks.vfc_mapper.build_wwpn_pair') + @mock.patch('pypowervm.tasks.vfc_mapper.derive_npiv_map') + def test_wwpns(self, mock_derive, mock_build_pair): """Tests that new WWPNs get generated properly.""" # Mock Data - mock_add_port.return_value = [('21000024FF649104', 'AA BB'), - ('21000024FF649105', 'CC DD')] - mock_vios = mock.MagicMock() - mock_vios.uuid = '3443DB77-AED1-47ED-9AA5-3DB9C6CF7089' - mock_mgmt_part.return_value = mock_vios + mock_derive.return_value = [('21000024FF649104', 'AA BB'), + ('21000024FF649105', 'CC DD')] self.adpt.read.return_value = self.vios_feed_resp meta_key = self.vol_drv._sys_meta_fabric_key('A') @@ -373,15 +320,7 @@ class TestNPIVAdapter(test_vol.TestVolumeAdapter): 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) - - # Check when mgmt_uuid is None - mock_add_port.reset_mock() - mock_vios.uuid = None - self.vol_drv.wwpns() - self.assertEqual(0, mock_add_port.call_count) - self.assertEqual('mgmt_mapped', - self.vol_drv._get_fabric_state('A')) + self.assertEqual(1, mock_derive.call_count) @mock.patch('nova_powervm.virt.powervm.volume.npiv.NPIVVolumeAdapter.' '_get_fabric_state') @@ -473,21 +412,13 @@ class TestNPIVAdapter(test_vol.TestVolumeAdapter): self.assertEqual([1, 2], mig_data.get('npiv_fabric_slots_A')) self.assertEqual([3], mig_data.get('npiv_fabric_slots_B')) - @mock.patch('pypowervm.tasks.vfc_mapper.remove_maps') - @mock.patch('pypowervm.tasks.vfc_mapper.find_vios_for_vfc_wwpns') @mock.patch('pypowervm.tasks.vfc_mapper.' 'build_migration_mappings_for_fabric') - @mock.patch('nova_powervm.virt.powervm.mgmt.get_mgmt_partition') - @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_pre_live_migration_on_destination( - self, mock_fabric_names, mock_get_fabric_meta, mock_mgmt_lpar_id, - mock_build_mig_map, mock_find_vios_for_vfc_wwpns, mock_remove_map): + self, mock_fabric_names, mock_build_mig_map): mock_fabric_names.return_value = ['A', 'B'] - mock_get_fabric_meta.side_effect = [[], []] - mock_mgmt_lpar_id.return_value = mock.Mock(uuid='1') src_mig_data = {'npiv_fabric_slots_A': [1, 2], 'npiv_fabric_slots_B': [3]} @@ -506,21 +437,6 @@ class TestNPIVAdapter(test_vol.TestVolumeAdapter): self.assertEqual(set(['b', 'a']), set(dest_mig_data.get('vfc_lpm_mappings'))) - mock_find_vios_for_vfc_wwpns.return_value = None, None - dest_mig_data = {} - mock_fabric_names.return_value = ['A', 'B'] - mock_get_fabric_meta.side_effect = [ - [('11', 'AA BB'), ('22', 'CC DD')], - [('33', 'EE FF')]] - mock_build_mig_map.side_effect = [['a'], ['b']] - - # Execute the test - with self.assertLogs(npiv.__name__, level='WARNING'): - self.vol_drv.pre_live_migration_on_destination( - src_mig_data, dest_mig_data) - # remove_map should not be called since vios_w is None - self.assertEqual(0, mock_remove_map.call_count) - def test_set_fabric_meta(self): port_map = [('1', 'aa AA'), ('2', 'bb BB'), ('3', 'cc CC'), ('4', 'dd DD'), diff --git a/nova_powervm/virt/powervm/volume/npiv.py b/nova_powervm/virt/powervm/volume/npiv.py index 211e35d0..5d55207f 100644 --- a/nova_powervm/virt/powervm/volume/npiv.py +++ b/nova_powervm/virt/powervm/volume/npiv.py @@ -24,7 +24,6 @@ from pypowervm.tasks import vfc_mapper as pvm_vfcm from pypowervm.wrappers import virtual_io_server as pvm_vios from nova_powervm.virt import powervm -from nova_powervm.virt.powervm import mgmt from nova_powervm.virt.powervm.volume import driver as v_driver LOG = logging.getLogger(__name__) @@ -33,7 +32,7 @@ CONF = cfg.CONF WWPN_SYSTEM_METADATA_KEY = 'npiv_adpt_wwpns' FABRIC_STATE_METADATA_KEY = 'fabric_state' FS_UNMAPPED = 'unmapped' -FS_MGMT_MAPPED = 'mgmt_mapped' +FS_MIGRATING = 'migrating' FS_INST_MAPPED = 'inst_mapped' TASK_STATES_FOR_DISCONNECT = [task_states.DELETING, task_states.SPAWNING] @@ -135,48 +134,17 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter): should be added to this dictionary. """ vios_wraps = self.stg_ftsk.feed - mgmt_uuid = mgmt.get_mgmt_partition(self.adapter).uuid - # Each mapping should attempt to remove itself from the management - # partition. + # Need to first derive the port mappings that can be passed back + # to the source system for the live migration call. This tells + # the source system what 'vfc mappings' to pass in on the live + # migration command. for fabric in self._fabric_names(): - npiv_port_maps = self._get_fabric_meta(fabric) - - # Need to first derive the port mappings that can be passed back - # to the source system for the live migration call. This tells - # the source system what 'vfc mappings' to pass in on the live - # migration command. slots = src_mig_data['npiv_fabric_slots_%s' % fabric] fabric_mapping = pvm_vfcm.build_migration_mappings_for_fabric( vios_wraps, self._fabric_ports(fabric), slots) dest_mig_data['npiv_fabric_mapping_%s' % fabric] = fabric_mapping - # Next we need to remove the mappings off the mgmt partition. - for npiv_port_map in npiv_port_maps: - ls = [LOG.info, _LI("Removing mgmt NPIV mapping for instance " - "%(inst)s for fabric %(fabric)s."), - {'inst': self.instance.name, 'fabric': fabric}] - vios_w, vfc_map = pvm_vfcm.find_vios_for_vfc_wwpns( - vios_wraps, npiv_port_map[1].split()) - - if vios_w is not None: - # Add the subtask to remove the mapping from the management - # partition. - task_wrapper = self.stg_ftsk.wrapper_tasks[vios_w.uuid] - task_wrapper.add_functor_subtask( - pvm_vfcm.remove_maps, mgmt_uuid, - client_adpt=vfc_map.client_adapter, logspec=ls) - else: - LOG.warn(_LW("No storage connections found between the " - "Virtual I/O Servers and FC Fabric " - "%(fabric)s. The connection might be removed " - "already."), {'fabric': fabric}) - - # TODO(thorst) Find a better place for this execute. Works for now - # as the stg_ftsk is all local. Also won't do anything if there - # happen to be no fabric changes. - self.stg_ftsk.execute() - # Collate all of the individual fabric mappings into a single element. full_map = [] for key, value in dest_mig_data.items(): @@ -226,6 +194,7 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter): new_map = (p_wwpn, " ".join(c_wwpns)) new_port_maps.append(new_map) self._set_fabric_meta(fabric, new_port_maps) + self._set_fabric_state(fabric, FS_INST_MAPPED) # Store that this fabric is now flipped. mig_vol_stor[fabric_key] = True @@ -244,9 +213,9 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter): if (fc_state == FS_UNMAPPED and self.instance.task_state not in [task_states.DELETING, task_states.MIGRATING]): - LOG.info(_LI("Mapping instance %(inst)s to the mgmt partition for " - "fabric %(fabric)s because the VM does not yet have " - "a valid vFC device."), + LOG.info(_LI("Instance %(inst)s has not yet defined a WWPN on " + "fabric %(fabric)s. Appropriate WWPNs will be " + "generated."), {'inst': self.instance.name, 'fabric': fabric}) return True @@ -268,71 +237,61 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter): def _configure_wwpns_for_migration(self, fabric): """Configures the WWPNs for a migration. - During a NPIV migration, the WWPNs need to be flipped and attached to - the management VM. This is so that the peer WWPN is brought online. + During a NPIV migration, the WWPNs need to be flipped. This is because + the second WWPN is what will be logged in on the source system. So by + flipping them, we indicate that the 'second' wwpn is the new one to + log in. - The WWPNs will be removed from the management partition via the - pre_live_migration_on_destination method. The WWPNs invocation is - done prior to the migration, when the volume connector is gathered. + Another way to think of it is, this code should always return the + correct WWPNs for the system that the workload will be running on. + + This WWPNs invocation is done on the target server prior to the + actual migration call. It is used to build the volume connector. + Therefore this code simply flips the ports around. :param fabric: The fabric to configure. :return: An updated port mapping. """ - LOG.info(_LI("Mapping instance %(inst)s to the mgmt partition for " - "fabric %(fabric)s because the VM is migrating to " - "this host."), - {'inst': self.instance.name, 'fabric': fabric}) - - mgmt_uuid = mgmt.get_mgmt_partition(self.adapter).uuid + if self._get_fabric_state(fabric) == FS_MIGRATING: + # If the fabric is migrating, just return the existing port maps. + # They've already been flipped. + return self._get_fabric_meta(fabric) # When we migrate...flip the WWPNs around. This is so the other - # WWPN logs in on the target fabric. But we should only flip new - # WWPNs. There may already be some on the overall fabric...and if - # there are, we keep those 'as-is' - # - # TODO(thorst) pending API change should be able to indicate which - # wwpn is active. + # WWPN logs in on the target fabric. If this code is hit, the flip + # hasn't yet occurred (read as first volume on the instance). port_maps = self._get_fabric_meta(fabric) - existing_wwpns = [] - new_wwpns = [] - + client_wwpns = [] for port_map in port_maps: c_wwpns = port_map[1].split() + c_wwpns.reverse() + client_wwpns.extend(c_wwpns) - # Only add it as a 'new' mapping if it isn't on a VIOS already. If - # it is, then we know that it has already been serviced, perhaps - # by a previous volume. - vfc_map = pvm_vfcm.has_client_wwpns(self.stg_ftsk.feed, c_wwpns)[1] - if vfc_map is None: - c_wwpns.reverse() - new_wwpns.extend(c_wwpns) - else: - existing_wwpns.extend(c_wwpns) - - # Now derive the mapping to THESE VIOSes physical ports + # Now derive the mapping to the VIOS physical ports on this system + # (the destination) port_mappings = pvm_vfcm.derive_npiv_map( - self.stg_ftsk.feed, self._fabric_ports(fabric), - new_wwpns + existing_wwpns) + self.stg_ftsk.feed, self._fabric_ports(fabric), client_wwpns) - # Add the port maps to the mgmt partition - if len(new_wwpns) > 0: - pvm_vfcm.add_npiv_port_mappings( - self.adapter, self.host_uuid, mgmt_uuid, port_mappings) + # This won't actually get saved by the process. The instance save will + # only occur after the 'post migration'. But if there are multiple + # volumes, their WWPNs calls will subsequently see the data saved + # temporarily here, and therefore won't "double flip" the wwpns back + # to the original. + self._set_fabric_meta(fabric, port_mappings) + self._set_fabric_state(fabric, FS_MIGRATING) + + # Return the mappings return port_mappings def wwpns(self): """Builds the WWPNs of the adapters that will connect the ports.""" - vios_wraps, mgmt_uuid = None, None + vios_wraps = None resp_wwpns = [] - # If this is a new mapping altogether, the WWPNs need to be logged - # into the fabric so that Cinder can make use of them. This is a bit - # of a catch-22 because the LPAR doesn't exist yet. So a mapping will - # be created against the mgmt partition and then upon VM creation, the - # mapping will be moved over to the VM. - # - # If a mapping already exists, we can instead just pull the data off - # of the system metadata from the nova instance. + # If this is the first time to query the WWPNs for the instance, we + # need to generate a set of valid WWPNs. Loop through the configured + # FC fabrics and determine if these are new, part of a migration, or + # were already configured. for fabric in self._fabric_names(): fc_state = self._get_fabric_state(fabric) LOG.info(_LI("NPIV wwpns fabric state=%(st)s for " @@ -340,50 +299,40 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter): {'st': fc_state, 'inst': self.instance.name}) if self._is_initial_wwpn(fc_state, fabric): - # At this point we've determined that we need to do a mapping. - # So we go and obtain the mgmt uuid and the VIOS wrappers. - # We only do this for the first loop through so as to ensure - # that we do not keep invoking these expensive calls + # It is a new WWPN. Need to investigate the Virtual I/O + # Servers. We only do this for the first loop through so as + # to ensure that we do not keep invoking the expensive call # unnecessarily. - if mgmt_uuid is None: - mgmt_uuid = mgmt.get_mgmt_partition(self.adapter).uuid - - # The VIOS wrappers are also not set at this point. Seed - # them as well. Will get reused on subsequent loops. + if vios_wraps is None: + # The VIOS wrappers are not set at this point. Seed + # them. Will get reused on subsequent loops. vios_wraps = self.stg_ftsk.feed + # Get a set of WWPNs that are globally unique from the system. + v_wwpns = pvm_vfcm.build_wwpn_pair( + self.adapter, self.host_uuid, + pair_count=self._ports_per_fabric()) + # Derive the virtual to physical port mapping - port_maps = pvm_vfcm.derive_base_npiv_map( - vios_wraps, self._fabric_ports(fabric), - self._ports_per_fabric()) + port_maps = pvm_vfcm.derive_npiv_map( + vios_wraps, self._fabric_ports(fabric), v_wwpns) # Every loop through, we reverse the vios wrappers. This is # done so that if Fabric A only has 1 port, it goes on the # first VIOS. Then Fabric B would put its port on a different - # VIOS. As a form of multi pathing (so that your paths were - # not restricted to a single VIOS). + # VIOS. This servers as a form of multi pathing (so that your + # paths are not restricted to a single VIOS). vios_wraps.reverse() - # Check if the fabrics are unmapped then we need to map it - # temporarily with the management partition. - LOG.info(_LI("Adding NPIV Mapping with mgmt partition for " - "instance %s") % self.instance.name) - port_maps = pvm_vfcm.add_npiv_port_mappings( - self.adapter, self.host_uuid, mgmt_uuid, port_maps) - # Set the fabric meta (which indicates on the instance how # the fabric is mapped to the physical port) and the fabric # state. self._set_fabric_meta(fabric, port_maps) - self._set_fabric_state(fabric, FS_MGMT_MAPPED) + self._set_fabric_state(fabric, FS_UNMAPPED) elif self._is_migration_wwpn(fc_state): + # The migration process requires the 'second' wwpn from the + # fabric to be used. port_maps = self._configure_wwpns_for_migration(fabric) - - # This won't actually get saved by the process. The save will - # only occur after the 'post migration'. But if there are - # multiple volumes, their WWPNs calls will subsequently see - # the data saved temporarily here. - self._set_fabric_meta(fabric, port_maps) else: # This specific fabric had been previously set. Just pull # from the meta (as it is likely already mapped to the @@ -404,38 +353,11 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter): def _add_maps_for_fabric(self, fabric): """Adds the vFC storage mappings to the VM for a given fabric. - Will check if the Fabric is mapped to the management partition. If it - is, then it will remove the mappings and update the fabric state. This - is because, in order for the WWPNs to be on the fabric (for Cinder) - before the VM is online, the WWPNs get mapped to the management - partition. - - This method will remove from the management partition (if needed), and - then assign it to the instance itself. - :param fabric: The fabric to add the mappings to. """ npiv_port_maps = self._get_fabric_meta(fabric) vios_wraps = self.stg_ftsk.feed - # If currently mapped to the mgmt partition, remove the mappings so - # that they can be added to the client. - if self._get_fabric_state(fabric) == FS_MGMT_MAPPED: - mgmt_uuid = mgmt.get_mgmt_partition(self.adapter).uuid - - # Each port mapping should be removed from the VIOS. - for npiv_port_map in npiv_port_maps: - vios_w = pvm_vfcm.find_vios_for_port_map(vios_wraps, - npiv_port_map) - ls = [LOG.info, _LI("Removing NPIV mapping for mgmt partition " - "for instance %(inst)s on VIOS %(vios)s."), - {'inst': self.instance.name, 'vios': vios_w.name}] - - # Add the subtask to remove the map from the mgmt partition - self.stg_ftsk.wrapper_tasks[vios_w.uuid].add_functor_subtask( - pvm_vfcm.remove_maps, mgmt_uuid, port_map=npiv_port_map, - logspec=ls) - # This loop adds the maps from the appropriate VIOS to the client VM for npiv_port_map in npiv_port_maps: vios_w = pvm_vfcm.find_vios_for_port_map(vios_wraps, npiv_port_map) @@ -499,7 +421,6 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter): Possible Valid States: FS_UNMAPPED: Initial state unmapped. - FS_MGMT_MAPPED: Fabric is mapped with the management partition FS_INST_MAPPED: Fabric is mapped with the nova instance. """ meta_key = self._sys_fabric_state_key(fabric) @@ -515,7 +436,6 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter): Possible Valid States: FS_UNMAPPED: Initial state unmapped. - FS_MGMT_MAPPED: Fabric is mapped with the management partition FS_INST_MAPPED: Fabric is mapped with the nova instance. """ meta_key = self._sys_fabric_state_key(fabric)