
When we added software raid support, we started calling bootloader installation. As time went on, we ehnanced that code path for non RAID cases in order to ensure that UEFI nvram was setup for the instance to boot properly. Somewhere in this process, we missed a possible failure case where the iscsi client tgtadm may return failures. Obviously, the correct path is to not call iscsi teardown if we don't need to. Since it was always semi-opportunistic teardown, we can't blindly catch any error, and if we started iSCSI and failed to tear the connection down, we might want to still fail, so this change moves the logic over to use a flag on the agent object which one extension to set the flag and the other to read it and take action based upon that. Change-Id: Id3b1ae5e59282f4109f6246d5614d44c93aefa7c Story: 2007937 Task: 40395
221 lines
8.6 KiB
Python
221 lines
8.6 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())
|
|
# 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}
|