308cddc1bb
The _execute helper was catching OSError, except it was expecting the same attributes as ProcessExecutionError, which is incorrect. Added a separate catch and unit test to ensure that we at least properly catch and convert the error instead of raising an error about an attribute that does not exist. Change-Id: Id47715a5657478e4d9dd10ea7f360b1ededa27de Closes-Bug: #1715466
199 lines
7.5 KiB
Python
199 lines
7.5 KiB
Python
# 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 (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, 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')
|
|
|
|
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())
|
|
|
|
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}
|