diff --git a/nova_powervm/tests/virt/powervm/test_driver.py b/nova_powervm/tests/virt/powervm/test_driver.py index 0d5f774e..d340a54f 100644 --- a/nova_powervm/tests/virt/powervm/test_driver.py +++ b/nova_powervm/tests/virt/powervm/test_driver.py @@ -1,4 +1,4 @@ -# Copyright 2014, 2017 IBM Corp. +# Copyright 2014, 2018 IBM Corp. # # All Rights Reserved. # @@ -15,7 +15,7 @@ # under the License. from __future__ import absolute_import - +import collections import fixtures import logging import mock @@ -206,21 +206,21 @@ class TestPowerVMDriver(test.NoDBTestCase): self.assertTrue( self.drv.session.get_event_listener.return_value.shutdown.called) - @mock.patch('nova_powervm.virt.powervm.volume.get_iscsi_initiator', - autospec=True) - def test_get_volume_connector(self, mock_initiator): + @mock.patch('nova_powervm.virt.powervm.volume.iscsi.get_iscsi_initiators') + def test_get_volume_connector(self, mock_initiators): """Tests that a volume connector can be built.""" - mock_initiator.return_value = 'iscsi_initiator' + + initiators = [('1300C76F-9814-4A4D-B1F0-5B69352A7DEA', 'fake_iqn1'), + ('7DBBE705-E4C4-4458-8223-3EBE07015CA9', 'fake_iqn2')] + initiators = collections.OrderedDict(initiators) + + mock_initiators.return_value = initiators self.flags(volume_adapter='fibre_channel', group='powervm') vol_connector = self.drv.get_volume_connector(mock.Mock()) self.assertIsNotNone(vol_connector['wwpns']) self.assertIsNotNone(vol_connector['host']) - self.assertEqual('iscsi_initiator', vol_connector['initiator']) - - self.flags(volume_adapter='iscsi', group='powervm') - vol_connector = self.drv.get_volume_connector(mock.Mock()) - self.assertEqual('iscsi_initiator', vol_connector['initiator']) + self.assertEqual('fake_iqn1', vol_connector['initiator']) def test_setup_disk_adapter(self): # Ensure we can handle upper case option and we instantiate the class diff --git a/nova_powervm/tests/virt/powervm/volume/test_driver.py b/nova_powervm/tests/virt/powervm/volume/test_driver.py index b9ecc269..48295bba 100644 --- a/nova_powervm/tests/virt/powervm/volume/test_driver.py +++ b/nova_powervm/tests/virt/powervm/volume/test_driver.py @@ -1,4 +1,4 @@ -# Copyright 2015, 2017 IBM Corp. +# Copyright 2015, 2018 IBM Corp. # # All Rights Reserved. # @@ -15,7 +15,6 @@ # under the License. import mock -from pypowervm.wrappers import virtual_io_server as pvm_vios import six from nova import test @@ -54,34 +53,6 @@ class TestInitMethods(test.NoDBTestCase): 'gpfs': gpfs.GPFSVolumeAdapter, } - @mock.patch('pypowervm.tasks.hdisk.discover_iscsi_initiator') - @mock.patch('pypowervm.tasks.partition.get_mgmt_partition') - def test_get_iscsi_initiator(self, mock_mgmt, mock_iscsi_init): - # Set up mocks and clear out data that may have been set by other - # tests - mock_adpt = mock.Mock() - mock_mgmt.return_value = mock.Mock(spec=pvm_vios.VIOS) - mock_iscsi_init.return_value = 'test_initiator' - - self.assertEqual('test_initiator', - volume.get_iscsi_initiator(mock_adpt)) - - # Make sure it gets set properly in the backend - self.assertEqual('test_initiator', volume._ISCSI_INITIATOR) - self.assertTrue(volume._ISCSI_LOOKUP_COMPLETE) - mock_mgmt.assert_called_once_with(mock_adpt) - self.assertEqual(1, mock_mgmt.call_count) - - # Invoke again, make sure it doesn't call down to the mgmt part again - self.assertEqual('test_initiator', - volume.get_iscsi_initiator(mock_adpt)) - self.assertEqual(1, mock_mgmt.call_count) - - # Check if initiator returned does not have newline character - mock_iscsi_init.return_value = 'test_initiator\n' - self.assertEqual('test_initiator', - volume.get_iscsi_initiator(mock_adpt)) - def test_get_volume_class(self): for vol_type, class_type in six.iteritems(self.volume_drivers): self.assertEqual(class_type, volume.get_volume_class(vol_type)) diff --git a/nova_powervm/tests/virt/powervm/volume/test_iscsi.py b/nova_powervm/tests/virt/powervm/volume/test_iscsi.py index adac7c78..39b13871 100644 --- a/nova_powervm/tests/virt/powervm/volume/test_iscsi.py +++ b/nova_powervm/tests/virt/powervm/volume/test_iscsi.py @@ -392,3 +392,64 @@ class TestISCSIAdapter(test_vol.TestVolumeAdapter): retrieved_devname = self.vol_drv._get_devname() # Check key not found self.assertIsNone(retrieved_devname) + + @mock.patch('pypowervm.tasks.partition.get_active_vioses') + @mock.patch('pypowervm.tasks.hdisk.discover_iscsi_initiator') + def test_get_iscsi_initiators(self, mock_iscsi_init, mock_active_vioses): + # Set up mocks and clear out data that may have been set by other + # tests + iscsi._ISCSI_INITIATORS = dict() + mock_iscsi_init.return_value = 'test_initiator' + + vios_ids = ['1300C76F-9814-4A4D-B1F0-5B69352A7DEA', + '7DBBE705-E4C4-4458-8223-3EBE07015CA9'] + + mock_active_vioses.return_value = vios_ids + + expected_output = { + '1300C76F-9814-4A4D-B1F0-5B69352A7DEA': 'test_initiator', + '7DBBE705-E4C4-4458-8223-3EBE07015CA9': 'test_initiator' + } + + self.assertEqual(expected_output, + iscsi.get_iscsi_initiators(self.adpt, vios_ids)) + + # Make sure it gets set properly in the backend + self.assertEqual(expected_output, iscsi._ISCSI_INITIATORS) + self.assertEqual(mock_active_vioses.call_count, 0) + self.assertEqual(mock_iscsi_init.call_count, 2) + + # Invoke again, make sure it doesn't call down to the mgmt part again + mock_iscsi_init.reset_mock() + self.assertEqual(expected_output, + iscsi.get_iscsi_initiators(self.adpt, vios_ids)) + self.assertEqual(mock_active_vioses.call_count, 0) + self.assertEqual(mock_iscsi_init.call_count, 0) + + # Invoke iscsi.get_iscsi_initiators with vios_id=None + iscsi._ISCSI_INITIATORS = dict() + mock_iscsi_init.reset_mock() + self.assertEqual(expected_output, + iscsi.get_iscsi_initiators(self.adpt, None)) + self.assertEqual(expected_output, iscsi._ISCSI_INITIATORS) + self.assertEqual(mock_active_vioses.call_count, 1) + self.assertEqual(mock_iscsi_init.call_count, 2) + + # Invoke again with vios_id=None to ensure get_active_vioses, + # discover_iscsi_initiator is not called + mock_iscsi_init.reset_mock() + mock_active_vioses.reset_mock() + self.assertEqual(expected_output, + iscsi.get_iscsi_initiators(self.adpt, None)) + self.assertEqual(mock_active_vioses.call_count, 0) + self.assertEqual(mock_iscsi_init.call_count, 0) + + # Invoke iscsi.get_iscsi_initiators with discover_iscsi_initiator() + # raises ISCSIDiscoveryFailed exception + iscsi._ISCSI_INITIATORS = dict() + mock_iscsi_init.reset_mock() + mock_iscsi_init.side_effect = pvm_exc.ISCSIDiscoveryFailed( + vios_uuid='fake_vios_uid', status="fake_status") + self.assertEqual(dict(), + iscsi.get_iscsi_initiators(self.adpt, vios_ids)) + self.assertEqual(dict(), iscsi._ISCSI_INITIATORS) diff --git a/nova_powervm/virt/powervm/driver.py b/nova_powervm/virt/powervm/driver.py index eb36ed86..f0df5b5c 100644 --- a/nova_powervm/virt/powervm/driver.py +++ b/nova_powervm/virt/powervm/driver.py @@ -1,4 +1,4 @@ -# Copyright 2014, 2017 IBM Corp. +# Copyright 2014, 2018 IBM Corp. # # All Rights Reserved. # @@ -66,7 +66,7 @@ from nova_powervm.virt.powervm.tasks import storage as tf_stg from nova_powervm.virt.powervm.tasks import vm as tf_vm from nova_powervm.virt.powervm import vm from nova_powervm.virt.powervm import volume as vol_attach - +from nova_powervm.virt.powervm.volume import iscsi LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -1103,7 +1103,9 @@ class PowerVMDriver(driver.ComputeDriver): connector["wwpns"] = wwpn_list connector["multipath"] = CONF.powervm.volume_use_multipath connector['host'] = vol_attach.get_hostname_for_volume(instance) - connector['initiator'] = vol_attach.get_iscsi_initiator(self.adapter) + initiator_dict = iscsi.get_iscsi_initiators(self.adapter) + if initiator_dict: + connector['initiator'] = list(initiator_dict.values())[0] return connector def migrate_disk_and_power_off(self, context, instance, dest, diff --git a/nova_powervm/virt/powervm/volume/__init__.py b/nova_powervm/virt/powervm/volume/__init__.py index 5a8a370f..fa5bc251 100644 --- a/nova_powervm/virt/powervm/volume/__init__.py +++ b/nova_powervm/virt/powervm/volume/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2015, 2017 IBM Corp. +# Copyright 2015, 2018 IBM Corp. # # All Rights Reserved. # @@ -14,11 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_concurrency import lockutils -from pypowervm.tasks import hdisk -from pypowervm.tasks import partition -from pypowervm.wrappers import virtual_io_server as pvm_vios - # Defines the various volume connectors that can be used. from nova import exception @@ -85,28 +80,3 @@ def get_wwpns_for_volume_connector(adapter, host_uuid, instance): fc_vol_drv = build_volume_driver(adapter, host_uuid, instance, fake_fc_conn_info) return fc_vol_drv.wwpns() - - -_ISCSI_INITIATOR = None -_ISCSI_LOOKUP_COMPLETE = False - - -@lockutils.synchronized("PowerVM_iSCSI_Initiator_Lookup") -def get_iscsi_initiator(adapter): - """Gets the iSCSI initiator. - - This is looked up once at process start up. Stored in memory thereafter. - - :param adapter: The pypowervm adapter. - :return: The initiator name. If the NovaLink is not capable of supporting - iSCSI, None will be returned. - """ - global _ISCSI_INITIATOR, _ISCSI_LOOKUP_COMPLETE - if not _ISCSI_LOOKUP_COMPLETE: - mgmt_w = partition.get_mgmt_partition(adapter) - if isinstance(mgmt_w, pvm_vios.VIOS): - _ISCSI_INITIATOR = hdisk.discover_iscsi_initiator( - adapter, mgmt_w.uuid).strip() - - _ISCSI_LOOKUP_COMPLETE = True - return _ISCSI_INITIATOR diff --git a/nova_powervm/virt/powervm/volume/iscsi.py b/nova_powervm/virt/powervm/volume/iscsi.py index 67dfc2f4..5c96ea2e 100644 --- a/nova_powervm/virt/powervm/volume/iscsi.py +++ b/nova_powervm/virt/powervm/volume/iscsi.py @@ -13,6 +13,7 @@ # 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 collections import copy from oslo_concurrency import lockutils from oslo_log import log as logging @@ -26,6 +27,7 @@ from nova_powervm.virt.powervm.volume import volume from pypowervm import const as pvm_const from pypowervm import exceptions as pvm_exc from pypowervm.tasks import hdisk +from pypowervm.tasks import partition as pvm_partition from pypowervm.utils import transaction as tx from pypowervm.wrappers import virtual_io_server as pvm_vios @@ -37,6 +39,56 @@ import six LOG = logging.getLogger(__name__) CONF = cfg.CONF DEVNAME_KEY = 'target_devname' +_ISCSI_INITIATORS = collections.OrderedDict() + + +def get_iscsi_initiators(adapter, vios_ids=None): + """Gets the VIOS iSCSI initiators. + + For the first time invocation of this method after process start up, + it populates initiators data for VIOSes (if specified, otherwise it + gets active VIOSes from the host) and stores in memory for futher + lookup. + + :param adapter: The pypowervm adapter + :param vios_ids: List of VIOS ids to get the initiators. If not + specified, a list of active VIOSes for the + host is fetched (but only for the first time) + through the pypowervm adapter. + :return: A dict of the form + {: } + """ + + global _ISCSI_INITIATORS + + def discover_initiator(vios_id): + + # Get the VIOS id lock for initiator lookup + @lockutils.synchronized('inititator-lookup-' + vios_id) + def _discover_initiator(): + if (vios_id in _ISCSI_INITIATORS and + _ISCSI_INITIATORS[vios_id]): + return + else: + try: + initiator = hdisk.discover_iscsi_initiator( + adapter, vios_id) + _ISCSI_INITIATORS[vios_id] = initiator + except pvm_exc.ISCSIDiscoveryFailed as e: + # TODO(chhagarw): handle differently based on + # error codes + LOG.error(e) + + _discover_initiator() + + if vios_ids is None and not _ISCSI_INITIATORS: + vios_ids = pvm_partition.get_active_vioses(adapter) + + for vios_id in vios_ids or []: + discover_initiator(vios_id) + + LOG.debug("iSCSI initiator info: %s" % _ISCSI_INITIATORS) + return _ISCSI_INITIATORS class IscsiVolumeAdapter(volume.VscsiVolumeAdapter,