Support Linux-IO in addition to tgtd
The iSCSI extension now tries to use Linux-IO first (via rtslib) and falls back to tgtd if Linux-IO can't be used (e.g. in the CoreOS-based image which uses containers). Change-Id: I9cc7a30d9c93c445a66d183146e9260c2b096d33 Closes-Bug: #1504562
This commit is contained in:
parent
f4ad4d7500
commit
c474a5ac6c
@ -310,10 +310,18 @@ class ISCSIError(RESTError):
|
||||
|
||||
message = 'Error starting iSCSI target'
|
||||
|
||||
def __init__(self, error_msg):
|
||||
details = 'Error starting iSCSI target: {0}'.format(error_msg)
|
||||
super(ISCSIError, self).__init__(details)
|
||||
|
||||
|
||||
class ISCSICommandError(ISCSIError):
|
||||
"""Error executing TGT command."""
|
||||
|
||||
def __init__(self, error_msg, exit_code, stdout, stderr):
|
||||
details = ('{0}. Failed with exit code {1}. stdout: {2}. stderr: {3}')
|
||||
details = details.format(error_msg, exit_code, stdout, stderr)
|
||||
super(ISCSIError, self).__init__(details)
|
||||
super(ISCSICommandError, self).__init__(details)
|
||||
|
||||
|
||||
class DeviceNotFound(NotFound):
|
||||
|
@ -19,6 +19,10 @@
|
||||
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
|
||||
@ -33,10 +37,11 @@ def _execute(cmd, error_msg, **kwargs):
|
||||
stdout, stderr = utils.execute(*cmd, **kwargs)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
LOG.error(error_msg)
|
||||
raise errors.ISCSIError(error_msg, e.exit_code, e.stdout, e.stderr)
|
||||
raise errors.ISCSICommandError(error_msg, e.exit_code,
|
||||
e.stdout, e.stderr)
|
||||
|
||||
|
||||
def _wait_for_iscsi_daemon(attempts=10):
|
||||
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
|
||||
@ -44,14 +49,12 @@ def _wait_for_iscsi_daemon(attempts=10):
|
||||
_execute(cmd, "ISCSI daemon didn't initialize", attempts=attempts)
|
||||
|
||||
|
||||
def _start_iscsi_daemon(iqn, device):
|
||||
def _start_tgtd(iqn, device):
|
||||
"""Start a ISCSI target for the device."""
|
||||
LOG.debug("Starting ISCSI target on device %(device)s", {'device': device})
|
||||
|
||||
# Start ISCSI Target daemon
|
||||
_execute(['tgtd'], "Unable to start the ISCSI daemon")
|
||||
|
||||
_wait_for_iscsi_daemon()
|
||||
_wait_for_tgtd()
|
||||
|
||||
cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op',
|
||||
'new', '--tid', '1', '--targetname', iqn]
|
||||
@ -67,15 +70,59 @@ def _start_iscsi_daemon(iqn, device):
|
||||
"initiators for iqn %s" % iqn)
|
||||
|
||||
|
||||
class ISCSIExtension(base.BaseAgentExtension):
|
||||
def _start_lio(iqn, 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: {0}'.format(exc)
|
||||
raise errors.ISCSIError(msg)
|
||||
|
||||
try:
|
||||
# bind to the default port on all interfaces
|
||||
rtslib_fb.NetworkPortal(tpg, '0.0.0.0')
|
||||
except rtslib_fb.utils.RTSLibError as exc:
|
||||
msg = 'Failed to publish a target: {0}'.format(exc)
|
||||
raise errors.ISCSIError(msg)
|
||||
|
||||
|
||||
class ISCSIExtension(base.BaseAgentExtension):
|
||||
@base.sync_command('start_iscsi_target')
|
||||
def start_iscsi_target(self, iqn=None):
|
||||
"""Expose the disk as an ISCSI target."""
|
||||
# If iqn is not given, generate one
|
||||
if iqn is None:
|
||||
iqn = 'iqn-' + uuidutils.generate_uuid()
|
||||
iqn = 'iqn.2008-10.org.openstack:%s' % uuidutils.generate_uuid()
|
||||
|
||||
device = hardware.dispatch_to_managers('get_os_install_device')
|
||||
_start_iscsi_daemon(iqn, device)
|
||||
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.warn('Linux-IO is not available, falling back to TGT. '
|
||||
'Error: %s.', exc)
|
||||
rts_root = None
|
||||
|
||||
if rts_root is None:
|
||||
_start_tgtd(iqn, device)
|
||||
else:
|
||||
_start_lio(iqn, 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,
|
||||
'method': 'tgtd' if rts_root is None else 'linux-io'})
|
||||
|
||||
return {"iscsi_target_iqn": iqn}
|
||||
|
@ -16,7 +16,6 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import time
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslotest import base as test_base
|
||||
@ -29,11 +28,12 @@ from ironic_python_agent import utils
|
||||
|
||||
@mock.patch.object(hardware, 'dispatch_to_managers')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
@mock.patch.object(time, 'sleep', lambda *_: None)
|
||||
class TestISCSIExtension(test_base.BaseTestCase):
|
||||
@mock.patch.object(iscsi.rtslib_fb, 'RTSRoot',
|
||||
mock.Mock(side_effect=iscsi.rtslib_fb.RTSLibError()))
|
||||
class TestISCSIExtensionTgt(test_base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestISCSIExtension, self).setUp()
|
||||
super(TestISCSIExtensionTgt, self).setUp()
|
||||
self.agent_extension = iscsi.ISCSIExtension()
|
||||
self.fake_dev = '/dev/fake'
|
||||
self.fake_iqn = 'iqn-fake'
|
||||
@ -78,11 +78,11 @@ class TestISCSIExtension(test_base.BaseTestCase):
|
||||
mock_execute.assert_has_calls(expected)
|
||||
mock_dispatch.assert_called_once_with('get_os_install_device')
|
||||
|
||||
@mock.patch.object(iscsi, '_wait_for_iscsi_daemon')
|
||||
@mock.patch.object(iscsi, '_wait_for_tgtd')
|
||||
def test_start_iscsi_target_fail_command(self, mock_wait_iscsi,
|
||||
mock_execute, mock_dispatch):
|
||||
mock_dispatch.return_value = self.fake_dev
|
||||
mock_execute.side_effect = [('', ''),
|
||||
mock_execute.side_effect = [('', ''), ('', ''),
|
||||
processutils.ProcessExecutionError('blah')]
|
||||
self.assertRaises(errors.ISCSIError,
|
||||
self.agent_extension.start_iscsi_target,
|
||||
@ -94,3 +94,64 @@ class TestISCSIExtension(test_base.BaseTestCase):
|
||||
'--targetname', self.fake_iqn)]
|
||||
mock_execute.assert_has_calls(expected)
|
||||
mock_dispatch.assert_called_once_with('get_os_install_device')
|
||||
|
||||
|
||||
_ORIG_UTILS = iscsi.rtslib_fb.utils
|
||||
|
||||
|
||||
@mock.patch.object(hardware, 'dispatch_to_managers')
|
||||
# Don't mock the utils module, as it contains exceptions
|
||||
@mock.patch.object(iscsi, 'rtslib_fb', utils=_ORIG_UTILS)
|
||||
class TestISCSIExtensionLIO(test_base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestISCSIExtensionLIO, self).setUp()
|
||||
self.agent_extension = iscsi.ISCSIExtension()
|
||||
self.fake_dev = '/dev/fake'
|
||||
self.fake_iqn = 'iqn-fake'
|
||||
|
||||
def test_start_iscsi_target(self, mock_rtslib, mock_dispatch):
|
||||
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')
|
||||
|
||||
def test_failed_to_start_iscsi(self, mock_rtslib, mock_dispatch):
|
||||
mock_dispatch.return_value = self.fake_dev
|
||||
mock_rtslib.Target.side_effect = _ORIG_UTILS.RTSLibError()
|
||||
self.assertRaisesRegexp(
|
||||
errors.ISCSIError, 'Failed to create a target',
|
||||
self.agent_extension.start_iscsi_target, iqn=self.fake_iqn)
|
||||
|
||||
def test_failed_to_bind_iscsi(self, mock_rtslib, mock_dispatch):
|
||||
mock_dispatch.return_value = self.fake_dev
|
||||
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)
|
||||
|
||||
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')
|
||||
|
@ -18,6 +18,7 @@ Pint>=0.5 # BSD
|
||||
psutil<2.0.0,>=1.1.1
|
||||
pyudev
|
||||
requests>=2.8.1
|
||||
rtslib-fb>=2.1.41
|
||||
six>=1.9.0
|
||||
stevedore>=1.5.0 # Apache-2.0
|
||||
WSME>=0.8
|
||||
|
Loading…
x
Reference in New Issue
Block a user