Browse Source

Remove the iscsi extension

Change-Id: I2f0e581575112d6c7ba0d211661cab3e0b6caca6
changes/01/790401/1
Dmitry Tantsur 5 months ago
parent
commit
be3882162e
  1. 1
      ironic_python_agent/agent.py
  2. 19
      ironic_python_agent/errors.py
  3. 6
      ironic_python_agent/extensions/image.py
  4. 221
      ironic_python_agent/extensions/iscsi.py
  5. 71
      ironic_python_agent/tests/unit/extensions/test_image.py
  6. 388
      ironic_python_agent/tests/unit/extensions/test_iscsi.py
  7. 5
      releasenotes/notes/no-iscsi-fd21808edbea5ac2.yaml
  8. 1
      requirements.txt
  9. 1
      setup.cfg

1
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):

19
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."""

6
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.

221
ironic_python_agent/extensions/iscsi.py

@ -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}

71
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')
]

388
ironic_python_agent/tests/unit/extensions/test_iscsi.py

@ -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)

5
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.

1
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

1
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

Loading…
Cancel
Save