From 4cd64e28aa24e91534800288ab1c60407b4a722f Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Tue, 15 Dec 2015 13:47:40 +0100 Subject: [PATCH] Delete the Linux-IO target before setting up local boot Otherwise it fails with "device already mounted". Change-Id: If959ba5a10e97f84be2f7498bed7589d13fa30c2 Closes-Bug: #1526304 --- ironic_python_agent/extensions/image.py | 2 + ironic_python_agent/extensions/iscsi.py | 39 ++++++++++++ .../tests/unit/extensions/test_image.py | 13 ++-- .../tests/unit/extensions/test_iscsi.py | 63 +++++++++++++++++++ 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py index 6e9abc271..a4edfb093 100644 --- a/ironic_python_agent/extensions/image.py +++ b/ironic_python_agent/extensions/image.py @@ -25,6 +25,7 @@ from oslo_log import log from ironic_python_agent import errors from ironic_python_agent.extensions import base +from ironic_python_agent.extensions import iscsi from ironic_python_agent import hardware from ironic_python_agent import utils @@ -194,6 +195,7 @@ class ImageExtension(base.BaseAgentExtension): """ device = hardware.dispatch_to_managers('get_os_install_device') + iscsi.clean_up(device) _install_grub2(device, root_uuid=root_uuid, efi_system_part_uuid=efi_system_part_uuid) diff --git a/ironic_python_agent/extensions/iscsi.py b/ironic_python_agent/extensions/iscsi.py index 8d38c6b24..2e38cd1bf 100644 --- a/ironic_python_agent/extensions/iscsi.py +++ b/ironic_python_agent/extensions/iscsi.py @@ -95,6 +95,45 @@ def _start_lio(iqn, device): raise errors.ISCSIError(msg) +def clean_up(device): + """Clean up iSCSI for a given device.""" + try: + rts_root = rtslib_fb.RTSRoot() + except (EnvironmentError, rtslib_fb.RTSLibError) as exc: + LOG.info('Linux-IO is not available, not cleaning up. Error: %s.', exc) + return + + storage = None + for x in rts_root.storage_objects: + if x.udev_path == device: + storage = x + break + + if storage is None: + LOG.info('Device %(dev)s not found in the current iSCSI mounts ' + '%(mounts)s.', + {'dev': device, + 'mounts': [x.udev_path for x in rts_root.storage_objects]}) + return + else: + LOG.info('Deleting iSCSI target %(target)s for device %(dev)s.', + {'target': storage.name, 'dev': device}) + + try: + for x in rts_root.targets: + if x.wwn == storage.name: + x.delete() + break + + storage.delete() + except rtslib_fb.utils.RTSLibError as exc: + msg = ('Failed to delete iSCSI target %(target)s for device %(dev)s: ' + '%(error)s') % {'target': storage.name, + 'dev': device, + 'error': exc} + raise errors.ISCSIError(msg) + + class ISCSIExtension(base.BaseAgentExtension): @base.sync_command('start_iscsi_target') def start_iscsi_target(self, iqn=None): diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py index 20a64e4fa..78836443c 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -25,6 +25,7 @@ from oslotest import base as test_base from ironic_python_agent import errors from ironic_python_agent.extensions import image +from ironic_python_agent.extensions import iscsi from ironic_python_agent import hardware from ironic_python_agent import utils @@ -45,19 +46,22 @@ class TestImageExtension(test_base.BaseTestCase): self.fake_efi_system_part_uuid = '45AB-2312' self.fake_dir = '/tmp/fake-dir' + @mock.patch.object(iscsi, 'clean_up') @mock.patch.object(image, '_install_grub2') - def test_install_bootloader_bios(self, mock_grub2, mock_execute, - mock_dispatch): + def test_install_bootloader_bios(self, mock_grub2, mock_iscsi_clean, + mock_execute, mock_dispatch): mock_dispatch.return_value = self.fake_dev self.agent_extension.install_bootloader(root_uuid=self.fake_root_uuid) mock_dispatch.assert_called_once_with('get_os_install_device') mock_grub2.assert_called_once_with( self.fake_dev, root_uuid=self.fake_root_uuid, efi_system_part_uuid=None) + mock_iscsi_clean.assert_called_once_with(self.fake_dev) + @mock.patch.object(iscsi, 'clean_up') @mock.patch.object(image, '_install_grub2') - def test_install_bootloader_uefi(self, mock_grub2, mock_execute, - mock_dispatch): + def test_install_bootloader_uefi(self, mock_grub2, mock_iscsi_clean, + mock_execute, mock_dispatch): mock_dispatch.return_value = self.fake_dev self.agent_extension.install_bootloader( root_uuid=self.fake_root_uuid, @@ -67,6 +71,7 @@ class TestImageExtension(test_base.BaseTestCase): self.fake_dev, root_uuid=self.fake_root_uuid, efi_system_part_uuid=self.fake_efi_system_part_uuid) + mock_iscsi_clean.assert_called_once_with(self.fake_dev) @mock.patch.object(os, 'environ') @mock.patch.object(image, '_get_partition') diff --git a/ironic_python_agent/tests/unit/extensions/test_iscsi.py b/ironic_python_agent/tests/unit/extensions/test_iscsi.py index eaae2c50b..05e2d8cdf 100644 --- a/ironic_python_agent/tests/unit/extensions/test_iscsi.py +++ b/ironic_python_agent/tests/unit/extensions/test_iscsi.py @@ -155,3 +155,66 @@ class TestISCSIExtensionLIO(test_base.BaseTestCase): lun=1) mock_rtslib.NetworkPortal.assert_called_once_with( mock_rtslib.TPG.return_value, '0.0.0.0') + + +@mock.patch.object(iscsi.rtslib_fb, 'RTSRoot') +class TestISCSIExtensionCleanUp(test_base.BaseTestCase): + + def setUp(self): + super(TestISCSIExtensionCleanUp, self).setUp() + self.agent_extension = iscsi.ISCSIExtension() + self.fake_dev = '/dev/fake' + self.fake_iqn = 'iqn-fake' + + def test_lio_not_available(self, mock_rtslib): + mock_rtslib.side_effect = IOError() + iscsi.clean_up(self.fake_dev) + + def test_device_not_found(self, mock_rtslib): + mock_rtslib.return_value.storage_objects = [] + iscsi.clean_up(self.fake_dev) + + def test_ok(self, mock_rtslib): + mock_rtslib.return_value.storage_objects = [ + mock.Mock(udev_path='wrong path'), + mock.Mock(udev_path=self.fake_dev), + mock.Mock(udev_path='wrong path'), + ] + # mocks don't play well with name attribute + for i, fake_storage in enumerate( + mock_rtslib.return_value.storage_objects): + fake_storage.name = 'iqn%d' % i + + mock_rtslib.return_value.targets = [ + mock.Mock(wwn='iqn0'), + mock.Mock(wwn='iqn1'), + ] + + iscsi.clean_up(self.fake_dev) + + for fake_storage in mock_rtslib.return_value.storage_objects: + self.assertEqual(fake_storage.udev_path == self.fake_dev, + fake_storage.delete.called) + for fake_target in mock_rtslib.return_value.targets: + self.assertEqual(fake_target.wwn == 'iqn1', + fake_target.delete.called) + + def test_delete_fails(self, mock_rtslib): + mock_rtslib.return_value.storage_objects = [ + mock.Mock(udev_path='wrong path'), + mock.Mock(udev_path=self.fake_dev), + mock.Mock(udev_path='wrong path'), + ] + # mocks don't play well with name attribute + for i, fake_storage in enumerate( + mock_rtslib.return_value.storage_objects): + fake_storage.name = 'iqn%d' % i + + mock_rtslib.return_value.targets = [ + mock.Mock(wwn='iqn0'), + mock.Mock(wwn='iqn1'), + ] + mock_rtslib.return_value.targets[1].delete.side_effect = ( + _ORIG_UTILS.RTSLibError()) + + self.assertRaises(errors.ISCSIError, iscsi.clean_up, self.fake_dev)