diff --git a/ironic_python_agent/extensions/iscsi.py b/ironic_python_agent/extensions/iscsi.py index 288e6a2fb..48a0a8874 100644 --- a/ironic_python_agent/extensions/iscsi.py +++ b/ironic_python_agent/extensions/iscsi.py @@ -31,6 +31,7 @@ from ironic_python_agent import hardware from ironic_python_agent import utils LOG = log.getLogger(__name__) +DEFAULT_ISCSI_PORTAL_PORT = 3260 def _execute(cmd, error_msg, **kwargs): @@ -50,13 +51,21 @@ def _wait_for_tgtd(attempts=10): _execute(cmd, "ISCSI daemon didn't initialize", attempts=attempts) -def _start_tgtd(iqn, device): +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) @@ -71,7 +80,7 @@ def _start_tgtd(iqn, device): "initiators for iqn %s" % iqn) -def _start_lio(iqn, device): +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, @@ -90,7 +99,7 @@ def _start_lio(iqn, device): try: # bind to the default port on all interfaces - rtslib_fb.NetworkPortal(tpg, '0.0.0.0') + rtslib_fb.NetworkPortal(tpg, '0.0.0.0', portal_port) except rtslib_fb.utils.RTSLibError as exc: msg = 'Failed to publish a target: {0}'.format(exc) raise errors.ISCSIError(msg) @@ -137,7 +146,8 @@ def clean_up(device): class ISCSIExtension(base.BaseAgentExtension): @base.sync_command('start_iscsi_target') - def start_iscsi_target(self, iqn=None, wipe_disk_metadata=False): + def start_iscsi_target(self, iqn=None, wipe_disk_metadata=False, + portal_port=None): """Expose the disk as an ISCSI target. :param wipe_disk_metadata: if the disk metadata should be wiped out @@ -164,15 +174,18 @@ class ISCSIExtension(base.BaseAgentExtension): '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, device) + _start_tgtd(iqn, portal_port, device) else: - _start_lio(iqn, device) + _start_lio(iqn, portal_port, device) LOG.debug('Linux-IO configuration: %s', rts_root.dump()) - LOG.info('Created iSCSI target with iqn %(iqn)s on device %(dev)s ' - 'using %(method)s', - {'iqn': iqn, 'dev': device, + LOG.info('Created iSCSI target with iqn %(iqn)s, portal_port %(port)d,' + ' on device %(dev)s using %(method)s', + {'iqn': iqn, 'portal_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_iscsi.py b/ironic_python_agent/tests/unit/extensions/test_iscsi.py index 374e9fc49..6691e09a7 100644 --- a/ironic_python_agent/tests/unit/extensions/test_iscsi.py +++ b/ironic_python_agent/tests/unit/extensions/test_iscsi.py @@ -70,6 +70,34 @@ class TestISCSIExtensionTgt(test_base.BaseTestCase): 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') + 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): @@ -142,9 +170,31 @@ class TestISCSIExtensionLIO(test_base.BaseTestCase): 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') + mock_rtslib.TPG.return_value, '0.0.0.0', 3260) self.assertFalse(mock_destroy.called) + def test_start_iscsi_target_with_special_port(self, mock_rtslib, + mock_dispatch, + mock_destroy): + 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, '0.0.0.0', 3266) + def test_failed_to_start_iscsi(self, mock_rtslib, mock_dispatch, mock_destroy): mock_dispatch.return_value = self.fake_dev @@ -159,7 +209,8 @@ class TestISCSIExtensionLIO(test_base.BaseTestCase): mock_rtslib.NetworkPortal.side_effect = _ORIG_UTILS.RTSLibError() self.assertRaisesRegexp( errors.ISCSIError, 'Failed to publish a target', - self.agent_extension.start_iscsi_target, iqn=self.fake_iqn) + 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) @@ -172,7 +223,7 @@ class TestISCSIExtensionLIO(test_base.BaseTestCase): 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') + mock_rtslib.TPG.return_value, '0.0.0.0', 3260) self.assertFalse(mock_destroy.called) def test_failed_to_start_iscsi_wipe_disk_metadata(self, mock_rtslib, diff --git a/releasenotes/notes/add-portal-port-arg-6d4faec2f709c8e9.yaml b/releasenotes/notes/add-portal-port-arg-6d4faec2f709c8e9.yaml new file mode 100644 index 000000000..638c1022b --- /dev/null +++ b/releasenotes/notes/add-portal-port-arg-6d4faec2f709c8e9.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add new portal_port argument into iscsi.start_iscsi_target + to support iscsi portal port customization.