Bye-bye iSCSI deploy, you served us well
The iSCSI deploy was very easy to start with, but it has since become apparently that it suffers from scalability and maintenance issues. It was deprecated in the Victoria cycle and can now be removed. Hide the guide to upgrade to hardware types since it's very outdated. I had to remove the iBMC diagram since my SVG-fu is not enough to fix it. Change-Id: I2cd6bf7b27fe0be2c08104b0cc37654b506b2e62changes/82/789382/5
parent
e79f163837
commit
929907d684
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 130 KiB |
@ -1,5 +0,0 @@
|
||||
Configuring iSCSI-based drivers
|
||||
-------------------------------
|
||||
|
||||
Ensure that the ``qemu-img`` and ``iscsiadm`` tools are installed on the
|
||||
**ironic-conductor** host(s).
|
@ -1,44 +0,0 @@
|
||||
# Copyright 2016 Intel Corporation
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# 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 oslo_config import cfg
|
||||
|
||||
from ironic.common.i18n import _
|
||||
|
||||
opts = [
|
||||
cfg.PortOpt('portal_port',
|
||||
default=3260,
|
||||
mutable=True,
|
||||
help=_('The port number on which the iSCSI portal listens '
|
||||
'for incoming connections.')),
|
||||
cfg.StrOpt('conv_flags',
|
||||
mutable=True,
|
||||
help=_('Flags that need to be sent to the dd command, '
|
||||
'to control the conversion of the original file '
|
||||
'when copying to the host. It can contain several '
|
||||
'options separated by commas.')),
|
||||
cfg.IntOpt('verify_attempts',
|
||||
default=3,
|
||||
min=1,
|
||||
mutable=True,
|
||||
help=_('Maximum attempts to verify an iSCSI connection is '
|
||||
'active, sleeping 1 second between attempts. Defaults '
|
||||
'to 3.')),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_opts(opts, group='iscsi')
|
@ -1,813 +0,0 @@
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# 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.
|
||||
|
||||
import contextlib
|
||||
import glob
|
||||
import os
|
||||
import time
|
||||
from urllib import parse as urlparse
|
||||
|
||||
from ironic_lib import disk_utils
|
||||
from ironic_lib import metrics_utils
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent_base
|
||||
from ironic.drivers.modules import boot_mode_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
|
||||
DISK_LAYOUT_PARAMS = ('root_gb', 'swap_mb', 'ephemeral_gb')
|
||||
|
||||
|
||||
def _save_disk_layout(node, i_info):
|
||||
"""Saves the disk layout.
|
||||
|
||||
The disk layout used for deployment of the node, is saved.
|
||||
|
||||
:param node: the node of interest
|
||||
:param i_info: instance information (a dictionary) for the node, containing
|
||||
disk layout information
|
||||
"""
|
||||
driver_internal_info = node.driver_internal_info
|
||||
driver_internal_info['instance'] = {}
|
||||
|
||||
for param in DISK_LAYOUT_PARAMS:
|
||||
driver_internal_info['instance'][param] = i_info[param]
|
||||
|
||||
node.driver_internal_info = driver_internal_info
|
||||
node.save()
|
||||
|
||||
|
||||
def discovery(portal_address, portal_port):
|
||||
"""Do iSCSI discovery on portal."""
|
||||
utils.execute('iscsiadm',
|
||||
'-m', 'discovery',
|
||||
'-t', 'st',
|
||||
'-p', '%s:%s' % (utils.wrap_ipv6(portal_address),
|
||||
portal_port),
|
||||
run_as_root=True,
|
||||
attempts=5,
|
||||
delay_on_retry=True)
|
||||
|
||||
|
||||
def login_iscsi(portal_address, portal_port, target_iqn):
|
||||
"""Login to an iSCSI target."""
|
||||
utils.execute('iscsiadm',
|
||||
'-m', 'node',
|
||||
'-p', '%s:%s' % (utils.wrap_ipv6(portal_address),
|
||||
portal_port),
|
||||
'-T', target_iqn,
|
||||
'--login',
|
||||
run_as_root=True,
|
||||
attempts=5,
|
||||
delay_on_retry=True)
|
||||
|
||||
error_occurred = False
|
||||
try:
|
||||
# Ensure the login complete
|
||||
verify_iscsi_connection(target_iqn)
|
||||
# force iSCSI initiator to re-read luns
|
||||
force_iscsi_lun_update(target_iqn)
|
||||
# ensure file system sees the block device
|
||||
check_file_system_for_iscsi_device(portal_address,
|
||||
portal_port,
|
||||
target_iqn)
|
||||
except (exception.InstanceDeployFailure,
|
||||
processutils.ProcessExecutionError) as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
error_occurred = True
|
||||
LOG.error("Failed to login to an iSCSI target due to %s", e)
|
||||
finally:
|
||||
if error_occurred:
|
||||
try:
|
||||
logout_iscsi(portal_address, portal_port, target_iqn)
|
||||
delete_iscsi(portal_address, portal_port, target_iqn)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
LOG.warning("An error occurred when trying to cleanup "
|
||||
"failed ISCSI session error %s", e)
|
||||
|
||||
|
||||
def check_file_system_for_iscsi_device(portal_address,
|
||||
portal_port,
|
||||
target_iqn):
|
||||
"""Ensure the file system sees the iSCSI block device."""
|
||||
check_dir = "/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-1" % (portal_address,
|
||||
portal_port,
|
||||
target_iqn)
|
||||
total_checks = CONF.iscsi.verify_attempts
|
||||
for attempt in range(total_checks):
|
||||
if os.path.exists(check_dir):
|
||||
break
|
||||
time.sleep(1)
|
||||
if LOG.isEnabledFor(logging.DEBUG):
|
||||
existing_devs = ', '.join(glob.iglob('/dev/disk/by-path/*iscsi*'))
|
||||
LOG.debug("iSCSI connection not seen by file system. Rechecking. "
|
||||
"Attempt %(attempt)d out of %(total)d. Available iSCSI "
|
||||
"devices: %(devs)s.",
|
||||
{"attempt": attempt + 1,
|
||||
"total": total_checks,
|
||||
"devs": existing_devs})
|
||||
else:
|
||||
msg = _("iSCSI connection was not seen by the file system after "
|
||||
"attempting to verify %d times.") % total_checks
|
||||
LOG.error(msg)
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
|
||||
def verify_iscsi_connection(target_iqn):
|
||||
"""Verify iscsi connection."""
|
||||
LOG.debug("Checking for iSCSI target to become active.")
|
||||
|
||||
total_checks = CONF.iscsi.verify_attempts
|
||||
for attempt in range(total_checks):
|
||||
out, _err = utils.execute('iscsiadm',
|
||||
'-m', 'node',
|
||||
'-S',
|
||||
run_as_root=True)
|
||||
if target_iqn in out:
|
||||
break
|
||||
time.sleep(1)
|
||||
LOG.debug("iSCSI connection not active. Rechecking. Attempt "
|
||||
"%(attempt)d out of %(total)d",
|
||||
{"attempt": attempt + 1, "total": total_checks})
|
||||
else:
|
||||
msg = _("iSCSI connection did not become active after attempting to "
|
||||
"verify %d times.") % total_checks
|
||||
LOG.error(msg)
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
|
||||
def force_iscsi_lun_update(target_iqn):
|
||||
"""force iSCSI initiator to re-read luns."""
|
||||
LOG.debug("Re-reading iSCSI luns.")
|
||||
utils.execute('iscsiadm',
|
||||
'-m', 'node',
|
||||
'-T', target_iqn,
|
||||
'-R',
|
||||
run_as_root=True)
|
||||
|
||||
|
||||
def logout_iscsi(portal_address, portal_port, target_iqn):
|
||||
"""Logout from an iSCSI target."""
|
||||
utils.execute('iscsiadm',
|
||||
'-m', 'node',
|
||||
'-p', '%s:%s' % (utils.wrap_ipv6(portal_address),
|
||||
portal_port),
|
||||
'-T', target_iqn,
|
||||
'--logout',
|
||||
run_as_root=True,
|
||||
attempts=5,
|
||||
delay_on_retry=True)
|
||||
|
||||
|
||||
def delete_iscsi(portal_address, portal_port, target_iqn):
|
||||
"""Delete the iSCSI target."""
|
||||
# Retry delete until it succeeds (exit code 0) or until there is
|
||||
# no longer a target to delete (exit code 21).
|
||||
utils.execute('iscsiadm',
|
||||
'-m', 'node',
|
||||
'-p', '%s:%s' % (utils.wrap_ipv6(portal_address),
|
||||
portal_port),
|
||||
'-T', target_iqn,
|
||||
'-o', 'delete',
|
||||
run_as_root=True,
|
||||
check_exit_code=[0, 21],
|
||||
attempts=5,
|
||||
delay_on_retry=True)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _iscsi_setup_and_handle_errors(address, port, iqn, lun):
|
||||
"""Function that yields an iSCSI target device to work on.
|
||||
|
||||
:param address: The iSCSI IP address.
|
||||
:param port: The iSCSI port number.
|
||||
:param iqn: The iSCSI qualified name.
|
||||
:param lun: The iSCSI logical unit number.
|
||||
"""
|
||||
dev = ("/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-%s"
|
||||
% (address, port, iqn, lun))
|
||||
discovery(address, port)
|
||||
login_iscsi(address, port, iqn)
|
||||
if not disk_utils.is_block_device(dev):
|
||||
raise exception.InstanceDeployFailure(_("Parent device '%s' not found")
|
||||
% dev)
|
||||
try:
|
||||
yield dev
|
||||
except processutils.ProcessExecutionError as err:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error("Deploy to address %s failed.", address)
|
||||
LOG.error("Command: %s", err.cmd)
|
||||
LOG.error("StdOut: %r", err.stdout)
|
||||
LOG.error("StdErr: %r", err.stderr)
|
||||
except exception.InstanceDeployFailure as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error("Deploy to address %s failed.", address)
|
||||
LOG.error(e)
|
||||
finally:
|
||||
logout_iscsi(address, port, iqn)
|
||||
delete_iscsi(address, port, iqn)
|
||||
|
||||
|
||||
def deploy_partition_image(
|
||||
address, port, iqn, lun, image_path,
|
||||
root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid,
|
||||
preserve_ephemeral=False, configdrive=None,
|
||||
boot_option=None, boot_mode="bios", disk_label=None,
|
||||
cpu_arch=""):
|
||||
"""All-in-one function to deploy a partition image to a node.
|
||||
|
||||
:param address: The iSCSI IP address.
|
||||
:param port: The iSCSI port number.
|
||||
:param iqn: The iSCSI qualified name.
|
||||
:param lun: The iSCSI logical unit number.
|
||||
:param image_path: Path for the instance's disk image.
|
||||
:param root_mb: Size of the root partition in megabytes.
|
||||
:param swap_mb: Size of the swap partition in megabytes.
|
||||
:param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0,
|
||||
no ephemeral partition will be created.
|
||||
:param ephemeral_format: The type of file system to format the ephemeral
|
||||
partition.
|
||||
:param node_uuid: node's uuid. Used for logging.
|
||||
:param preserve_ephemeral: If True, no filesystem is written to the
|
||||
ephemeral block device, preserving whatever
|
||||
content it had (if the partition table has
|
||||
not changed).
|
||||
:param configdrive: Optional. Base64 encoded Gzipped configdrive content
|
||||
or configdrive HTTP URL.
|
||||
:param boot_option: Can be "local" or "netboot".
|
||||
"netboot" by default.
|
||||
:param boot_mode: Can be "bios" or "uefi". "bios" by default.
|
||||
:param disk_label: The disk label to be used when creating the
|
||||
partition table. Valid values are: "msdos",
|
||||
"gpt" or None; If None ironic will figure it
|
||||
out according to the boot_mode parameter.
|
||||
:param cpu_arch: Architecture of the node being deployed to.
|
||||
:raises: InstanceDeployFailure if image virtual size is bigger than root
|
||||
partition size.
|
||||
:returns: a dictionary containing the following keys:
|
||||
'root uuid': UUID of root partition
|
||||
'efi system partition uuid': UUID of the uefi system partition
|
||||
(if boot mode is uefi).
|
||||
NOTE: If key exists but value is None, it means partition doesn't
|
||||
exist.
|
||||
"""
|
||||
# NOTE(dtantsur): CONF.default_boot_option is mutable, don't use it in
|
||||
# the function signature!
|
||||
boot_option = boot_option or deploy_utils.get_default_boot_option()
|
||||
image_mb = disk_utils.get_image_mb(image_path)
|
||||
if image_mb > root_mb:
|
||||
msg = (_('Root partition is too small for requested image. Image '
|
||||
'virtual size: %(image_mb)d MB, Root size: %(root_mb)d MB')
|
||||
% {'image_mb': image_mb, 'root_mb': root_mb})
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
with _iscsi_setup_and_handle_errors(address, port, iqn, lun) as dev:
|
||||
uuid_dict_returned = disk_utils.work_on_disk(
|
||||
dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path,
|
||||
node_uuid, preserve_ephemeral=preserve_ephemeral,
|
||||
configdrive=configdrive, boot_option=boot_option,
|
||||
boot_mode=boot_mode, disk_label=disk_label, cpu_arch=cpu_arch)
|
||||
|
||||
return uuid_dict_returned
|
||||
|
||||
|
||||
def deploy_disk_image(address, port, iqn, lun,
|
||||
image_path, node_uuid, configdrive=None,
|
||||
conv_flags=None):
|
||||
"""All-in-one function to deploy a whole disk image to a node.
|
||||
|
||||
:param address: The iSCSI IP address.
|
||||
:param port: The iSCSI port number.
|
||||
:param iqn: The iSCSI qualified name.
|
||||
:param lun: The iSCSI logical unit number.
|
||||
:param image_path: Path for the instance's disk image.
|
||||
:param node_uuid: node's uuid.
|
||||
:param configdrive: Optional. Base64 encoded Gzipped configdrive content
|
||||
or configdrive HTTP URL.
|
||||
:param conv_flags: Optional. Add a flag that will modify the behaviour of
|
||||
the image copy to disk.
|
||||
:returns: a dictionary containing the key 'disk identifier' to identify
|
||||
the disk which was used for deployment.
|
||||
"""
|
||||
with _iscsi_setup_and_handle_errors(address, port, iqn,
|
||||
lun) as dev:
|
||||
disk_utils.populate_image(image_path, dev, conv_flags=conv_flags)
|
||||
|
||||
if configdrive:
|
||||
disk_utils.create_config_drive_partition(node_uuid, dev,
|
||||
configdrive)
|
||||
|
||||
disk_identifier = disk_utils.get_disk_identifier(dev)
|
||||
|
||||
return {'disk identifier': disk_identifier}
|
||||
|
||||
|
||||
@METRICS.timer('check_image_size')
|
||||
def check_image_size(task):
|
||||
"""Check if the requested image is larger than the root partition size.
|
||||
|
||||
Does nothing for whole-disk images.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InstanceDeployFailure if size of the image is greater than root
|
||||
partition.
|
||||
"""
|
||||
if task.node.driver_internal_info['is_whole_disk_image']:
|
||||
# The root partition is already created and populated, no use
|
||||
# validating its size
|
||||
return
|
||||
|
||||
i_info = deploy_utils.parse_instance_info(task.node)
|
||||
image_path = deploy_utils._get_image_file_path(task.node.uuid)
|
||||
image_mb = disk_utils.get_image_mb(image_path)
|
||||
root_mb = 1024 * int(i_info['root_gb'])
|
||||
if image_mb > root_mb:
|
||||
|