ironic-python-agent/ironic_python_agent/extensions/iscsi.py
Julia Kreger c5956bdada Suppress errors from iscsi session cleanup
In some cases, where the rts library is not installed, IPA
was recently changed to try and tear down the local side of
the iscsi connection by trying to tear down bond and target
being offered. The whole attempt with this is to ensure that
no disk locks are in place which can prevent partition table
updates.

Since we added this logic, in some cases these commands can
fail and cause the deployment process to fail when it would
have otherwise succeeded. As such, suppress the errors.

Change-Id: I0e04936ad337b394dd68e9b0396a9f1203218f9f
2019-11-01 06:57:58 -07:00

220 lines
8.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 (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')
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}