From be3882162e432b292af9e787661661a05fa7d11a Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Mon, 10 May 2021 12:43:17 +0200 Subject: [PATCH] Remove the iscsi extension Change-Id: I2f0e581575112d6c7ba0d211661cab3e0b6caca6 --- ironic_python_agent/agent.py | 1 - ironic_python_agent/errors.py | 19 - ironic_python_agent/extensions/image.py | 6 +- ironic_python_agent/extensions/iscsi.py | 221 ---------- .../tests/unit/extensions/test_image.py | 71 +--- .../tests/unit/extensions/test_iscsi.py | 388 ------------------ .../notes/no-iscsi-fd21808edbea5ac2.yaml | 5 + requirements.txt | 1 - setup.cfg | 1 - 9 files changed, 14 insertions(+), 699 deletions(-) delete mode 100644 ironic_python_agent/extensions/iscsi.py delete mode 100644 ironic_python_agent/tests/unit/extensions/test_iscsi.py create mode 100644 releasenotes/notes/no-iscsi-fd21808edbea5ac2.yaml diff --git a/ironic_python_agent/agent.py b/ironic_python_agent/agent.py index 660619eb8..8e36af2c0 100644 --- a/ironic_python_agent/agent.py +++ b/ironic_python_agent/agent.py @@ -187,7 +187,6 @@ class IronicPythonAgent(base.ExecuteCommandMixin): # in the event of long running ramdisks where the conductor # got upgraded somewhere along the way. self.agent_token_required = cfg.CONF.agent_token_required - self.iscsi_started = False self.generated_cert = None def get_status(self): diff --git a/ironic_python_agent/errors.py b/ironic_python_agent/errors.py index 0f7a185ca..1f97c0e2e 100644 --- a/ironic_python_agent/errors.py +++ b/ironic_python_agent/errors.py @@ -312,31 +312,12 @@ class DeploymentError(RESTError): super(DeploymentError, self).__init__(details) -class ISCSIError(RESTError): - """Error raised when an image cannot be written to a device.""" - - message = 'Error starting iSCSI target' - - def __init__(self, error_msg): - details = 'Error starting iSCSI target: {}'.format(error_msg) - super(ISCSIError, self).__init__(details) - - class IncompatibleNumaFormatError(RESTError): """Error raised when unexpected format data in NUMA node.""" message = 'Error in NUMA node data format' -class ISCSICommandError(ISCSIError): - """Error executing TGT command.""" - - def __init__(self, error_msg, exit_code, stdout, stderr): - details = ('{}. Failed with exit code {}. stdout: {}. stderr: {}') - details = details.format(error_msg, exit_code, stdout, stderr) - super(ISCSICommandError, self).__init__(details) - - class DeviceNotFound(NotFound): """Error raised when the device to deploy the image onto is not found.""" diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py index c482b69ca..cafbd9bf0 100644 --- a/ironic_python_agent/extensions/image.py +++ b/ironic_python_agent/extensions/image.py @@ -27,7 +27,6 @@ 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 raid_utils from ironic_python_agent import utils @@ -289,8 +288,7 @@ def _manage_uefi(device, efi_system_part_uuid=None): efi_mounted = False try: - # Force UEFI to rescan the device. Required if the deployment - # was over iscsi. + # Force UEFI to rescan the device. _rescan_device(device) local_path = tempfile.mkdtemp() @@ -1009,8 +1007,6 @@ class ImageExtension(base.BaseAgentExtension): """ device = hardware.dispatch_to_managers('get_os_install_device') - if self.agent.iscsi_started: - iscsi.clean_up(device) # Always allow the API client to be the final word on if this is # overridden or not. diff --git a/ironic_python_agent/extensions/iscsi.py b/ironic_python_agent/extensions/iscsi.py deleted file mode 100644 index c519ab32c..000000000 --- a/ironic_python_agent/extensions/iscsi.py +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright 2015 Red Hat, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -from ironic_lib import disk_utils -from oslo_concurrency import processutils -from oslo_log import log -from oslo_utils import uuidutils -try: - import rtslib_fb -except ImportError: - import rtslib as rtslib_fb - -from ironic_python_agent import errors -from ironic_python_agent.extensions import base -from ironic_python_agent import hardware -from ironic_python_agent import netutils -from ironic_python_agent import utils - -LOG = log.getLogger(__name__) -DEFAULT_ISCSI_PORTAL_PORT = 3260 - - -def _execute(cmd, error_msg, **kwargs): - try: - stdout, stderr = utils.execute(*cmd, **kwargs) - except processutils.ProcessExecutionError as e: - LOG.error(error_msg) - raise errors.ISCSICommandError(error_msg, e.exit_code, - e.stdout, e.stderr) - except OSError as e: - LOG.error("Error: %(error)s: OS Error: %(os_error)s", - {'error': error_msg, 'os_error': e}) - raise errors.ISCSICommandError(e, e.errno, None, None) - - -def _wait_for_tgtd(attempts=10): - """Wait for the ISCSI daemon to start.""" - # here, iscsi daemon is considered not running in case - # tgtadm is not able to talk to tgtd to show iscsi targets - cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op', 'show'] - _execute(cmd, "ISCSI daemon didn't initialize", attempts=attempts) - - -def _start_tgtd(iqn, portal_port, device): - """Start a ISCSI target for the device.""" - # Start ISCSI Target daemon - _execute(['tgtd'], "Unable to start the ISCSI daemon") - - _wait_for_tgtd() - - # tgt service will create default portal on default port 3260. - # so no need to create again if input portal_port == 3260. - if portal_port != DEFAULT_ISCSI_PORTAL_PORT: - cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'portal', '--op', - 'new', '--param', 'portal=0.0.0.0:' + str(portal_port)] - _execute(cmd, "Error when adding a new portal with portal_port %d" - % portal_port) - - cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op', - 'new', '--tid', '1', '--targetname', iqn] - _execute(cmd, "Error when adding a new target for iqn %s" % iqn) - - cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'logicalunit', '--op', - 'new', '--tid', '1', '--lun', '1', '--backing-store', device] - _execute(cmd, "Error when adding a new logical unit for iqn %s" % iqn) - - cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op', - 'bind', '--tid', '1', '--initiator-address', 'ALL'] - _execute(cmd, "Error when enabling the target to accept the specific " - "initiators for iqn %s" % iqn) - - -def _start_lio(iqn, portal_port, device): - try: - storage = rtslib_fb.BlockStorageObject(name=iqn, dev=device) - target = rtslib_fb.Target(rtslib_fb.FabricModule('iscsi'), iqn, - mode='create') - tpg = rtslib_fb.TPG(target, mode='create') - # disable all authentication - tpg.set_attribute('authentication', '0') - tpg.set_attribute('demo_mode_write_protect', '0') - tpg.set_attribute('generate_node_acls', '1') - # lun=1 is hardcoded in ironic - rtslib_fb.LUN(tpg, storage_object=storage, lun=1) - tpg.enable = 1 - except rtslib_fb.utils.RTSLibError as exc: - msg = 'Failed to create a target: {}'.format(exc) - raise errors.ISCSIError(msg) - - try: - # bind to the default port on all interfaces - listen_ip = netutils.wrap_ipv6(netutils.get_wildcard_address()) - rtslib_fb.NetworkPortal(tpg, listen_ip, portal_port) - except rtslib_fb.utils.RTSLibError as exc: - msg = 'Failed to publish a target: {}'.format(exc) - raise errors.ISCSIError(msg) - - -def clean_up(device): - """Clean up iSCSI for a given device.""" - try: - rts_root = rtslib_fb.RTSRoot() - except (OSError, EnvironmentError, rtslib_fb.RTSLibError) as exc: - try: - LOG.info('Linux-IO is not available, attemting to stop tgtd ' - 'mapping. Error: %s.', exc) - cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op', - 'unbind', '--tid', '1', '--initiator-address', 'ALL'] - _execute(cmd, "Error when cleaning up iscsi binds.") - except errors.ISCSICommandError: - # This command may fail if the target was already torn down - # and that is okay, we just want to ensure it has been torn - # down so there should be no disk locks persisting. - pass - cmd = ['sync'] - _execute(cmd, "Error flushing buffers to disk.") - try: - cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op', - 'delete', '--tid', '1'] - _execute(cmd, "Error deleting the iscsi target configuration.") - except errors.ISCSICommandError: - # This command should remove the target from being offered. - # It is just proper clean-up, and often previously the IPA - # side, or "target" was never really torn down in many cases. - pass - 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, wipe_disk_metadata=False, - portal_port=None): - """Expose the disk as an ISCSI target. - - :param iqn: IQN for iSCSI target. If None, a new IQN is generated. - :param wipe_disk_metadata: if the disk metadata should be wiped out - before the disk is exposed. - :param portal_port: customized port for iSCSI port, can be None. - :returns: a dict that provides IQN of iSCSI target. - """ - # If iqn is not given, generate one - if iqn is None: - iqn = 'iqn.2008-10.org.openstack:%s' % uuidutils.generate_uuid() - - device = hardware.dispatch_to_managers('get_os_install_device', - permit_refresh=True) - - if wipe_disk_metadata: - disk_utils.destroy_disk_metadata( - device, - self.agent.get_node_uuid()) - - LOG.debug("Starting ISCSI target with iqn %(iqn)s on device " - "%(device)s", {'iqn': iqn, 'device': device}) - - try: - rts_root = rtslib_fb.RTSRoot() - except (EnvironmentError, rtslib_fb.RTSLibError) as exc: - LOG.warning('Linux-IO is not available, falling back to TGT. ' - 'Error: %s.', exc) - rts_root = None - - if portal_port is None: - portal_port = DEFAULT_ISCSI_PORTAL_PORT - - if rts_root is None: - _start_tgtd(iqn, portal_port, device) - else: - _start_lio(iqn, portal_port, device) - LOG.debug('Linux-IO configuration: %s', rts_root.dump()) - # Mark iscsi as previously started - self.agent.iscsi_started = True - LOG.info('Created iSCSI target with iqn %(iqn)s, portal port %(port)d,' - ' on device %(dev)s using %(method)s', - {'iqn': iqn, 'port': portal_port, 'dev': device, - 'method': 'tgtd' if rts_root is None else 'linux-io'}) - - return {"iscsi_target_iqn": iqn} diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py index d7093b6b2..61ff84369 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -23,7 +23,6 @@ from oslo_concurrency import processutils 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.tests.unit import base from ironic_python_agent import utils @@ -46,12 +45,9 @@ class TestImageExtension(base.IronicAgentTest): self.fake_efi_system_part_uuid = '45AB-2312' self.fake_prep_boot_part_uuid = '76937797-3253-8843-999999999999' self.fake_dir = '/tmp/fake-dir' - self.agent_extension.agent = mock.Mock() - self.agent_extension.agent.iscsi_started = True - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) - def test__install_bootloader_bios(self, mock_grub2, mock_iscsi_clean, + def test__install_bootloader_bios(self, mock_grub2, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='bios') @@ -66,13 +62,10 @@ class TestImageExtension(base.IronicAgentTest): efi_system_part_uuid=None, prep_boot_part_uuid=None, target_boot_mode='bios' ) - mock_iscsi_clean.assert_called_once_with(self.fake_dev) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_manage_uefi', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) def test__install_bootloader_uefi(self, mock_grub2, mock_uefi, - mock_iscsi_clean, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='uefi') @@ -93,14 +86,11 @@ class TestImageExtension(base.IronicAgentTest): prep_boot_part_uuid=None, target_boot_mode='uefi' ) - mock_iscsi_clean.assert_called_once_with(self.fake_dev) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_manage_uefi', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) def test__install_bootloader_uefi_ignores_manage_failure( self, mock_grub2, mock_uefi, - mock_iscsi_clean, mock_execute, mock_dispatch): self.config(ignore_bootloader_failure=True) mock_uefi.side_effect = OSError('meow') @@ -123,14 +113,11 @@ class TestImageExtension(base.IronicAgentTest): prep_boot_part_uuid=None, target_boot_mode='uefi' ) - mock_iscsi_clean.assert_called_once_with(self.fake_dev) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_manage_uefi', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) def test__install_bootloader_uefi_ignores_grub_failure( self, mock_grub2, mock_uefi, - mock_iscsi_clean, mock_execute, mock_dispatch): self.config(ignore_bootloader_failure=True) mock_grub2.side_effect = OSError('meow') @@ -153,14 +140,11 @@ class TestImageExtension(base.IronicAgentTest): prep_boot_part_uuid=None, target_boot_mode='uefi' ) - mock_iscsi_clean.assert_called_once_with(self.fake_dev) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_manage_uefi', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) def test__install_bootloader_uefi_ignores_grub_failure_api_override( self, mock_grub2, mock_uefi, - mock_iscsi_clean, mock_execute, mock_dispatch): self.config(ignore_bootloader_failure=False) mock_grub2.side_effect = OSError('meow') @@ -183,14 +167,11 @@ class TestImageExtension(base.IronicAgentTest): prep_boot_part_uuid=None, target_boot_mode='uefi' ) - mock_iscsi_clean.assert_called_once_with(self.fake_dev) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_manage_uefi', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) def test__install_bootloader_uefi_grub_failure_api_override( self, mock_grub2, mock_uefi, - mock_iscsi_clean, mock_execute, mock_dispatch): self.config(ignore_bootloader_failure=True) mock_grub2.side_effect = OSError('meow') @@ -214,11 +195,9 @@ class TestImageExtension(base.IronicAgentTest): prep_boot_part_uuid=None, target_boot_mode='uefi' ) - mock_iscsi_clean.assert_called_once_with(self.fake_dev) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) - def test__install_bootloader_no_root(self, mock_grub2, mock_iscsi_clean, + def test__install_bootloader_no_root(self, mock_grub2, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='bios') @@ -229,18 +208,16 @@ class TestImageExtension(base.IronicAgentTest): mock_dispatch.assert_any_call('get_boot_info') self.assertEqual(2, mock_dispatch.call_count) self.assertFalse(mock_grub2.called) - mock_iscsi_clean.assert_called_once_with(self.fake_dev) @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_get_efi_bootloaders', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(utils, 'get_efi_part_on_device', autospec=False) @mock.patch.object(os, 'makedirs', autospec=True) def test__uefi_bootloader_given_partition( self, mkdir_mock, mock_utils_efi_part, mock_partition, - mock_efi_bl, mock_iscsi_clean, mock_execute, mock_dispatch): + mock_efi_bl, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='uefi') ] @@ -282,14 +259,13 @@ class TestImageExtension(base.IronicAgentTest): @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_get_efi_bootloaders', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(utils, 'get_efi_part_on_device', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) def test__uefi_bootloader_find_partition( self, mkdir_mock, mock_utils_efi_part, mock_partition, - mock_efi_bl, mock_iscsi_clean, mock_execute, mock_dispatch): + mock_efi_bl, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='uefi') ] @@ -330,14 +306,13 @@ class TestImageExtension(base.IronicAgentTest): @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_get_efi_bootloaders', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(utils, 'get_efi_part_on_device', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) def test__uefi_bootloader_with_entry_removal( self, mkdir_mock, mock_utils_efi_part, mock_partition, - mock_efi_bl, mock_iscsi_clean, mock_execute, mock_dispatch): + mock_efi_bl, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='uefi') ] @@ -385,14 +360,13 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_get_efi_bootloaders', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(utils, 'get_efi_part_on_device', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) def test__add_multi_bootloaders( self, mkdir_mock, mock_utils_efi_part, mock_partition, - mock_efi_bl, mock_iscsi_clean, mock_execute, mock_dispatch): + mock_efi_bl, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='uefi') ] @@ -438,9 +412,8 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n mock_utils_efi_part.assert_called_once_with(self.fake_dev) self.assertEqual(9, mock_execute.call_count) - @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) - def test__install_bootloader_prep(self, mock_grub2, mock_iscsi_clean, + def test__install_bootloader_prep(self, mock_grub2, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='bios') @@ -459,38 +432,10 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n prep_boot_part_uuid=self.fake_prep_boot_part_uuid, target_boot_mode='bios' ) - mock_iscsi_clean.assert_called_once_with(self.fake_dev) - - @mock.patch.object(iscsi, 'clean_up', autospec=True) - @mock.patch.object(image, '_install_grub2', autospec=True) - def test__install_bootloader_prep_no_iscsi( - self, mock_grub2, mock_iscsi_clean, - mock_execute, mock_dispatch): - self.agent_extension.agent.iscsi_started = False - mock_dispatch.side_effect = [ - self.fake_dev, hardware.BootInfo(current_boot_mode='bios') - ] - self.agent_extension.install_bootloader( - root_uuid=self.fake_root_uuid, - efi_system_part_uuid=None, - prep_boot_part_uuid=self.fake_prep_boot_part_uuid).join() - mock_dispatch.assert_any_call('get_os_install_device') - mock_dispatch.assert_any_call('get_boot_info') - self.assertEqual(2, mock_dispatch.call_count) - mock_grub2.assert_called_once_with( - self.fake_dev, - root_uuid=self.fake_root_uuid, - efi_system_part_uuid=None, - prep_boot_part_uuid=self.fake_prep_boot_part_uuid, - target_boot_mode='bios' - ) - mock_iscsi_clean.assert_not_called() @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) - @mock.patch.object(iscsi, 'clean_up', autospec=True) - def test_install_bootloader_failure(self, mock_iscsi_clean, mock_execute, - mock_dispatch): + def test_install_bootloader_failure(self, mock_execute, mock_dispatch): mock_dispatch.side_effect = [ self.fake_dev, hardware.BootInfo(current_boot_mode='uefi') ] diff --git a/ironic_python_agent/tests/unit/extensions/test_iscsi.py b/ironic_python_agent/tests/unit/extensions/test_iscsi.py deleted file mode 100644 index a4678a98e..000000000 --- a/ironic_python_agent/tests/unit/extensions/test_iscsi.py +++ /dev/null @@ -1,388 +0,0 @@ -# Copyright 2015 Red Hat, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from unittest import mock - -from ironic_lib import disk_utils -from oslo_concurrency import processutils - -from ironic_python_agent import errors -from ironic_python_agent.extensions import iscsi -from ironic_python_agent import hardware -from ironic_python_agent.tests.unit import base -from ironic_python_agent import utils - - -class FakeAgent(object): - - iscsi_started = False - - def get_node_uuid(self): - return 'my_node_uuid' - - -@mock.patch.object(disk_utils, 'destroy_disk_metadata', autospec=True) -@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) -@mock.patch.object(utils, 'execute', autospec=True) -@mock.patch.object(iscsi.rtslib_fb, 'RTSRoot', - mock.Mock(side_effect=iscsi.rtslib_fb.RTSLibError())) -class TestISCSIExtensionTgt(base.IronicAgentTest): - - def setUp(self): - super(TestISCSIExtensionTgt, self).setUp() - self.agent_extension = iscsi.ISCSIExtension(FakeAgent()) - self.fake_dev = '/dev/fake' - self.fake_iqn = 'iqn-fake' - - def test_start_iscsi_target(self, mock_execute, - mock_dispatch, - mock_destroy): - mock_dispatch.return_value = self.fake_dev - mock_execute.return_value = ('', '') - self.assertFalse(self.agent_extension.agent.iscsi_started) - - result = self.agent_extension.start_iscsi_target(iqn=self.fake_iqn) - - self.assertTrue(self.agent_extension.agent.iscsi_started) - expected = [mock.call('tgtd'), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'target', '--op', 'show', attempts=10), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'target', '--op', 'new', '--tid', '1', - '--targetname', self.fake_iqn), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'logicalunit', '--op', 'new', '--tid', '1', - '--lun', '1', '--backing-store', self.fake_dev), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', 'target', - '--op', 'bind', '--tid', '1', - '--initiator-address', 'ALL')] - mock_execute.assert_has_calls(expected) - mock_dispatch.assert_called_once_with('get_os_install_device', - permit_refresh=True) - self.assertEqual({'iscsi_target_iqn': self.fake_iqn}, - result.command_result) - self.assertFalse(mock_destroy.called) - - def test_start_iscsi_target_with_special_port(self, mock_execute, - mock_dispatch, - mock_destroy): - mock_dispatch.return_value = self.fake_dev - mock_execute.return_value = ('', '') - result = self.agent_extension.start_iscsi_target(iqn=self.fake_iqn, - portal_port=3268) - - expected = [mock.call('tgtd'), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'target', '--op', 'show', attempts=10), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'portal', '--op', 'new', '--param', - 'portal=0.0.0.0:3268'), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'target', '--op', 'new', '--tid', '1', - '--targetname', self.fake_iqn), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'logicalunit', '--op', 'new', '--tid', '1', - '--lun', '1', '--backing-store', self.fake_dev), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', 'target', - '--op', 'bind', '--tid', '1', - '--initiator-address', 'ALL')] - mock_execute.assert_has_calls(expected) - mock_dispatch.assert_called_once_with('get_os_install_device', - permit_refresh=True) - self.assertEqual({'iscsi_target_iqn': self.fake_iqn}, - result.command_result) - - def test_start_iscsi_target_fail_wait_daemon(self, mock_execute, - mock_dispatch, - mock_destroy): - mock_dispatch.return_value = self.fake_dev - # side effects here: - # - execute tgtd: stdout=='', stderr=='' - # - induce tgtadm failure while in _wait_for_scsi_daemon - mock_execute.side_effect = [('', ''), - processutils.ProcessExecutionError('blah')] - self.assertRaises(errors.ISCSIError, - self.agent_extension.start_iscsi_target, - iqn=self.fake_iqn) - expected = [mock.call('tgtd'), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', 'target', - '--op', 'show', attempts=10)] - - mock_execute.assert_has_calls(expected) - mock_dispatch.assert_called_once_with('get_os_install_device', - permit_refresh=True) - self.assertFalse(mock_destroy.called) - - @mock.patch.object(iscsi, '_wait_for_tgtd', autospec=True) - def test_start_iscsi_target_fail_command(self, mock_wait_iscsi, - mock_execute, mock_dispatch, - mock_destroy): - mock_dispatch.return_value = self.fake_dev - mock_execute.side_effect = [('', ''), ('', ''), - processutils.ProcessExecutionError('blah')] - self.assertRaises(errors.ISCSIError, - self.agent_extension.start_iscsi_target, - iqn=self.fake_iqn) - - expected = [mock.call('tgtd'), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'target', '--op', 'new', '--tid', '1', - '--targetname', self.fake_iqn)] - mock_execute.assert_has_calls(expected) - mock_dispatch.assert_called_once_with('get_os_install_device', - permit_refresh=True) - - def test_start_iscsi_target_fail_command_not_exist(self, mock_execute, - mock_dispatch, - mock_destroy): - mock_dispatch.return_value = self.fake_dev - mock_execute.side_effect = OSError('file not found') - self.assertRaises(errors.ISCSIError, - self.agent_extension.start_iscsi_target, - iqn=self.fake_iqn) - - -_ORIG_UTILS = iscsi.rtslib_fb.utils - - -@mock.patch.object(disk_utils, 'destroy_disk_metadata', autospec=True) -@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) -# Don't mock the utils module, as it contains exceptions -@mock.patch.object(iscsi, 'rtslib_fb', utils=_ORIG_UTILS, autospec=True) -class TestISCSIExtensionLIO(base.IronicAgentTest): - - def setUp(self): - super(TestISCSIExtensionLIO, self).setUp() - self.agent_extension = iscsi.ISCSIExtension(FakeAgent()) - self.fake_dev = '/dev/fake' - self.fake_iqn = 'iqn-fake' - - @mock.patch('ironic_python_agent.netutils.get_wildcard_address', - autospec=True) - def test_start_iscsi_target(self, mock_get_wildcard_address, - mock_rtslib, mock_dispatch, - mock_destroy): - mock_get_wildcard_address.return_value = '::' - mock_dispatch.return_value = self.fake_dev - result = self.agent_extension.start_iscsi_target(iqn=self.fake_iqn) - - self.assertEqual({'iscsi_target_iqn': self.fake_iqn}, - result.command_result) - mock_rtslib.BlockStorageObject.assert_called_once_with( - name=self.fake_iqn, dev=self.fake_dev) - mock_rtslib.Target.assert_called_once_with(mock.ANY, self.fake_iqn, - mode='create') - mock_rtslib.TPG.assert_called_once_with( - mock_rtslib.Target.return_value, mode='create') - mock_rtslib.LUN.assert_called_once_with( - mock_rtslib.TPG.return_value, - storage_object=mock_rtslib.BlockStorageObject.return_value, - lun=1) - mock_rtslib.NetworkPortal.assert_called_once_with( - mock_rtslib.TPG.return_value, '[::]', 3260) - self.assertFalse(mock_destroy.called) - - @mock.patch('ironic_python_agent.netutils.get_wildcard_address', - autospec=True) - def test_start_iscsi_target_noipv6(self, mock_get_wildcard_address, - mock_rtslib, mock_dispatch, - mock_destroy): - mock_get_wildcard_address.return_value = '0.0.0.0' - mock_dispatch.return_value = self.fake_dev - result = self.agent_extension.start_iscsi_target(iqn=self.fake_iqn) - - self.assertEqual({'iscsi_target_iqn': self.fake_iqn}, - result.command_result) - mock_rtslib.BlockStorageObject.assert_called_once_with( - name=self.fake_iqn, dev=self.fake_dev) - mock_rtslib.Target.assert_called_once_with(mock.ANY, self.fake_iqn, - mode='create') - mock_rtslib.TPG.assert_called_once_with( - mock_rtslib.Target.return_value, mode='create') - mock_rtslib.LUN.assert_called_once_with( - mock_rtslib.TPG.return_value, - storage_object=mock_rtslib.BlockStorageObject.return_value, - lun=1) - mock_rtslib.NetworkPortal.assert_called_once_with( - mock_rtslib.TPG.return_value, '0.0.0.0', 3260) - self.assertFalse(mock_destroy.called) - - @mock.patch('ironic_python_agent.netutils.get_wildcard_address', - autospec=True) - def test_start_iscsi_target_with_special_port(self, - mock_get_wildcard_address, - mock_rtslib, mock_dispatch, - mock_destroy): - mock_get_wildcard_address.return_value = '::' - mock_dispatch.return_value = self.fake_dev - result = self.agent_extension.start_iscsi_target(iqn=self.fake_iqn, - portal_port=3266) - - self.assertEqual({'iscsi_target_iqn': self.fake_iqn}, - result.command_result) - mock_rtslib.BlockStorageObject.assert_called_once_with( - name=self.fake_iqn, dev=self.fake_dev) - mock_rtslib.Target.assert_called_once_with(mock.ANY, self.fake_iqn, - mode='create') - mock_rtslib.TPG.assert_called_once_with( - mock_rtslib.Target.return_value, mode='create') - mock_rtslib.LUN.assert_called_once_with( - mock_rtslib.TPG.return_value, - storage_object=mock_rtslib.BlockStorageObject.return_value, - lun=1) - mock_rtslib.NetworkPortal.assert_called_once_with( - mock_rtslib.TPG.return_value, '[::]', 3266) - - def test_failed_to_start_iscsi(self, mock_rtslib, mock_dispatch, - mock_destroy): - mock_dispatch.return_value = self.fake_dev - mock_rtslib.Target.side_effect = _ORIG_UTILS.RTSLibError() - self.assertRaisesRegex( - errors.ISCSIError, 'Failed to create a target', - self.agent_extension.start_iscsi_target, iqn=self.fake_iqn) - - @mock.patch('ironic_python_agent.netutils.get_wildcard_address', - autospec=True) - def test_failed_to_bind_iscsi(self, mock_get_wildcard_address, - mock_rtslib, mock_dispatch, mock_destroy): - mock_get_wildcard_address.return_value = '::' - mock_dispatch.return_value = self.fake_dev - mock_rtslib.NetworkPortal.side_effect = _ORIG_UTILS.RTSLibError() - self.assertRaisesRegex( - errors.ISCSIError, 'Failed to publish a target', - self.agent_extension.start_iscsi_target, iqn=self.fake_iqn, - portal_port=None) - - mock_rtslib.BlockStorageObject.assert_called_once_with( - name=self.fake_iqn, dev=self.fake_dev) - mock_rtslib.Target.assert_called_once_with(mock.ANY, self.fake_iqn, - mode='create') - mock_rtslib.TPG.assert_called_once_with( - mock_rtslib.Target.return_value, mode='create') - mock_rtslib.LUN.assert_called_once_with( - mock_rtslib.TPG.return_value, - storage_object=mock_rtslib.BlockStorageObject.return_value, - lun=1) - mock_rtslib.NetworkPortal.assert_called_once_with( - mock_rtslib.TPG.return_value, '[::]', 3260) - self.assertFalse(mock_destroy.called) - - def test_failed_to_start_iscsi_wipe_disk_metadata(self, mock_rtslib, - mock_dispatch, - mock_destroy): - mock_dispatch.return_value = self.fake_dev - mock_rtslib.Target.side_effect = _ORIG_UTILS.RTSLibError() - self.assertRaisesRegex( - errors.ISCSIError, 'Failed to create a target', - self.agent_extension.start_iscsi_target, - iqn=self.fake_iqn, - wipe_disk_metadata=True) - mock_destroy.assert_called_once_with('/dev/fake', 'my_node_uuid') - - -@mock.patch.object(iscsi.rtslib_fb, 'RTSRoot', autospec=True) -@mock.patch.object(utils, 'execute', autospec=True) -class TestISCSIExtensionCleanUpFallback(base.IronicAgentTest): - - def setUp(self): - super(TestISCSIExtensionCleanUpFallback, self).setUp() - self.agent_extension = iscsi.ISCSIExtension() - self.fake_dev = '/dev/fake' - self.fake_iqn = 'iqn-fake' - - def test_lio_not_available(self, mock_execute, mock_rtslib): - mock_execute.return_value = ('', '') - mock_rtslib.side_effect = EnvironmentError() - expected = [mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'target', '--op', 'unbind', '--tid', '1', - '--initiator-address', 'ALL'), - mock.call('sync'), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', 'target', - '--op', 'delete', '--tid', '1')] - iscsi.clean_up(self.fake_dev) - mock_execute.assert_has_calls(expected) - - def test_commands_fail(self, mock_execute, mock_rtslib): - mock_execute.side_effect = [processutils.ProcessExecutionError(), - ('', ''), - processutils.ProcessExecutionError()] - mock_rtslib.side_effect = EnvironmentError() - expected = [mock.call('tgtadm', '--lld', 'iscsi', '--mode', - 'target', '--op', 'unbind', '--tid', '1', - '--initiator-address', 'ALL'), - mock.call('sync'), - mock.call('tgtadm', '--lld', 'iscsi', '--mode', 'target', - '--op', 'delete', '--tid', '1')] - iscsi.clean_up(self.fake_dev) - mock_execute.assert_has_calls(expected) - - -@mock.patch.object(iscsi.rtslib_fb, 'RTSRoot', autospec=True) -class TestISCSIExtensionCleanUp(base.IronicAgentTest): - - def setUp(self): - super(TestISCSIExtensionCleanUp, self).setUp() - self.agent_extension = iscsi.ISCSIExtension() - self.fake_dev = '/dev/fake' - self.fake_iqn = 'iqn-fake' - - 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) diff --git a/releasenotes/notes/no-iscsi-fd21808edbea5ac2.yaml b/releasenotes/notes/no-iscsi-fd21808edbea5ac2.yaml new file mode 100644 index 000000000..efebe63d0 --- /dev/null +++ b/releasenotes/notes/no-iscsi-fd21808edbea5ac2.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Removes support for the ``iscsi`` deploy interface. Please use a ramdisk + from the Wallaby cycle if you need it. diff --git a/requirements.txt b/requirements.txt index eacef3150..a08384d25 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,6 @@ Pint>=0.5 # BSD psutil>=3.2.2 # BSD pyudev>=0.18 # LGPLv2.1+ requests>=2.14.2 # Apache-2.0 -rtslib-fb>=2.1.65 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 tenacity>=6.2.0 # Apache-2.0 ironic-lib>=4.5.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index bea68f54d..cdeb73a1b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,6 @@ ironic_python_agent.extensions = clean = ironic_python_agent.extensions.clean:CleanExtension deploy = ironic_python_agent.extensions.deploy:DeployExtension flow = ironic_python_agent.extensions.flow:FlowExtension - iscsi = ironic_python_agent.extensions.iscsi:ISCSIExtension image = ironic_python_agent.extensions.image:ImageExtension log = ironic_python_agent.extensions.log:LogExtension rescue = ironic_python_agent.extensions.rescue:RescueExtension