Files
ironic/devstack/lib/ironic

4456 lines
186 KiB
Bash

#!/bin/bash
#
# lib/ironic
# Functions to control the configuration and operation of the **Ironic** service
# Dependencies:
#
# - ``functions`` file
# - ``DEST``, ``DATA_DIR``, ``STACK_USER`` must be defined
# - ``SERVICE_{TENANT_NAME|PASSWORD}`` must be defined
# - ``SERVICE_HOST``
# - ``KEYSTONE_TOKEN_FORMAT`` must be defined
# ``stack.sh`` calls the entry points in this order:
#
# - install_ironic
# - install_ironicclient
# - init_ironic
# - start_ironic
# - stop_ironic
# - cleanup_ironic
# ensure we don't re-source this in the same environment
[[ -z "$_IRONIC_DEVSTACK_LIB" ]] || return 0
declare -r -g _IRONIC_DEVSTACK_LIB=1
# Save xtrace and pipefail settings
_XTRACE_IRONIC=$(set +o | grep xtrace)
_PIPEFAIL_IRONIC=$(set +o | grep pipefail)
set -o xtrace
set +o pipefail
# Defaults
# --------
# Set up default directories
GITDIR["python-ironicclient"]=$DEST/python-ironicclient
GITREPO["pyghmi"]=${PYGHMI_REPO:-${GIT_BASE}/x/pyghmi}
GITBRANCH["pyghmi"]=${PYGHMI_BRANCH:-master}
GITDIR["pyghmi"]=$DEST/pyghmi
GITREPO["virtualbmc"]=${VIRTUALBMC_REPO:-${GIT_BASE}/openstack/virtualbmc.git}
GITBRANCH["virtualbmc"]=${VIRTUALBMC_BRANCH:-master}
GITDIR["virtualbmc"]=$DEST/virtualbmc
GITREPO["virtualpdu"]=${VIRTUALPDU_REPO:-${GIT_BASE}/openstack/virtualpdu.git}
GITBRANCH["virtualpdu"]=${VIRTUALPDU_BRANCH:-master}
GITDIR["virtualpdu"]=$DEST/virtualpdu
GITREPO["sushy"]=${SUSHY_REPO:-${GIT_BASE}/openstack/sushy.git}
GITBRANCH["sushy"]=${SUSHY_BRANCH:-master}
GITDIR["sushy"]=$DEST/sushy
GITREPO["sushy-tools"]=${SUSHY_TOOLS_REPO:-${GIT_BASE}/openstack/sushy-tools.git}
GITBRANCH["sushy-tools"]=${SUSHY_TOOLS_BRANCH:-master}
GITDIR["sushy-tools"]=$DEST/sushy-tools
IRONIC_DIR=$DEST/ironic
IRONIC_DEVSTACK_DIR=$IRONIC_DIR/devstack
IRONIC_DEVSTACK_FILES_DIR=$IRONIC_DEVSTACK_DIR/files
# TODO(dtantsur): delete these three when we migrate image building to
# ironic-python-agent-builder completely
IRONIC_PYTHON_AGENT_REPO=${IRONIC_PYTHON_AGENT_REPO:-${GIT_BASE}/openstack/ironic-python-agent.git}
IRONIC_PYTHON_AGENT_BRANCH=${IRONIC_PYTHON_AGENT_BRANCH:-$TARGET_BRANCH}
IRONIC_PYTHON_AGENT_DIR=$DEST/ironic-python-agent
IRONIC_PYTHON_AGENT_BUILDER_REPO=${IRONIC_PYTHON_AGENT_BUILDER_REPO:-${GIT_BASE}/openstack/ironic-python-agent-builder.git}
IRONIC_PYTHON_AGENT_BUILDER_BRANCH=${IRONIC_PYTHON_AGENT_BUILDER_BRANCH:-$BRANCHLESS_TARGET_BRANCH}
IRONIC_PYTHON_AGENT_BUILDER_DIR=$DEST/ironic-python-agent-builder
IRONIC_DIB_BINDEP_FILE=https://opendev.org/openstack/diskimage-builder/raw/branch/master/bindep.txt
IRONIC_DATA_DIR=$DATA_DIR/ironic
IRONIC_STATE_PATH=/var/lib/ironic
IRONIC_AUTH_CACHE_DIR=${IRONIC_AUTH_CACHE_DIR:-/var/cache/ironic}
IRONIC_CONF_DIR=${IRONIC_CONF_DIR:-/etc/ironic}
IRONIC_CONF_FILE=$IRONIC_CONF_DIR/ironic.conf
IRONIC_MACHINE_TYPE=${IRONIC_MACHINE_TYPE:-q35}
# Whether DevStack will be setup for bare metal or VMs
IRONIC_IS_HARDWARE=$(trueorfalse False IRONIC_IS_HARDWARE)
# Deploy callback timeout can be changed from its default (1800), if required.
IRONIC_CALLBACK_TIMEOUT=${IRONIC_CALLBACK_TIMEOUT:-}
# Timeout before retrying PXE boot. Set low to help the CI.
if [[ "$IRONIC_IS_HARDWARE" == False ]]; then
IRONIC_PXE_BOOT_RETRY_TIMEOUT=${IRONIC_PXE_BOOT_RETRY_TIMEOUT:-900}
else
IRONIC_PXE_BOOT_RETRY_TIMEOUT=${IRONIC_PXE_BOOT_RETRY_TIMEOUT:-}
fi
# Ping timeout after the node becomes active
IRONIC_PING_TIMEOUT=${IRONIC_PING_TIMEOUT:-}
# Whether to allow devstack to continue when ironic-status upgrade check
# finds unsupported drivers. Set to "true" to continue despite the error.
IRONIC_ALLOW_UNSUPPORTED_DRIVERS=$(trueorfalse False IRONIC_ALLOW_UNSUPPORTED_DRIVERS)
# Deploy to hardware platform
IRONIC_HW_NODE_CPU=${IRONIC_HW_NODE_CPU:-1}
IRONIC_HW_NODE_RAM=${IRONIC_HW_NODE_RAM:-512}
IRONIC_HW_NODE_DISK=${IRONIC_HW_NODE_DISK:-10}
IRONIC_HW_EPHEMERAL_DISK=${IRONIC_HW_EPHEMERAL_DISK:-0}
# Ironic hardware architecture type, supported types are:
IRONIC_SUPPORTED_HW_ARCH_TYPES_RE="^(x86_64|aarch64)$"
IRONIC_HW_ARCH=${IRONIC_HW_ARCH:-x86_64}
if [[ ! "$IRONIC_HW_ARCH" =~ $IRONIC_SUPPORTED_HW_ARCH_TYPES_RE ]]; then
die $LINENO "Unsupported IRONIC_HW_ARCH: $IRONIC_HW_ARCH. Expected either of 'x86_64' or 'aarch64'."
fi
if is_ubuntu; then
IRONIC_VM_EMULATOR="/usr/bin/qemu-system-${IRONIC_HW_ARCH}"
else
# Fedora/Centos
IRONIC_VM_EMULATOR="/usr/libexec/qemu-kvm"
fi
GRUB_ARCH=x64
if [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
GRUB_ARCH=aa64
# NOTE(cid): Original change was tested on virt-6.2
# 'virt' is an alias for whatever is the latest version
IRONIC_MACHINE_TYPE=virt
fi
# Deploy Ironic Nodes & configure n-cpu for sharding
# Currently only supports 0 (old behavior) or 1 (adds all nodes
# to the same shard). You can still use this with multinode to
# get multiple shards in one Ironic install.
# IRONIC_SHARD_1_NAME + IRONIC_SHARDS will also configure n-cpu
IRONIC_SHARDS=${IRONIC_SHARDS:-0}
IRONIC_SHARD_1_NAME=${IRONIC_SHARD_1_NAME:-ds_shard_1}
IRONIC_NODE_SHARD_NAME=${IRONIC_NODE_SHARD_NAME:-ds_shard_1}
# The file is composed of multiple lines, each line includes fields
# separated by white space, in the format:
#
# <BMC address> <MAC address> <BMC username> <BMC password> [<driver specific fields>]
#
# For example:
#
# 192.168.110.107 00:1e:67:57:50:4c root otc123
#
# Supported IRONIC_DEPLOY_DRIVERs:
# ipmi:
# <BMC address> <MAC address> <BMC username> <BMC password>
#
# idrac:
# <BMC address> <MAC address> <BMC username> <BMC password>
#
# irmc:
# <BMC address> <MAC address> <BMC username> <BMC password>
#
IRONIC_HWINFO_FILE=${IRONIC_HWINFO_FILE:-$IRONIC_DATA_DIR/hardware_info}
# Set up defaults for functional / integration testing
IRONIC_NODE_UUID=${IRONIC_NODE_UUID:-`uuidgen`}
IRONIC_SCRIPTS_DIR=${IRONIC_SCRIPTS_DIR:-$IRONIC_DEVSTACK_DIR/tools/ironic/scripts}
IRONIC_TEMPLATES_DIR=${IRONIC_TEMPLATES_DIR:-$IRONIC_DEVSTACK_DIR/tools/ironic/templates}
IRONIC_BAREMETAL_BASIC_OPS=$(trueorfalse False IRONIC_BAREMETAL_BASIC_OPS)
IRONIC_TFTPBOOT_DIR=${IRONIC_TFTPBOOT_DIR:-$IRONIC_DATA_DIR/tftpboot}
IRONIC_TFTPSERVER_IP=${IRONIC_TFTPSERVER_IP:-$HOST_IP}
IRONIC_TFTP_BLOCKSIZE=${IRONIC_TFTP_BLOCKSIZE:-$((PUBLIC_BRIDGE_MTU-50))}
IRONIC_VM_COUNT=${IRONIC_VM_COUNT:-1}
IRONIC_VM_SPECS_CPU=${IRONIC_VM_SPECS_CPU:-1}
IRONIC_VM_SPECS_RAM=${IRONIC_VM_SPECS_RAM:-3072}
IRONIC_VM_SPECS_DISK=${IRONIC_VM_SPECS_DISK:-10}
IRONIC_VM_SPECS_DISK_FORMAT=${IRONIC_VM_SPECS_DISK_FORMAT:-qcow2}
IRONIC_VM_EPHEMERAL_DISK=${IRONIC_VM_EPHEMERAL_DISK:-0}
IRONIC_VM_ENGINE=${IRONIC_VM_ENGINE:-qemu}
IRONIC_VM_NETWORK_BRIDGE=${IRONIC_VM_NETWORK_BRIDGE:-brbm}
IRONIC_VM_INTERFACE_COUNT=${IRONIC_VM_INTERFACE_COUNT:-2}
IRONIC_VM_VOLUME_COUNT=${IRONIC_VM_VOLUME_COUNT:-1}
IRONIC_VM_BLOCK_SIZE=${IRONIC_VM_BLOCK_SIZE:-512}
IRONIC_VM_MACS_CSV_FILE=${IRONIC_VM_MACS_CSV_FILE:-$IRONIC_DATA_DIR/ironic_macs.csv}
IRONIC_CLEAN_NET_NAME=${IRONIC_CLEAN_NET_NAME:-${IRONIC_PROVISION_NETWORK_NAME:-${PRIVATE_NETWORK_NAME}}}
IRONIC_RESCUE_NET_NAME=${IRONIC_RESCUE_NET_NAME:-${IRONIC_CLEAN_NET_NAME}}
IRONIC_INSPECTION_NET_NAME=${IRONIC_INSPECTION_NET_NAME:-${IRONIC_CLEAN_NET_NAME}}
IRONIC_EXTRA_PXE_PARAMS=${IRONIC_EXTRA_PXE_PARAMS:-}
IRONIC_TTY_DEV=${IRONIC_TTY_DEV:-ttyS0,115200}
IRONIC_USE_PORT_GROUP=$(trueorfalse False IRONIC_USE_PORT_GROUP)
IRONIC_PORT_GROUP_MODE=${IRONIC_PORT_GROUP_MODE:-"balance-rr"}
IRONIC_LOG_STEPS_TO_SYSLOG=${IRONIC_LOG_STEPS_TO_SYSLOG:-False}
IRONIC_TEMPEST_BUILD_TIMEOUT=${IRONIC_TEMPEST_BUILD_TIMEOUT:-${BUILD_TIMEOUT:-}}
if [[ -n "$BUILD_TIMEOUT" ]]; then
echo "WARNING: BUILD_TIMEOUT variable is renamed to IRONIC_TEMPEST_BUILD_TIMEOUT and will be deprecated in Pike."
fi
if [[ "$IRONIC_USE_PORT_GROUP" == "True" && ${IRONIC_VM_INTERFACE_COUNT} -ne 2 ]]; then
die $LINENO "To use portgroups IRONIC_USE_PORT_GROUP=True 2 interfaces for VM is required IRONIC_VM_INTERFACE_COUNT"
fi
hostdomain=$(hostname)
if [[ "$hostdomain" =~ "rax" ]] || [[ "$hostdomain" =~ "iweb" ]]; then
echo "WARNING: Auto-increasing the requested build timeout by 1.5 as the detected hostname suggests a cloud host where VMs are software emulated."
# NOTE(TheJulia): Rax hosts are entirely qemu emulated, not CPU enabled
# virtualization. As such, the ramdisk decompression is known to take an
# eceptional amount of time and we need to afford a little more time to
# these hosts for jobs to complete without issues.
# NOTE(TheJulia): This is also the case for iweb hosts. And arm64 in general.
new_timeout=$(echo "$IRONIC_TEMPEST_BUILD_TIMEOUT * 1.5 / 1" | bc)
IRONIC_TEMPEST_BUILD_TIMEOUT=$new_timeout
if [ -n "$IRONIC_PXE_BOOT_RETRY_TIMEOUT" ]; then
new_timeout=$(echo "$IRONIC_PXE_BOOT_RETRY_TIMEOUT * 1.5 / 1" | bc)
IRONIC_PXE_BOOT_RETRY_TIMEOUT=$new_timeout
fi
# TODO(TheJulia): If we have to do magically extend timeouts again,
# we should make a helper method...
fi
# DO NOT MODIFY/SET
# NOTE(TheJulia): These settings are for virtual media based testing, and
# the need to send traffic *back* to the host. The subnet ID is dynamic,
# the IP needs to be within the floating IP range.
PUBLIC_SUBNET_ID=""
PUBLIC_SUBNET_IP="172.24.5.250"
# Oslo Policy enforcement for Scope and new policy enforcement.
IRONIC_ENFORCE_SCOPE=$(trueorfalse True IRONIC_ENFORCE_SCOPE)
if [[ "$IRONIC_ENFORCE_SCOPE" == "True" ]]; then
IRONIC_OS_CLOUD=devstack-system-admin
else
IRONIC_OS_CLOUD=devstack-admin
fi
# NOTE(TheJulia): Project scoped admin as of Yoga cycle
# the core devstack code doesn't assert any environment variables
# upfront except for OS_CLOUD, which is defaulted to devstack-admin
# in the last keystone step which executes after ironic's base setup
# occurs. We navigate this just fine for yoga with fixes, however
# we need this variable for Xena and possibly Wallaby grenade.
OS_CLOUD=${OS_CLOUD:-devstack-admin}
# If the node owner needs to be set on nodes, for example, jobs
# such as metalsmith have hardcoded OS_CLOUD variables which mean
# an ironic in a full system scope enforced mode can't find nodes
# for an allocation as suddenly the owner is applied.
IRONIC_SET_NODE_OWNER=${IRONIC_SET_NODE_OWNER:-}
# Versions and command line for API client
IRONIC_DEFAULT_API_VERSION=${IRONIC_DEFAULT_API_VERSION:-}
IRONIC_CMD="openstack --os-cloud $IRONIC_OS_CLOUD baremetal"
if [[ -n "$IRONIC_DEFAULT_API_VERSION" ]]; then
IRONIC_CMD="$IRONIC_CMD --os-baremetal-api-version $IRONIC_DEFAULT_API_VERSION"
fi
# Settings!
IRONIC_ENABLED_HARDWARE_TYPES=${IRONIC_ENABLED_HARDWARE_TYPES:-"ipmi,fake-hardware"}
# list of all available driver interfaces types
IRONIC_DRIVER_INTERFACE_TYPES="bios boot power management deploy console inspect raid rescue storage network vendor"
IRONIC_ENABLED_BIOS_INTERFACES=${IRONIC_ENABLED_BIOS_INTERFACES:-"fake,no-bios"}
IRONIC_ENABLED_BOOT_INTERFACES=${IRONIC_ENABLED_BOOT_INTERFACES:-"fake,ipxe"}
IRONIC_ENABLED_CONSOLE_INTERFACES=${IRONIC_ENABLED_CONSOLE_INTERFACES:-"fake,no-console"}
IRONIC_ENABLED_DEPLOY_INTERFACES=${IRONIC_ENABLED_DEPLOY_INTERFACES:-"fake,direct,ramdisk"}
IRONIC_ENABLED_INSPECT_INTERFACES=${IRONIC_ENABLED_INSPECT_INTERFACES:-"fake,agent,no-inspect"}
IRONIC_ENABLED_MANAGEMENT_INTERFACES=${IRONIC_ENABLED_MANAGEMENT_INTERFACES:-""}
IRONIC_ENABLED_NETWORK_INTERFACES=${IRONIC_ENABLED_NETWORK_INTERFACES:-"flat,noop"}
IRONIC_ENABLED_POWER_INTERFACES=${IRONIC_ENABLED_POWER_INTERFACES:-""}
IRONIC_ENABLED_RAID_INTERFACES=${IRONIC_ENABLED_RAID_INTERFACES:-"fake,agent,no-raid"}
IRONIC_ENABLED_RESCUE_INTERFACES=${IRONIC_ENABLED_RESCUE_INTERFACES:-"fake,no-rescue"}
IRONIC_ENABLED_STORAGE_INTERFACES=${IRONIC_ENABLED_STORAGE_INTERFACES:-"fake,cinder,noop"}
IRONIC_ENABLED_VENDOR_INTERFACES=${IRONIC_ENABLED_VENDOR_INTERFACES:-"fake,ipmitool,no-vendor"}
# for usage with hardware types
IRONIC_DEFAULT_BIOS_INTERFACE=${IRONIC_DEFAULT_BIOS_INTERFACE:-}
IRONIC_DEFAULT_BOOT_INTERFACE=${IRONIC_DEFAULT_BOOT_INTERFACE:-}
IRONIC_DEFAULT_CONSOLE_INTERFACE=${IRONIC_DEFAULT_CONSOLE_INTERFACE:-}
IRONIC_DEFAULT_DEPLOY_INTERFACE=${IRONIC_DEFAULT_DEPLOY_INTERFACE:-}
IRONIC_DEFAULT_INSPECT_INTERFACE=${IRONIC_DEFAULT_INSPECT_INTERFACE:-}
IRONIC_DEFAULT_MANAGEMENT_INTERFACE=${IRONIC_DEFAULT_MANAGEMENT_INTERFACE:-}
IRONIC_DEFAULT_NETWORK_INTERFACE=${IRONIC_DEFAULT_NETWORK_INTERFACE:-}
IRONIC_DEFAULT_POWER_INTERFACE=${IRONIC_DEFAULT_POWER_INTERFACE:-}
IRONIC_DEFAULT_RAID_INTERFACE=${IRONIC_DEFAULT_RAID_INTERFACE:-}
IRONIC_DEFAULT_RESCUE_INTERFACE=${IRONIC_DEFAULT_RESCUE_INTERFACE:-}
IRONIC_DEFAULT_STORAGE_INTERFACE=${IRONIC_DEFAULT_STORAGE_INTERFACE:-}
IRONIC_DEFAULT_VENDOR_INTERFACE=${IRONIC_DEFAULT_VENDOR_INTERFACE:-}
IRONIC_UNMANAGED_INSPECTION=$(trueorfalse False IRONIC_UNMANAGED_INSPECTION)
# If IRONIC_VM_ENGINE is explicitly set to "auto" or "kvm",
# devstack will attempt to use hardware virtualization
# (aka nested kvm). We do not enable it in the infra gates
# because it is not consistently supported/working across
# all gate infrastructure providers.
if [[ "$IRONIC_VM_ENGINE" == "auto" ]]; then
sudo modprobe kvm || true
if [ ! -e /dev/kvm ]; then
echo "WARNING: Switching to QEMU"
IRONIC_VM_ENGINE=qemu
else
IRONIC_VM_ENGINE=kvm
fi
fi
if [[ "$IRONIC_VM_ENGINE" == "kvm" ]]; then
# Set this to empty, so configure-vm.py can autodetect location
# of KVM binary
IRONIC_VM_EMULATOR=""
fi
# By default, baremetal VMs will console output to file.
IRONIC_VM_LOG_CONSOLE=$(trueorfalse True IRONIC_VM_LOG_CONSOLE)
IRONIC_VM_LOG_DIR=${IRONIC_VM_LOG_DIR:-$IRONIC_DATA_DIR/logs/}
IRONIC_VM_LOG_ROTATE=$(trueorfalse True IRONIC_VM_LOG_ROTATE)
# Set resource_classes for nodes to use Nova's placement engine
IRONIC_DEFAULT_RESOURCE_CLASS=${IRONIC_DEFAULT_RESOURCE_CLASS:-baremetal}
# Set traits for nodes. Traits should be separated by whitespace.
IRONIC_DEFAULT_TRAITS=${IRONIC_DEFAULT_TRAITS:-CUSTOM_GOLD}
# Whether to build the ramdisk or download a prebuilt one.
IRONIC_BUILD_DEPLOY_RAMDISK=$(trueorfalse True IRONIC_BUILD_DEPLOY_RAMDISK)
# Ironic IPA ramdisk type, supported types are:
IRONIC_SUPPORTED_RAMDISK_TYPES_RE="^(tinyipa|dib)$"
IRONIC_RAMDISK_TYPE=${IRONIC_RAMDISK_TYPE:-dib}
# Confirm we have a supported ramdisk type or fail early.
if [[ ! "$IRONIC_RAMDISK_TYPE" =~ $IRONIC_SUPPORTED_RAMDISK_TYPES_RE ]]; then
die $LINENO "Unrecognized IRONIC_RAMDISK_TYPE: $IRONIC_RAMDISK_TYPE. Expected 'tinyipa' or 'dib'"
fi
# Which deploy driver to use - valid choices right now
# are ``ipmi``, ``snmp`` and ``redfish``.
#
# Additional valid choices if IRONIC_IS_HARDWARE == true are:
# ``idrac`` and ``irmc``.
IRONIC_DEPLOY_DRIVER=${IRONIC_DEPLOY_DRIVER:-ipmi}
# If present, these files are used as deploy ramdisk/kernel.
# (The value must be an absolute path)
IRONIC_DEPLOY_RAMDISK=${IRONIC_DEPLOY_RAMDISK:-$TOP_DIR/files/ir-deploy-$IRONIC_DEPLOY_DRIVER.initramfs}
IRONIC_DEPLOY_KERNEL=${IRONIC_DEPLOY_KERNEL:-$TOP_DIR/files/ir-deploy-$IRONIC_DEPLOY_DRIVER.kernel}
IRONIC_DEPLOY_ISO=${IRONIC_DEPLOY_ISO:-$TOP_DIR/files/ir-deploy-$IRONIC_DEPLOY_DRIVER.iso}
# If present, this file is used to deploy/boot nodes over virtual media
# (The value must be an absolute path)
IRONIC_EFIBOOT=${IRONIC_EFIBOOT:-$TOP_DIR/files/ir-deploy-$IRONIC_DEPLOY_DRIVER.efiboot}
# NOTE(jroll) this needs to be updated when stable branches are cut
IPA_DOWNLOAD_BRANCH=${IPA_DOWNLOAD_BRANCH:-${TARGET_BRANCH:-master}}
IPA_DOWNLOAD_BRANCH=$(echo $IPA_DOWNLOAD_BRANCH | tr / -)
IPA_DOWNLOAD_SOURCE=ironic-python-agent
if [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
IPA_DOWNLOAD_SOURCE=ironic-python-agent-builder
fi
# OS for using with DIB images
IRONIC_DIB_RAMDISK_OS=${IRONIC_DIB_RAMDISK_OS:-centos9}
IRONIC_DIB_RAMDISK_RELEASE=${IRONIC_DIB_RAMDISK_RELEASE:-}
# OS for building images in CI
IRONIC_DIB_OS=${IRONIC_DIB_RAMDISK_OS:-centos9}
IRONIC_DIB_RELEASE=${IRONIC_DIB_RAMDISK_RELEASE:-}
# Configure URLs required to download ramdisk if we're not building it, and
# IRONIC_DEPLOY_RAMDISK/KERNEL or the RAMDISK/KERNEL_URLs have not been
# preconfigured.
if [[ "$IRONIC_BUILD_DEPLOY_RAMDISK" == "False" && \
! (-e "$IRONIC_DEPLOY_RAMDISK" && -e "$IRONIC_DEPLOY_KERNEL") && \
(-z "$IRONIC_AGENT_KERNEL_URL" || -z "$IRONIC_AGENT_RAMDISK_URL") ]]; then
case $IRONIC_RAMDISK_TYPE in
tinyipa)
IRONIC_AGENT_KERNEL_FILE=tinyipa-${IPA_DOWNLOAD_BRANCH}.vmlinuz
IRONIC_AGENT_RAMDISK_FILE=tinyipa-${IPA_DOWNLOAD_BRANCH}.gz
;;
dib)
IRONIC_AGENT_KERNEL_FILE=ipa-${IRONIC_DIB_RAMDISK_OS}-${IPA_DOWNLOAD_BRANCH}.kernel
IRONIC_AGENT_RAMDISK_FILE=ipa-${IRONIC_DIB_RAMDISK_OS}-${IPA_DOWNLOAD_BRANCH}.initramfs
;;
esac
IRONIC_AGENT_KERNEL_URL=https://tarballs.openstack.org/${IPA_DOWNLOAD_SOURCE}/${IRONIC_RAMDISK_TYPE}/files/${IRONIC_AGENT_KERNEL_FILE}
IRONIC_AGENT_RAMDISK_URL=https://tarballs.openstack.org/${IPA_DOWNLOAD_SOURCE}/${IRONIC_RAMDISK_TYPE}/files/${IRONIC_AGENT_RAMDISK_FILE}
fi
# This refers the options for disk-image-create and the platform on which
# to build the dib based ironic-python-agent ramdisk.
IRONIC_DIB_RAMDISK_OPTIONS=${IRONIC_DIB_RAMDISK_OPTIONS:-}
if [[ -z "$IRONIC_DIB_RAMDISK_OPTIONS" ]]; then
# Adapt for DIB naming change
if [[ "$IRONIC_DIB_RAMDISK_OS" == "centos8" ]]; then
IRONIC_DIB_RAMDISK_OS=centos
IRONIC_DIB_RAMDISK_RELEASE=8-stream
fi
if [[ "$IRONIC_DIB_RAMDISK_OS" == "centos9" ]]; then
IRONIC_DIB_RAMDISK_OS=centos
IRONIC_DIB_RAMDISK_RELEASE=9-stream
fi
IRONIC_DIB_RAMDISK_OPTIONS="$IRONIC_DIB_RAMDISK_OS"
fi
# DHCP timeout for the dhcp-all-interfaces element.
IRONIC_DIB_DHCP_TIMEOUT=${IRONIC_DIB_DHCP_TIMEOUT:-60}
# Options for building a DIB image
IRONIC_BUILD_DIB_IMAGE=${IRONIC_BUILD_DIB_IMAGE:-False}
IRONIC_DIB_IMAGE_OPTIONS=${IRONIC_DIB_IMAGE_OPTIONS:-}
IRONIC_DIB_IMAGE_OS=${IRONIC_DIB_IMAGE_OS:-centos}
IRONIC_DIB_IMAGE_RELEASE=${IRONIC_DIB_IMAGE_RELEASE:-9-stream}
if [[ -z "$IRONIC_DIB_IMAGE_OPTIONS" ]]; then
IRONIC_DIB_IMAGE_OPTIONS="$IRONIC_DIB_IMAGE_OS bootloader block-device-efi-lvm growvols dracut-regenerate"
fi
# Some drivers in Ironic require deploy ramdisk in bootable ISO format.
# Set this variable to "true" to build an ISO for deploy ramdisk and
# upload to Glance.
IRONIC_DEPLOY_ISO_REQUIRED=$(trueorfalse False IRONIC_DEPLOY_ISO_REQUIRED)
if [[ "$IRONIC_DEPLOY_ISO_REQUIRED" = "True" \
&& "$IRONIC_BUILD_DEPLOY_RAMDISK" = "False" \
&& ! -e "$IRONIC_DEPLOY_ISO" ]]; then
die "Prebuilt ISOs are not available, provide an ISO via IRONIC_DEPLOY_ISO \
or set IRONIC_BUILD_DEPLOY_RAMDISK=True to use ISOs"
fi
# If the requested driver is not yet enable, enable it, if it is not it will fail anyway
if [[ -z "$(echo ${IRONIC_ENABLED_HARDWARE_TYPES} | grep -w ${IRONIC_DEPLOY_DRIVER})" ]]; then
die "The deploy driver $IRONIC_DEPLOY_DRIVER is not in the list of enabled \
hardware types $IRONIC_ENABLED_HARDWARE_TYPES"
fi
# Support entry points installation of console scripts
IRONIC_BIN_DIR=$(get_python_exec_prefix)
IRONIC_UWSGI_CONF=$IRONIC_CONF_DIR/ironic-uwsgi.ini
IRONIC_UWSGI=${IRONIC_UWSGI:-ironic.wsgi:application}
# Lets support IPv6 testing!
IRONIC_IP_VERSION=${IRONIC_IP_VERSION:-${IP_VERSION:-4}}
IRONIC_IPV6_ADDRESS_MODE=${IRONIC_IPV6_ADDRESS_MODE:-dhcpv6-stateless}
# Ironic connection info. Note the port must be specified.
if is_service_enabled tls-proxy; then
IRONIC_SERVICE_PROTOCOL=https
fi
IRONIC_SERVICE_PROTOCOL=${IRONIC_SERVICE_PROTOCOL:-$SERVICE_PROTOCOL}
IRONIC_SERVICE_PORT=${IRONIC_SERVICE_PORT:-6385}
IRONIC_SERVICE_PORT_INT=${IRONIC_SERVICE_PORT_INT:-16385}
IRONIC_NOVNCPROXY_PORT=${IRONIC_NOVNCPROXY_PORT:-6090}
IRONIC_HOSTPORT=${IRONIC_HOSTPORT:-$SERVICE_HOST/baremetal}
# Enable iPXE
IRONIC_IPXE_ENABLED=$(trueorfalse True IRONIC_IPXE_ENABLED)
# Options below are only applied when IRONIC_IPXE_ENABLED is True
IRONIC_IPXE_USE_SWIFT=$(trueorfalse False IRONIC_IPXE_USE_SWIFT)
IRONIC_HTTP_DIR=${IRONIC_HTTP_DIR:-$IRONIC_DATA_DIR/httpboot}
IRONIC_HTTP_PORT=${IRONIC_HTTP_PORT:-3928}
# Allow using JSON RPC instead of oslo.messaging
IRONIC_RPC_TRANSPORT=${IRONIC_RPC_TRANSPORT:-oslo}
IRONIC_JSON_RPC_PORT=${IRONIC_JSON_RPC_PORT:-8089}
# The authentication strategy used by json-rpc. Valid values are:
# keystone, http_basic, noauth, or no value to inherit from ironic-api
# auth strategy.
IRONIC_JSON_RPC_AUTH_STRATEGY=${IRONIC_JSON_RPC_AUTH_STRATEGY:-}
# The first port in the range to bind the Virtual BMCs. The number of
# ports that will be used depends on $IRONIC_VM_COUNT variable, e.g if
# $IRONIC_VM_COUNT=3 the ports 6230, 6231 and 6232 will be used for the
# Virtual BMCs, one for each VM.
IRONIC_VBMC_PORT_RANGE_START=${IRONIC_VBMC_PORT_RANGE_START:-6230}
IRONIC_VBMC_CONFIG_FILE=${IRONIC_VBMC_CONFIG_FILE:-$IRONIC_CONF_DIR/virtualbmc/virtualbmc.conf}
IRONIC_VBMC_LOGFILE=${IRONIC_VBMC_LOGFILE:-$IRONIC_VM_LOG_DIR/virtualbmc.log}
IRONIC_VBMC_SYSTEMD_SERVICE=devstack@virtualbmc.service
# Virtual PDU configs
IRONIC_VPDU_CONFIG_FILE=${IRONIC_VPDU_CONFIG_FILE:-$IRONIC_CONF_DIR/virtualpdu/virtualpdu.conf}
IRONIC_VPDU_PORT_RANGE_START=${IRONIC_VPDU_PORT_RANGE_START:-1}
IRONIC_VPDU_LISTEN_PORT=${IRONIC_VPDU_LISTEN_PORT:-1161}
IRONIC_VPDU_COMMUNITY=${IRONIC_VPDU_COMMUNITY:-private}
IRONIC_VPDU_SNMPDRIVER=${IRONIC_VPDU_SNMPDRIVER:-apc_rackpdu}
IRONIC_VPDU_SYSTEMD_SERVICE=devstack@virtualpdu.service
# Redfish configs
IRONIC_REDFISH_EMULATOR_PORT=${IRONIC_REDFISH_EMULATOR_PORT:-9132}
IRONIC_REDFISH_EMULATOR_SYSTEMD_SERVICE="devstack@redfish-emulator.service"
IRONIC_REDFISH_EMULATOR_CONFIG=${IRONIC_REDFISH_EMULATOR_CONFIG:-$IRONIC_CONF_DIR/redfish/emulator.conf}
IRONIC_REDFISH_EMULATOR_FEATURE_SET=${IRONIC_REDFISH_EMULATOR_FEATURE_SET:-full}
# To explicitly enable configuration of Glance with Swift
# (which is required by some vendor drivers), set this
# variable to true.
IRONIC_CONFIGURE_GLANCE_WITH_SWIFT=$(trueorfalse False IRONIC_CONFIGURE_GLANCE_WITH_SWIFT)
# The path to the libvirt hooks directory, used if IRONIC_VM_LOG_ROTATE is True
IRONIC_LIBVIRT_HOOKS_PATH=${IRONIC_LIBVIRT_HOOKS_PATH:-/etc/libvirt/hooks/}
LIBVIRT_STORAGE_POOL=${LIBVIRT_STORAGE_POOL:-"default"}
LIBVIRT_STORAGE_POOL_PATH=${LIBVIRT_STORAGE_POOL_PATH:-/var/lib/libvirt/images}
# The authentication strategy used by ironic-api. Valid values are:
# keystone, http_basic, noauth.
IRONIC_AUTH_STRATEGY=${IRONIC_AUTH_STRATEGY:-keystone}
# By default, terminal SSL certificate is disabled.
IRONIC_TERMINAL_SSL=$(trueorfalse False IRONIC_TERMINAL_SSL)
IRONIC_TERMINAL_CERT_DIR=${IRONIC_TERMINAL_CERT_DIR:-$IRONIC_DATA_DIR/terminal_cert/}
# This flag is used to allow adding Link-Local-Connection info
# to ironic port-create command. LLC info is obtained from
# IRONIC_{VM,HW}_NODES_FILE
IRONIC_USE_LINK_LOCAL=$(trueorfalse False IRONIC_USE_LINK_LOCAL)
# Allow selecting dhcp provider
IRONIC_DHCP_PROVIDER=${IRONIC_DHCP_PROVIDER:-neutron}
# This is the network interface to use for a node
IRONIC_NETWORK_INTERFACE=${IRONIC_NETWORK_INTERFACE:-}
# Ironic provision network name, if this value is set it means we are using
# multi-tenant networking. If not set, then we are not using multi-tenant
# networking and are therefore using a 'flat' network.
IRONIC_PROVISION_NETWORK_NAME=${IRONIC_PROVISION_NETWORK_NAME:-}
# Provision network provider type. Can be flat or vlan.
# This is only used if IRONIC_PROVISION_NETWORK_NAME has been set.
IRONIC_PROVISION_PROVIDER_NETWORK_TYPE=${IRONIC_PROVISION_PROVIDER_NETWORK_TYPE:-'vlan'}
# If IRONIC_PROVISION_PROVIDER_NETWORK_TYPE is vlan. VLAN_ID may be specified. If it is not set,
# vlan will be allocated dynamically.
# This is only used if IRONIC_PROVISION_NETWORK_NAME has been set.
IRONIC_PROVISION_SEGMENTATION_ID=${IRONIC_PROVISION_SEGMENTATION_ID:-}
# NOTE(TheJulia): Some silly logic to help make multinode work because conductors
# refer to themselves and in this case we're running multiple conductors.
SUBNODE_FINAL="2"
# For nodes, there is a relative order, so when we have a subnode, we use
# this value to try and track which would be appropriate.
SUBNODE_ID=""
if [ -f /etc/devstack-host ]; then
# NOTE(TheJulia): this needs to match the Zuul Nodeset configuration
# of hostnames, and should then be mapped out with as many nodes
# required.
if $(grep -qi compute0 /etc/devstack-host); then
SUBNODE_FINAL="2"
SUBNODE_ID="1"
elif $(grep -qi compute1 /etc/devstack-host); then
SUBNODE_FINAL="3"
SUBNODE_ID="2"
fi
fi
if [[ "$IRONIC_IP_VERSION" != '6' ]]; then
# NOTE(TheJulia): Lets not try and support mixed mode since the conductor
# can't support mixed mode operation. We are either IPv4 OR IPv6.
IRONIC_IP_VERSION='4'
# Allocation network pool for provision network
# Example: IRONIC_PROVISION_ALLOCATION_POOL=start=10.0.5.10,end=10.0.5.100
# This is only used if IRONIC_PROVISION_NETWORK_NAME has been set.
IRONIC_PROVISION_ALLOCATION_POOL=${IRONIC_PROVISION_ALLOCATION_POOL:-'start=10.0.5.10,end=10.0.5.100'}
# With multinode case all ironic-conductors should have IP from provisioning network.
# IRONIC_PROVISION_SUBNET_GATEWAY - is configured on primary node.
# Ironic provision subnet gateway.
IRONIC_PROVISION_SUBNET_GATEWAY=${IRONIC_PROVISION_SUBNET_GATEWAY:-'10.0.5.1'}
IRONIC_PROVISION_SUBNET_SUBNODE_IP=${IRONIC_PROVISION_SUBNET_SUBNODE_IP:-'10.0.5.'$SUBNODE_FINAL}
# Ironic provision subnet prefix
# Example: IRONIC_PROVISION_SUBNET_PREFIX=10.0.5.0/24
IRONIC_PROVISION_SUBNET_PREFIX=${IRONIC_PROVISION_SUBNET_PREFIX:-'10.0.5.0/24'}
else
IRONIC_IP_VERSION='6'
# We setup an address elsewhere because the service address of
# HOST_IPV6 cannot be used for v6 testing.
IRONIC_HOST_IPV6='fc00::1'
IRONIC_PROVISION_SUBNET_GATEWAY=${IRONIC_PROVISION_SUBNET_GATEWAY:-'fc01::1'}
IRONIC_PROVISION_SUBNET_SUBNODE_IP=${IRONIC_PROVISION_SUBNET_SUBNODE_IP:-'fc01::'$SUBNODE_FINAL}
IRONIC_PROVISION_SUBNET_PREFIX=${IRONIC_PROVISION_SUBNET_PREFIX:-'fc01::/64'}
IRONIC_TFTPSERVER_IP=$IRONIC_HOST_IPV6
fi
IRONIC_ROUTER_NAME=${Q_ROUTER_NAME:-router1}
# Ironic provision subnet name.
# This is only used if IRONIC_PROVISION_NETWORK_NAME has been set.
IRONIC_PROVISION_PROVIDER_SUBNET_NAME=${IRONIC_PROVISION_PROVIDER_SUBNET_NAME:-${IRONIC_PROVISION_NETWORK_NAME}-subnet}
# When enabled this will set the physical_network attribute for ironic ports
# and subnet-to-segment association on provisioning network will be configured.
# NOTE: The neutron segments service_plugin must be loaded for this.
IRONIC_USE_NEUTRON_SEGMENTS=$(trueorfalse False IRONIC_USE_NEUTRON_SEGMENTS)
# This is the storage interface to use for a node
# Only 'cinder' can be set for testing boot from volume
IRONIC_STORAGE_INTERFACE=${IRONIC_STORAGE_INTERFACE:-}
# With multinode case all ironic-conductors should have IP from provisioning network.
# IRONIC_PROVISION_SUBNET_GATEWAY - is configured on primary node.
# Ironic provision subnet gateway.
IRONIC_PROVISION_SUBNET_GATEWAY=${IRONIC_PROVISION_SUBNET_GATEWAY:-'10.0.5.1'}
IRONIC_PROVISION_SUBNET_SUBNODE_IP=${IRONIC_PROVISION_SUBNET_SUBNODE_IP:-'10.0.5.2'}
# Ironic provision subnet prefix
# Example: IRONIC_PROVISION_SUBNET_PREFIX=10.0.5.0/24
IRONIC_PROVISION_SUBNET_PREFIX=${IRONIC_PROVISION_SUBNET_PREFIX:-'10.0.5.0/24'}
if [[ "$HOST_TOPOLOGY_ROLE" == "primary" ]]; then
# Some CI jobs get triggered without a HOST_TOPOLOGY_ROLE
# If so, none of this logic is, or needs to be executed.
IRONIC_TFTPSERVER_IP=$IRONIC_PROVISION_SUBNET_GATEWAY
IRONIC_HTTP_SERVER=$IRONIC_PROVISION_SUBNET_GATEWAY
fi
if [[ "$HOST_TOPOLOGY_ROLE" == "subnode" ]]; then
IRONIC_TFTPSERVER_IP=$IRONIC_PROVISION_SUBNET_SUBNODE_IP
IRONIC_HTTP_SERVER=$IRONIC_PROVISION_SUBNET_SUBNODE_IP
fi
# NOTE(TheJulia): Last catch for this being set or not.
# should only work for v4.
IRONIC_HTTP_SERVER=${IRONIC_HTTP_SERVER:-$IRONIC_TFTPSERVER_IP}
# Port that must be permitted for iSCSI connections to be
# established from the tenant network.
ISCSI_SERVICE_PORT=${ISCSI_SERVICE_PORT:-3260}
# Retrieving logs from the deploy ramdisk
#
# IRONIC_DEPLOY_LOGS_COLLECT possible values are:
# * always: Collect the ramdisk logs from the deployment on success or
# failure (Default in DevStack for debugging purpose).
# * on_failure: Collect the ramdisk logs upon a deployment failure
# (Default in Ironic).
# * never: Never collect the ramdisk logs.
IRONIC_DEPLOY_LOGS_COLLECT=${IRONIC_DEPLOY_LOGS_COLLECT:-always}
# IRONIC_DEPLOY_LOGS_STORAGE_BACKEND possible values are:
# * local: To store the logs in the local filesystem (Default in Ironic and DevStack).
# * swift: To store the logs in Swift.
IRONIC_DEPLOY_LOGS_STORAGE_BACKEND=${IRONIC_DEPLOY_LOGS_STORAGE_BACKEND:-local}
# The path to the directory where Ironic should put the logs when IRONIC_DEPLOY_LOGS_STORAGE_BACKEND is set to "local"
IRONIC_DEPLOY_LOGS_LOCAL_PATH=${IRONIC_DEPLOY_LOGS_LOCAL_PATH:-$IRONIC_VM_LOG_DIR/deploy_logs}
# Fast track option
IRONIC_DEPLOY_FAST_TRACK=${IRONIC_DEPLOY_FAST_TRACK:-False}
# Define baremetal min_microversion in tempest config. Default value None is picked from tempest.
TEMPEST_BAREMETAL_MIN_MICROVERSION=${TEMPEST_BAREMETAL_MIN_MICROVERSION:-}
# Define baremetal max_microversion in tempest config. No default value means that it is picked from tempest.
TEMPEST_BAREMETAL_MAX_MICROVERSION=${TEMPEST_BAREMETAL_MAX_MICROVERSION:-}
# TODO(TheJulia): This PHYSICAL_NETWORK needs to be refactored in
# our devstack plugin. It is used by the neutron integration,
# however they want to name the new variable for the current neutron
# plugin NEUTRON_PHYSICAL_NETWORK. For now we'll do some magic and
# change it later once we migrate our jobs.
PHYSICAL_NETWORK=${NEUTRON_PHYSICAL_NETWORK:-${PHYSICAL_NETWORK:-}}
# Ramdisk ISO image for Ramdisk Virtual Media/iPXE testing
# Originally http://tinycorelinux.net/13.x/x86/archive/13.0/Core-13.0.iso
# However sometimes this is unreachable.
# NOTE(TheJulia): As of May 19th, 2023, mirrors.dotsrc.org was
# accessible via ipv4 and ipv6, it appears to be the only v6
# accessible tinycore mirror..
# NOTE(TheJulia): The TinyCore Image *is* only compatible with
# BIOS booting. iPXE loaders with UEFI mode will ERROR pointing
# to https://ipxe.org/err/7f22208e.
IRONIC_RAMDISK_IMAGE=${IRONIC_RAMDISK_IMAGE:-http://mirrors.dotsrc.org/tinycorelinux/14.x/x86/release/Core-current.iso}
IRONIC_LOADER_PATHS=${IRONIC_LOADER_PATHS:-}
# update_loader_copy_paths() - Appends to the loader paths for automatic
# file copy in by Ironic upon startup.
function update_loader_copy_paths {
if [[ -n $IRONIC_LOADER_PATHS ]]; then
IRONIC_LOADER_PATHS="$IRONIC_LOADER_PATHS,$1"
else
IRONIC_LOADER_PATHS=$1
fi
}
# get_pxe_boot_file() - Get the PXE boot file path
function get_pxe_boot_file {
local pxe_boot_file
# Standard PXE
if is_ubuntu; then
# Ubuntu Xenial (16.04) places the file under /usr/lib/PXELINUX
pxe_paths="/usr/lib/syslinux/pxelinux.0 /usr/lib/PXELINUX/pxelinux.0"
for p in $pxe_paths; do
if [[ -f $p ]]; then
pxe_boot_file=$p
fi
done
elif is_fedora || is_suse; then
pxe_boot_file=/usr/share/syslinux/pxelinux.0
fi
echo $pxe_boot_file
}
# PXE boot image - Deprecated
IRONIC_PXE_BOOT_IMAGE=${IRONIC_PXE_BOOT_IMAGE:-$(get_pxe_boot_file)}
IRONIC_AUTOMATED_CLEAN_ENABLED=$(trueorfalse True IRONIC_AUTOMATED_CLEAN_ENABLED)
IRONIC_SECURE_BOOT=${IRONIC_SECURE_BOOT:-False}
IRONIC_UEFI_BOOT_LOADER=${IRONIC_UEFI_BOOT_LOADER:-grub2}
IRONIC_GRUB2_SHIM_FILE=${IRONIC_GRUB2_SHIM_FILE:-}
IRONIC_GRUB2_FILE=${IRONIC_GRUB2_FILE:-}
IRONIC_GRUB2_NETWORK_FILE=${IRONIC_GRUB2_NETWORK_FILE:-}
IRONIC_UEFI_FILES_DIR=${IRONIC_UEFI_FILES_DIR:-/var/lib/libvirt/images}
UEFI_LOADER_PATH=$IRONIC_UEFI_FILES_DIR/OVMF_CODE_4M.fd
UEFI_NVRAM_PATH=$IRONIC_UEFI_FILES_DIR/OVMF_VARS_4M.fd
if [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
UEFI_LOADER_PATH=$IRONIC_UEFI_FILES_DIR/AAVMF_CODE.fd
UEFI_NVRAM_PATH=$IRONIC_UEFI_FILES_DIR/AAVMF_VARS.fd
fi
# Handle architecture specific package installs
install_package shim shim-signed
if [[ $IRONIC_HW_ARCH == "x86_64" ]]; then
if is_ubuntu; then
install_package grub-efi-amd64-signed
elif is_fedora; then
install_package grub2 grub2-efi
fi
elif [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
if is_ubuntu; then
install_package qemu-system-arm qemu-efi-aarch64
elif is_fedora; then
install_package grub2-arm64-efi
fi
fi
# Sanity checks
if [[ "$IRONIC_BOOT_MODE" == "uefi" ]]; then
if [[ "$IRONIC_IPXE_ENABLED" == "False" ]] && [[ "$IRONIC_UEFI_BOOT_LOADER" != "grub2" ]]; then
die $LINENO "Boot mode UEFI is only supported with iPXE and grub2 bootloaders."
fi
if ! is_fedora && ! is_ubuntu; then
die $LINENO "Boot mode UEFI only works in Ubuntu or Fedora for now."
fi
if is_ubuntu; then
if [[ $IRONIC_HW_ARCH == "x86_64" ]]; then
if [[ -z $IRONIC_GRUB2_FILE ]]; then
IRONIC_GRUB2_SHIM_FILE=/usr/lib/shim/shimx64.efi.signed
IRONIC_GRUB2_FILE=/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed
IRONIC_GRUB2_NETWORK_FILE=/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed
fi
elif [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
if [[ -z $IRONIC_GRUB2_FILE ]]; then
IRONIC_GRUB2_SHIM_FILE=/usr/lib/shim/shimaa64.efi.signed
IRONIC_GRUB2_FILE=/usr/lib/grub/arm64-efi-signed/grubaa64.efi.signed
IRONIC_GRUB2_NETWORK_FILE=/usr/lib/grub/arm64-efi-signed/grubnetaa64.efi.signed
fi
fi
elif is_fedora && grep -qs "CentOS" /etc/redhat-release; then
if [[ $IRONIC_HW_ARCH == "x86_64" ]]; then
if [[ -z $IRONIC_GRUB2_FILE ]]; then
IRONIC_GRUB2_SHIM_FILE=/boot/efi/EFI/centos/shimx64.efi
IRONIC_GRUB2_FILE=/boot/efi/EFI/centos/grubx64.efi
IRONIC_GRUB2_NETWORK_FILE=/boot/efi/EFI/centos/grubx64.efi
fi
elif [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
if [[ -z $IRONIC_GRUB2_FILE ]]; then
IRONIC_GRUB2_SHIM_FILE=/boot/efi/EFI/centos/shimaa64.efi
IRONIC_GRUB2_FILE=/boot/efi/EFI/centos/grubaa64.efi
IRONIC_GRUB2_NETWORK_FILE=/boot/efi/EFI/centos/grubaa64.efi
fi
fi
elif is_fedora; then
if [[ $IRONIC_HW_ARCH == "x86_64" ]]; then
if [[ -z $IRONIC_GRUB2_FILE ]]; then
IRONIC_GRUB2_SHIM_FILE=/boot/efi/EFI/fedora/shimx64.efi
IRONIC_GRUB2_FILE=/boot/efi/EFI/fedora/grubx64.efi
IRONIC_GRUB2_NETWORK_FILE=/boot/efi/EFI/fedora/grubx64.efi
fi
elif [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
if [[ -z $IRONIC_GRUB2_FILE ]]; then
IRONIC_GRUB2_SHIM_FILE=/boot/efi/EFI/fedora/shimaa64.efi
IRONIC_GRUB2_FILE=/boot/efi/EFI/fedora/grubaa64.efi
IRONIC_GRUB2_NETWORK_FILE=/boot/efi/EFI/fedora/grubaa64.efi
fi
fi
fi
if [[ "$IRONIC_IPXE_ENABLED" == "False" ]]; then
# NOTE(TheJulia): While we no longer directly copy the
# IRONIC_GRUB2_FILE, we still check the existence as
# without the bootloader package we would be unable to build
# the netboot core image.
if [[ -z $IRONIC_GRUB2_SHIM_FILE ]] || [[ -z $IRONIC_GRUB2_FILE ]] || [[ ! -f $IRONIC_GRUB2_SHIM_FILE ]] || [[ ! -f $IRONIC_GRUB2_FILE ]]; then
die $LINENO "Grub2 Bootloader and Shim file missing."
fi
fi
# NOTE(tkajinam) Use local mirror in CI
if [ -f /etc/ci/mirror_info.sh ]; then
source /etc/ci/mirror_info.sh
CENTOS_MIRROR_HOST="http://${NODEPOOL_MIRROR_HOST}/centos-stream"
IRONIC_GRUB2_SHIM_FILE=$(echo $IRONIC_GRUB2_SHIM_FILE | sed "s|https://mirror.stream.centos.org|${CENTOS_MIRROR_HOST}|g")
IRONIC_GRUB2_FILE=$(echo $IRONIC_GRUB2_FILE | sed "s|https://mirror.stream.centos.org|${CENTOS_MIRROR_HOST}|g")
fi
fi
if [[ "$IRONIC_SHARDS" != "0" ]] && [[ "$IRONIC_SHARDS" != "1" ]]; then
die $LINENO "Only support 0 or 1 shards at this time."
fi
# TODO(pas-ha) find a way to (cross-)sign the custom CA bundle used by tls-proxy
# with default iPXE cert - for reference see http://ipxe.org/crypto
if is_service_enabled tls-proxy && [[ "$IRONIC_IPXE_USE_SWIFT" == "True" ]]; then
die $LINENO "Ironic in DevStack does not yet support booting iPXE from HTTPS URLs"
fi
IRONIC_NETWORK_DOWNLOAD_RETRY_MAX=${IRONIC_NETWORK_DOWNLOAD_RETRY_MAX:-5}
IRONIC_NETWORK_DOWNLOAD_RETRY_DELAY=${IRONIC_NETWORK_DOWNLOAD_RETRY_DELAY:-10}
function download_with_retry() {
local source_url=$1
local destination_path=$2
local attempts=1
echo "Downloading $source_url to $destination_path"
while [ $attempts -le $IRONIC_NETWORK_DOWNLOAD_RETRY_MAX ]; do
if wget --timeout=30 --tries=3 -q "$source_url" -O "$destination_path"; then
echo "Successfully downloaded $source_url on attempt $attempts"
return 0
fi
echo "Download attempt $attempts failed for $source_url, retrying in \
$IRONIC_NETWORK_DOWNLOAD_RETRY_DELAY seconds..."
sleep $IRONIC_NETWORK_DOWNLOAD_RETRY_DELAY
attempts=$((attempts + 1))
done
echo "Failed to download $source_url after $IRONIC_NETWORK_DOWNLOAD_RETRY_MAX attempts"
return 1
}
# Timeout for "manage" action. 2 minutes is more than enough.
IRONIC_MANAGE_TIMEOUT=${IRONIC_MANAGE_TIMEOUT:-120}
# Timeout for "provide" action. This involves cleaning.
if [[ -n "$IRONIC_PXE_BOOT_RETRY_TIMEOUT" ]]; then
IRONIC_DEFAULT_CLEANING_TIMEOUT=$(( $IRONIC_PXE_BOOT_RETRY_TIMEOUT * 2 ))
else
IRONIC_DEFAULT_CLEANING_TIMEOUT=1800
fi
IRONIC_CLEANING_TIMEOUT=${IRONIC_CLEANING_TIMEOUT:-$IRONIC_DEFAULT_CLEANING_TIMEOUT}
IRONIC_CLEANING_DELAY=10
IRONIC_CLEANING_ATTEMPTS=$(( $IRONIC_CLEANING_TIMEOUT / $IRONIC_CLEANING_DELAY ))
# Timeout for ironic-neutron-agent to report state before providing nodes.
# The agent reports every 60 seconds, 2 minutes should do.
IRONIC_NEUTRON_AGENT_REPORT_STATE_DELAY=10
IRONIC_NEUTRON_AGENT_REPORT_STATE_TIMEOUT=${IRONIC_NEUTRON_AGENT_REPORT_STATE_TIMEOUT:-120}
IRONIC_NEUTRON_AGENT_REPORT_STATE_ATTEMPTS=$(( $IRONIC_NEUTRON_AGENT_REPORT_STATE_TIMEOUT / IRONIC_NEUTRON_AGENT_REPORT_STATE_DELAY ))
# Username to use by Ansible to access ramdisk,
# to be set as '[ansible]/default_username' option.
# If not set here (default), will be set to 'tc' for TinyIPA ramdisk,
# for other ramdisks it must be either provided here,
# or set manually per-node via ironic API
IRONIC_ANSIBLE_SSH_USER=${IRONIC_ANSIBLE_SSH_USER:-}
# Path to the private SSH key to use by ansible deploy interface
# that will be set as '[ansible]/default_key_file' option in config.
# The public key path is assumed to be ${IRONIC_ANSIBLE_SSH_KEY}.pub
# and will be used when rebuilding the image to include this public key
# in ~/.ssh/authorized_keys of a $IRONIC_ANSIBLE_SSH_USER in the ramdisk.
# Only the TinyIPA ramdisks are currently supported for such rebuild.
# For TinyIPA ramdisks, if the specified file doesn't exist, it will
# be created and will contain a new RSA passwordless key. We assume
# that the directories in the path to this file exist and are
# writable.
# For other ramdisk types, make sure the corresponding public key is baked into
# the ramdisk to be used by DevStack and provide the path to the private key here,
# or set it manually per node via ironic API.
# FIXME(pas-ha) auto-generated keys currently won't work for multi-node
# DevStack deployment, as we do not distribute this generated key to subnodes yet.
IRONIC_ANSIBLE_SSH_KEY=${IRONIC_ANSIBLE_SSH_KEY:-$IRONIC_DATA_DIR/ansible_ssh_key}
if is_service_enabled swift && [[ "$SWIFT_ENABLE_TEMPURLS" == "True" ]]; then
IRONIC_DEFAULT_DOWNLOAD_SOURCE=swift
else
IRONIC_DEFAULT_DOWNLOAD_SOURCE=
fi
IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE=${IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE:-$IRONIC_DEFAULT_DOWNLOAD_SOURCE}
# Functions
# ---------
# UEFI related functions
function get_uefi_loader {
if [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
if is_ubuntu; then
echo /usr/share/AAVMF/AAVMF_CODE.fd
elif is_fedora; then
echo /usr/share/edk2/aarch64/QEMU_EFI.fd
fi
else
if is_ubuntu; then
echo /usr/share/OVMF/OVMF_CODE_4M.fd
elif is_fedora; then
echo /usr/share/edk2/ovmf/OVMF_CODE.fd
fi
fi
}
function get_uefi_nvram {
if [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
if is_ubuntu; then
echo /usr/share/AAVMF/AAVMF_VARS.fd
elif is_fedora; then
echo /usr/share/edk2/aarch64/QEMU_VARS.fd
fi
else
if is_ubuntu; then
echo /usr/share/OVMF/OVMF_VARS_4M.fd
elif is_fedora; then
echo /usr/share/edk2/ovmf/OVMF_VARS.fd
fi
fi
}
# Misc
function restart_libvirt {
local libvirt_service_name="libvirtd"
if is_ubuntu && ! type libvirtd; then
libvirt_service_name="libvirt-bin"
fi
restart_service $libvirt_service_name
}
# Test if any Ironic services are enabled
# is_ironic_enabled
function is_ironic_enabled {
[[ ,${ENABLED_SERVICES} =~ ,"ir-" ]] && return 0
return 1
}
function is_deployed_by_agent {
[[ "$IRONIC_DEFAULT_DEPLOY_INTERFACE" == "direct"
|| "$IRONIC_DEFAULT_DEPLOY_INTERFACE" == "" ]] && return 0
return 1
}
function is_deployed_by_ipmi {
[[ "$IRONIC_DEPLOY_DRIVER" == ipmi ]] && return 0
return 1
}
function is_deployed_by_ilo {
[[ "${IRONIC_DEPLOY_DRIVER}" == ilo ]] && return 0
return 1
}
function is_deployed_by_drac {
[[ "${IRONIC_DEPLOY_DRIVER}" == idrac ]] && return 0
return 1
}
function is_deployed_by_snmp {
[[ "${IRONIC_DEPLOY_DRIVER}" == snmp ]] && return 0
return 1
}
function is_deployed_by_redfish {
[[ "$IRONIC_DEPLOY_DRIVER" == redfish ]] && return 0
return 1
}
function is_deployed_by_irmc {
[[ "$IRONIC_DEPLOY_DRIVER" == irmc ]] && return 0
return 1
}
function is_drac_enabled {
[[ -z "${IRONIC_ENABLED_HARDWARE_TYPES%%*idrac*}" ]] && return 0
return 1
}
function is_ansible_deploy_enabled {
[[ -z "${IRONIC_ENABLED_DEPLOY_INTERFACES%%*ansible*}" ]] && return 0
return 1
}
function is_redfish_enabled {
[[ -z "${IRONIC_ENABLED_HARDWARE_TYPES%%*redfish*}" ]] && return 0
return 1
}
function is_ansible_with_tinyipa {
# NOTE(pas-ha) we support rebuilding the ramdisk to include (generated) SSH keys
# as needed for ansible deploy interface only for TinyIPA ramdisks for now
is_ansible_deploy_enabled && [[ "$IRONIC_RAMDISK_TYPE" == "tinyipa" ]] && return 0
return 1
}
function is_http_server_required {
[[ "$IRONIC_IPXE_ENABLED" == "True" ]] && return 0
is_deployed_by_agent && [[ "$IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE" != "swift" ]] && return 0
return 1
}
function is_glance_configuration_required {
# Always configure if we're asked to
[[ "$IRONIC_CONFIGURE_GLANCE_WITH_SWIFT" == "True" ]] && return 0
# Do not require swift configuration if using image_download_source!=swift
[[ "$IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE" == "swift" ]] || return 1
# Otherwise require for direct and ansible deploy
is_deployed_by_agent || is_ansible_deploy_enabled && return 0
return 1
}
function is_deploy_iso_required {
[[ "$IRONIC_IS_HARDWARE" == "True" && "$IRONIC_DEPLOY_ISO_REQUIRED" == "True" ]] && return 0
return 1
}
# Assert that the redfish hardware type is enabled in case we are using
# the redfish driver
if is_deployed_by_redfish && [[ "$IRONIC_ENABLED_HARDWARE_TYPES" != *"redfish"* ]]; then
die $LINENO "Please make sure that the redfish hardware" \
"type is enabled. Take a look at the " \
"IRONIC_ENABLED_HARDWARE_TYPES configuration option" \
"for DevStack"
fi
# Syslinux >= 5.00 pxelinux.0 binary is not "stand-alone" anymore,
# it depends on some c32 modules to work correctly.
# More info: http://www.syslinux.org/wiki/index.php/Library_modules
function setup_syslinux_modules {
# Ignore it for iPXE, it doesn't repend on syslinux modules
[[ "$IRONIC_IPXE_ENABLED" == "True" ]] && return 0
# Ubuntu Xenial keeps doesn't ship pxelinux.0 as part of syslinux anymore
if is_ubuntu && [[ -d /usr/lib/PXELINUX/ ]]; then
# NOTE(TheJulia): Few distributions package and ship syslinux.efi,
# so this is basically only for bios booting.
cp -aR /usr/lib/syslinux/modules/bios/*.c32 $IRONIC_TFTPBOOT_DIR
else
cp -aR $(dirname $IRONIC_PXE_BOOT_IMAGE)/*.c32 $IRONIC_TFTPBOOT_DIR
fi
}
function start_virtualbmc {
start_service $IRONIC_VBMC_SYSTEMD_SERVICE
}
function stop_virtualbmc {
stop_service $IRONIC_VBMC_SYSTEMD_SERVICE
}
function cleanup_virtualbmc {
stop_virtualbmc
disable_service $IRONIC_VBMC_SYSTEMD_SERVICE
local unitfile="$SYSTEMD_DIR/$IRONIC_VBMC_SYSTEMD_SERVICE"
sudo rm -f $unitfile
local vbmc_dir="${HOME}/.vbmc"
if [[ -d "$vbmc_dir" ]]; then
sudo rm -rf "$vbmc_dir"
fi
$SYSTEMCTL daemon-reload
}
function install_virtualbmc {
# Install pyghmi from source, if requested, otherwise it will be
# downloaded as part of the virtualbmc installation
if use_library_from_git "pyghmi"; then
git_clone_by_name "pyghmi"
setup_dev_lib "pyghmi"
fi
if use_library_from_git "virtualbmc"; then
git_clone_by_name "virtualbmc"
setup_dev_lib "virtualbmc"
else
pip_install_gr "virtualbmc"
fi
local cmd
BIN_DIR=$(get_python_exec_prefix)
cmd=$BIN_DIR/vbmcd
cmd+=" --foreground"
write_user_unit_file $IRONIC_VBMC_SYSTEMD_SERVICE "$cmd" "" "$STACK_USER"
local unitfile="$SYSTEMD_DIR/$IRONIC_VBMC_SYSTEMD_SERVICE"
iniset -sudo $unitfile "Service" "Environment" "VIRTUALBMC_CONFIG=$IRONIC_VBMC_CONFIG_FILE"
enable_service $IRONIC_VBMC_SYSTEMD_SERVICE
}
function configure_virtualbmc {
if [[ ! -d $(dirname $IRONIC_VBMC_CONFIG_FILE) ]]; then
mkdir -p $(dirname $IRONIC_VBMC_CONFIG_FILE)
fi
iniset -sudo $IRONIC_VBMC_CONFIG_FILE log debug True
}
function start_virtualpdu {
start_service $IRONIC_VPDU_SYSTEMD_SERVICE
}
function stop_virtualpdu {
stop_service $IRONIC_VPDU_SYSTEMD_SERVICE
}
function cleanup_virtualpdu {
stop_virtualpdu
disable_service $IRONIC_VPDU_SYSTEMD_SERVICE
local unitfile="$SYSTEMD_DIR/$IRONIC_VPDU_SYSTEMD_SERVICE"
sudo rm -f $unitfile
$SYSTEMCTL daemon-reload
}
function install_virtualpdu {
if use_library_from_git "virtualpdu"; then
git_clone_by_name "virtualpdu"
setup_dev_lib "virtualpdu"
else
pip_install "virtualpdu"
fi
local cmd
cmd=$(which virtualpdu)
cmd+=" $IRONIC_VPDU_CONFIG_FILE"
write_user_unit_file $IRONIC_VPDU_SYSTEMD_SERVICE "$cmd" "" "$STACK_USER"
enable_service $IRONIC_VPDU_SYSTEMD_SERVICE
}
function configure_virtualpdu {
mkdir -p $(dirname $IRONIC_VPDU_CONFIG_FILE)
iniset -sudo $IRONIC_VPDU_CONFIG_FILE global debug True
iniset -sudo $IRONIC_VPDU_CONFIG_FILE global libvirt_uri "qemu:///system"
iniset -sudo $IRONIC_VPDU_CONFIG_FILE PDU listen_address ${HOST_IP}
iniset -sudo $IRONIC_VPDU_CONFIG_FILE PDU listen_port ${IRONIC_VPDU_LISTEN_PORT}
iniset -sudo $IRONIC_VPDU_CONFIG_FILE PDU community ${IRONIC_VPDU_COMMUNITY}
iniset -sudo $IRONIC_VPDU_CONFIG_FILE PDU ports $(_generate_pdu_ports)
iniset -sudo $IRONIC_VPDU_CONFIG_FILE PDU outlet_default_state "OFF"
}
# _generate_pdu_ports() - Generates list of port:node_name.
function _generate_pdu_ports {
pdu_port_number=${IRONIC_VPDU_PORT_RANGE_START}
port_config=()
for vm_name in $(_ironic_bm_vm_names); do
port_config+=("${pdu_port_number}:${vm_name}")
pdu_port_number=$(( pdu_port_number + 1 ))
done
echo ${port_config[*]} | tr ' ' ','
}
function start_redfish {
start_service $IRONIC_REDFISH_EMULATOR_SYSTEMD_SERVICE
}
function stop_redfish {
stop_service $IRONIC_REDFISH_EMULATOR_SYSTEMD_SERVICE
}
function cleanup_redfish {
stop_redfish
rm -f $IRONIC_REDFISH_EMULATOR_CONFIG
disable_service $IRONIC_REDFISH_EMULATOR_SYSTEMD_SERVICE
local unitfile="$SYSTEMD_DIR/$IRONIC_REDFISH_EMULATOR_SYSTEMD_SERVICE"
sudo rm -f $unitfile
$SYSTEMCTL daemon-reload
}
function install_redfish {
# TODO(lucasagomes): Use Apache WSGI instead of gunicorn
. /opt/stack/data/venv/bin/activate
if is_fedora; then
install_package python3-gunicorn
else
pip_install_gr "gunicorn"
fi
if use_library_from_git "sushy-tools"; then
git_clone_by_name "sushy-tools"
setup_dev_lib "sushy-tools"
else
# 1.1.0 adds support for feature sets
pip_install "sushy-tools>=1.1.0"
fi
local cmd
cmd=$(which gunicorn)
cmd+=" sushy_tools.emulator.main:app"
cmd+=" --bind ${HOST_IP}:${IRONIC_REDFISH_EMULATOR_PORT}"
cmd+=" --env FLASK_DEBUG=1"
cmd+=" --env SUSHY_EMULATOR_CONFIG=${IRONIC_REDFISH_EMULATOR_CONFIG}"
# NOTE(dtantsur): handling virtual media ISO can take time, so increase
# both concurrency and the worker timeout.
cmd+=" --workers 2 --threads 2 --timeout 90"
# NOTE(dtantsur): log all accesses to stdout
cmd+=" --access-logfile=- --error-logfile=-"
# Exit virtualenv once installation is complete
deactivate
write_user_unit_file $IRONIC_REDFISH_EMULATOR_SYSTEMD_SERVICE "$cmd" "" "$STACK_USER"
# update unit file reference
local unitfile="$SYSTEMD_DIR/$IRONIC_REDFISH_EMULATOR_SYSTEMD_SERVICE"
iniset -sudo $unitfile "Service" "Environment" "PATH=/opt/stack/data/venv/bin:$PATH"
enable_service $IRONIC_REDFISH_EMULATOR_SYSTEMD_SERVICE
}
function configure_redfish {
if [[ ! -d $(dirname $IRONIC_REDFISH_EMULATOR_CONFIG) ]]; then
mkdir -p $(dirname $IRONIC_REDFISH_EMULATOR_CONFIG)
fi
cat - <<EOF > $IRONIC_REDFISH_EMULATOR_CONFIG
SUSHY_EMULATOR_BOOT_LOADER_MAP = {
'UEFI': {
'$IRONIC_HW_ARCH': '$UEFI_LOADER_PATH'
},
'Legacy': {
'$IRONIC_HW_ARCH': None
}
}
SUSHY_EMULATOR_FEATURE_SET = "$IRONIC_REDFISH_EMULATOR_FEATURE_SET"
EOF
}
function setup_sushy {
if use_library_from_git "sushy"; then
git_clone_by_name "sushy"
setup_dev_lib "sushy"
else
pip_install_gr "sushy"
fi
}
# install_ironic() - Install the things!
function install_ironic {
# NOTE(vsaienko) do not check required_services on subnode
if [[ "$HOST_TOPOLOGY_ROLE" != "subnode" ]]; then
# make sure all needed service were enabled
local req_services="key"
if is_service_enabled nova && [[ "$VIRT_DRIVER" == "ironic" ]]; then
req_services+=" nova glance neutron"
fi
for srv in $req_services; do
if ! is_service_enabled "$srv"; then
die $LINENO "$srv should be enabled for Ironic."
fi
done
fi
setup_develop $IRONIC_DIR
install_apache_wsgi
if is_redfish_enabled || is_deployed_by_redfish; then
setup_sushy
fi
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
if is_deployed_by_ipmi; then
install_virtualbmc
fi
if is_deployed_by_snmp; then
install_virtualpdu
fi
if is_deployed_by_redfish; then
install_redfish
fi
fi
if is_drac_enabled; then
pip_install python-dracclient
fi
if is_irmc_enabled; then
pip_install python-scciclient pysnmp
fi
if is_ansible_deploy_enabled; then
pip_install "$(grep '^ansible' $IRONIC_DIR/driver-requirements.txt | awk '{print $1}')"
fi
if is_service_enabled ir-novnc; then
# a websockets/html5 VNC console for bare metal hosts
NOVNC_FROM_PACKAGE=$(trueorfalse False NOVNC_FROM_PACKAGE)
if [ "$NOVNC_FROM_PACKAGE" = "True" ]; then
# Installing novnc on Debian bullseye breaks the global pip
# install. This happens because novnc pulls in distro cryptography
# which will be preferred by distro pip, but if anything has
# installed pyOpenSSL from pypi (keystone) that is not compatible
# with distro cryptography. Fix this by installing
# python3-openssl (pyOpenSSL) from the distro which pip will prefer
# on Debian. Ubuntu has inverse problems so we only do this for
# Debian.
local novnc_packages
novnc_packages="novnc"
GetOSVersion
if [[ "$os_VENDOR" = "Debian" ]] ; then
novnc_packages="$novnc_packages python3-openssl"
fi
NOVNC_WEB_DIR=/usr/share/novnc
install_package $novnc_packages
else
NOVNC_WEB_DIR=$DEST/novnc
git_clone $NOVNC_REPO $NOVNC_WEB_DIR $NOVNC_BRANCH
fi
# podman, systemd-container required by the systemd container provider
# buildah required below to build the VNC container
install_package podman systemd-container buildah
pushd $IRONIC_DIR/tools/vnc-container
buildah bud -f ./Containerfile -t localhost/ironic-vnc-container
popd
fi
}
# install_ironicclient() - Collect sources and prepare
function install_ironicclient {
if use_library_from_git "python-ironicclient"; then
git_clone_by_name "python-ironicclient"
setup_dev_lib "python-ironicclient"
else
# nothing actually "requires" ironicclient, so force instally from pypi
pip_install_gr python-ironicclient
fi
}
# _cleanup_ironic_apache_additions() - Remove uwsgi files, disable and remove apache vhost file
function _cleanup_ironic_apache_additions {
if is_http_server_required; then
sudo rm -rf $IRONIC_HTTP_DIR
# TODO(dtantsur): rename the site, it's also used for direct deploy
disable_apache_site ipxe-ironic
sudo rm -f $(apache_site_config_for ipxe-ironic)
fi
remove_uwsgi_config "$IRONIC_UWSGI_CONF" "$IRONIC_UWSGI"
restart_apache_server
}
# _config_ironic_apache_additions() - Configure ironic IPXE site
function _config_ironic_apache_additions {
local ipxe_apache_conf
ipxe_apache_conf=$(apache_site_config_for ipxe-ironic)
sudo cp $IRONIC_DEVSTACK_FILES_DIR/apache-ipxe-ironic.template $ipxe_apache_conf
sudo sed -e "
s|%PUBLICPORT%|$IRONIC_HTTP_PORT|g;
s|%HTTPROOT%|$IRONIC_HTTP_DIR|g;
s|%APACHELOGDIR%|$APACHE_LOG_DIR|g;
" -i $ipxe_apache_conf
enable_apache_site ipxe-ironic
}
# cleanup_ironic_config_files() - Remove residual cache/config/log files,
# left over from previous runs that would need to clean up.
function cleanup_ironic_config_files {
sudo rm -rf $IRONIC_AUTH_CACHE_DIR $IRONIC_CONF_DIR
sudo rm -rf $IRONIC_VM_LOG_DIR/*
}
function cleanup_switch_bridges {
interfaces=$(ip link |grep swbr|cut -d":" -f2)
for interface in interfaces; do
sudo ip link del dev $interface
done
}
# cleanup_ironic() - Clean everything left from Ironic
function cleanup_ironic {
cleanup_ironic_config_files
# Cleanup additions made to Apache
_cleanup_ironic_apache_additions
cleanup_virtualbmc
cleanup_virtualpdu
cleanup_redfish
cleanup_switch_bridges
# Remove the hook to disable log rotate
sudo rm -rf $IRONIC_LIBVIRT_HOOKS_PATH/qemu
# Force fresh rebuild of local build IPA on next run
if [[ "$IRONIC_BUILD_DEPLOY_RAMDISK" == "True" ]]; then
sudo rm -f $IRONIC_DEPLOY_KERNEL
sudo rm -f $IRONIC_DEPLOY_RAMDISK
sudo rm -f $IRONIC_DEPLOY_ISO
fi
}
# configure_ironic_dirs() - Create all directories required by Ironic and
# associated services.
function configure_ironic_dirs {
sudo install -d -o $STACK_USER $IRONIC_CONF_DIR $STACK_USER $IRONIC_DATA_DIR \
$IRONIC_STATE_PATH $IRONIC_TFTPBOOT_DIR $IRONIC_TFTPBOOT_DIR/pxelinux.cfg
sudo chown -R $STACK_USER:$STACK_USER $IRONIC_TFTPBOOT_DIR
if is_http_server_required; then
sudo install -d -o $STACK_USER -g $STACK_USER $IRONIC_HTTP_DIR
fi
# Deprecated - Remove at some point.
if [ ! -f "$IRONIC_PXE_BOOT_IMAGE" ] && [[ ! -z $IRONIC_LOADER_PATHS ]]; then
die $LINENO "PXE boot file $IRONIC_PXE_BOOT_IMAGE not found."
fi
# Copy PXE binary
# NOTE(mjturek): The PXE binary is x86_64 specific. So it should only be copied when
# deploying to an x86_64 node.
if [[ $IRONIC_HW_ARCH == "x86_64" ]]; then
cp $IRONIC_PXE_BOOT_IMAGE $IRONIC_TFTPBOOT_DIR
setup_syslinux_modules
fi
if [[ -z $IRONIC_LOADER_PATHS ]]; then
# This takes a slightly different model then the legacy
# path of devstack. If file exists, add it to the list.
# NOTE(TheJulia): All of these paths are for booting x86
# machines only, others arches can be used, just few distros
# pre-package such loader files.
if [[ "$IRONIC_BOOT_MODE" == "uefi" ]]; then
if is_ubuntu; then
if [[ $IRONIC_HW_ARCH == "x86_64" ]]; then
# NOTE(TheJulia): This is done separately here as this allows
# the script to have hirtuse/bionic compatibility.
if [[ -f /usr/lib/ipxe/snponly.efi ]]; then
update_loader_copy_paths snponly.efi:/usr/lib/ipxe/snponly.efi
elif [[ -f /usr/lib/ipxe/ipxe.efi ]]; then
update_loader_copy_paths snponly.efi:/usr/lib/ipxe/ipxe.efi
fi
elif [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
# NOTE(cid): Ensure ARM-specific binaries are prepared if they don't already exist
if [[ ! -f /usr/lib/ipxe/arm/snponly.efi ]]; then
sudo mkdir -p /usr/lib/ipxe/arm
sudo wget -O /usr/lib/ipxe/arm/snponly.efi http://boot.ipxe.org/arm64-efi/snponly.efi
fi
if [[ -f /usr/lib/ipxe/arm/snponly.efi ]]; then
update_loader_copy_paths snponly.efi:/usr/lib/ipxe/arm/snponly.efi
elif [[ -f /usr/lib/ipxe/arm/ipxe.efi ]]; then
update_loader_copy_paths snponly.efi:/usr/lib/ipxe/arm/ipxe.efi
fi
fi
fi
if is_fedora; then
if [[ $IRONIC_HW_ARCH == "x86_64" ]]; then
if [[ -f /usr/share/ipxe/ipxe-snponly-x86_64.efi ]]; then
# NOTE(TheJulia): I think this file got renamed at some
# point during its centos8 run, but this is current.
update_loader_copy_paths snponly.efi:/usr/share/ipxe/ipxe-snponly-x86_64.efi
fi
elif [[ $IRONIC_HW_ARCH == "aarch64" ]]; then
if [[ -f /usr/share/ipxe/arm/ipxe-snponly-aarch64.efi ]]; then
update_loader_copy_paths snponly.efi:/usr/share/ipxe/arm/ipxe-snponly-aarch64.efi
fi
fi
fi
if [[ "$IRONIC_HW_ARCH" == "aarch64" ]]; then
if [ -f $IRONIC_GRUB2_SHIM_FILE ]; then
update_loader_copy_paths "bootaa64.efi:$IRONIC_GRUB2_SHIM_FILE"
update_loader_copy_paths "grubaa64.efi:$IRONIC_GRUB2_FILE"
fi
else
if [ -f $IRONIC_GRUB2_SHIM_FILE ]; then
update_loader_copy_paths "bootx64.efi:$IRONIC_GRUB2_SHIM_FILE"
fi
if [ -f $IRONIC_GRUB2_SHIM_FILE ]; then
update_loader_copy_paths "grubx64.efi:$IRONIC_GRUB2_NETWORK_FILE"
fi
fi
else
if [[ -f /usr/lib/ipxe/undionly.kpxe ]]; then
update_loader_copy_paths undionly.kpxe:/usr/lib/ipxe/undionly.kpxe
elif [[ -f /usr/share/ipxe/undionly.kpxe ]]; then
update_loader_copy_paths undionly.kpxe:/usr/share/ipxe/undionly.kpxe
fi
fi
fi # end of IRONIC_LOADER_PATHS check
if [[ "$IRONIC_BOOT_MODE" == "uefi" ]]; then
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
local uefi_loader
local uefi_nvram
# Copy the OVMF images to libvirt's path
uefi_loader=$(get_uefi_loader)
uefi_nvram=$(get_uefi_nvram)
sudo cp $uefi_loader $UEFI_LOADER_PATH
sudo cp $uefi_nvram $UEFI_NVRAM_PATH
fi
fi
# Create the logs directory when saving the deploy logs to the filesystem
if [[ "$IRONIC_DEPLOY_LOGS_STORAGE_BACKEND" == "local" && "$IRONIC_DEPLOY_LOGS_COLLECT" != "never" ]]; then
install -d -o $STACK_USER $IRONIC_DEPLOY_LOGS_LOCAL_PATH
fi
}
function configure_ironic_networks {
if [[ -n "${IRONIC_PROVISION_NETWORK_NAME}" ]]; then
echo_summary "Configuring Ironic provisioning network"
configure_ironic_provision_network
fi
echo_summary "Configuring Ironic cleaning network"
configure_ironic_cleaning_network
echo_summary "Configuring Ironic rescue network"
configure_ironic_rescue_network
echo_summary "Configuring Neutron Private Subnet, if needed."
configure_ironic_private_subnet
echo_summary "Configuring Ironic inspection network"
configure_ironic_inspection_network
}
function configure_ironic_private_subnet {
if [[ "${IRONIC_ANACONDA_IMAGE_REF:-}" != "" ]]; then
# NOTE(TheJulia): Anaconda needs DNS for FQDN resolution
# and devstack doesn't create this network with dns.
subnet_id=$(openstack --os-cloud $OS_CLOUD subnet show private-subnet -f value -c id)
openstack --os-cloud $OS_CLOUD subnet set --dns-nameserver 8.8.8.8 $subnet_id
fi
}
function configure_ironic_cleaning_network {
iniset $IRONIC_CONF_FILE neutron cleaning_network $IRONIC_CLEAN_NET_NAME
}
function configure_ironic_rescue_network {
iniset $IRONIC_CONF_FILE neutron rescuing_network $IRONIC_RESCUE_NET_NAME
}
function configure_ironic_inspection_network {
iniset $IRONIC_CONF_FILE neutron inspection_network $IRONIC_INSPECTION_NET_NAME
}
function configure_ironic_provision_network {
if [[ "$HOST_TOPOLOGY" == "multinode" ]]; then
# This should artificially drop the default before we get any
# further into the overall process, as post neutron setup, before
# the plugin proceeds past neutorn, this gets set. Which is fine, but
# if we're in a multinode topology, we need to pin this down to match.
iniset $NEUTRON_CONF DEFAULT global_physnet_mtu "$PUBLIC_BRIDGE_MTU"
fi
configure_neutron_dhcp_enable_addr6_list
if [[ "$IRONIC_IP_VERSION" == "6" ]]; then
# NOTE(TheJulia): Ideally we should let this happen
# with our global address, but iPXE seems to have in
# consistent behavior in this configuration with devstack.
# so we will setup a dummy interface and use that.
if ! ( ping -c1 -w1 $IRONIC_HOST_IPV6 ); then
# But before we create an interface, lets make sure it is
# not already working.
sudo ip link add magicv6 type dummy
sudo ip link set dev magicv6 up
sudo ip -6 addr add $IRONIC_HOST_IPV6/64 dev magicv6
fi
fi
# This is only called if IRONIC_PROVISION_NETWORK_NAME has been set and
# means we are using multi-tenant networking.
local net_id
local ironic_provision_network_ip
# NOTE(vsaienko) For multinode case there is no need to create a new provisioning
# network on subnode, as it was created on primary node. Just get an existed network UUID.
if [[ "$HOST_TOPOLOGY_ROLE" != "subnode" ]]; then
die_if_not_set $LINENO IRONIC_PROVISION_SUBNET_PREFIX "You must specify the IRONIC_PROVISION_SUBNET_PREFIX"
die_if_not_set $LINENO PHYSICAL_NETWORK "You must specify the PHYSICAL_NETWORK"
die_if_not_set $LINENO IRONIC_PROVISION_SUBNET_GATEWAY "You must specify the IRONIC_PROVISION_SUBNET_GATEWAY"
net_id=$(openstack --os-cloud $OS_CLOUD network create --provider-network-type $IRONIC_PROVISION_PROVIDER_NETWORK_TYPE \
--provider-physical-network "$PHYSICAL_NETWORK" \
${IRONIC_PROVISION_SEGMENTATION_ID:+--provider-segment $IRONIC_PROVISION_SEGMENTATION_ID} \
${IRONIC_PROVISION_NETWORK_NAME} -f value -c id)
die_if_not_set $LINENO net_id "Failure creating net_id for $IRONIC_PROVISION_NETWORK_NAME"
if [[ "${IRONIC_USE_NEUTRON_SEGMENTS}" == "True" ]]; then
local net_segment_id
net_segment_id=$(openstack --os-cloud $OS_CLOUD network segment list --network $net_id -f value -c ID)
die_if_not_set $LINENO net_segment_id "Failure getting net_segment_id for $IRONIC_PROVISION_NETWORK_NAME"
fi
local subnet_id
if [[ "$IRONIC_IP_VERSION" == '4' ]]; then
subnet_id="$(openstack --os-cloud $OS_CLOUD subnet create --ip-version 4 \
${IRONIC_PROVISION_ALLOCATION_POOL:+--allocation-pool $IRONIC_PROVISION_ALLOCATION_POOL} \
${net_segment_id:+--network-segment $net_segment_id} \
$IRONIC_PROVISION_PROVIDER_SUBNET_NAME \
--gateway $IRONIC_PROVISION_SUBNET_GATEWAY --network $net_id \
--subnet-range $IRONIC_PROVISION_SUBNET_PREFIX \
--dns-nameserver 8.8.8.8 -f value -c id)"
else
# NOTE(hjensas) Ensure mtu matches public bridge mtu, to avoid "dropped over-mtu packet" issue on IPv6
if [[ -n "$PUBLIC_BRIDGE_MTU" ]]; then
openstack network set --mtu "$PUBLIC_BRIDGE_MTU" $net_id
fi
subnet_id="$(openstack --os-cloud $OS_CLOUD subnet create --ip-version 6 \
--ipv6-address-mode $IRONIC_IPV6_ADDRESS_MODE \
--ipv6-ra-mode $IRONIC_IPV6_ADDRESS_MODE \
--dns-nameserver 2001:4860:4860::8888 \
${net_segment_id:+--network-segment $net_segment_id} \
$IRONIC_PROVISION_PROVIDER_SUBNET_NAME \
--gateway $IRONIC_PROVISION_SUBNET_GATEWAY --network $net_id \
--subnet-range $IRONIC_PROVISION_SUBNET_PREFIX -f value -c id)"
# NOTE(TheJulia): router must be attached to the subnet for RAs.
openstack --os-cloud $OS_CLOUD router add subnet $IRONIC_ROUTER_NAME $subnet_id
# We're going to be using this router of public access to tenant networks
PUBLIC_ROUTER_ID=$(openstack --os-cloud $OS_CLOUD router show -c id -f value $IRONIC_ROUTER_NAME)
fi
die_if_not_set $LINENO subnet_id "Failure creating SUBNET_ID for $IRONIC_PROVISION_NETWORK_NAME"
ironic_provision_network_ip=$IRONIC_PROVISION_SUBNET_GATEWAY
else
net_id=$(openstack --os-cloud $OS_CLOUD network show $IRONIC_PROVISION_NETWORK_NAME -f value -c id)
ironic_provision_network_ip=$IRONIC_PROVISION_SUBNET_SUBNODE_IP
fi
IRONIC_PROVISION_SEGMENTATION_ID=${IRONIC_PROVISION_SEGMENTATION_ID:-`openstack --os-cloud $OS_CLOUD network show ${net_id} -f value -c provider:segmentation_id`}
provision_net_prefix=${IRONIC_PROVISION_SUBNET_PREFIX##*/}
# Set provision network GW on physical interface
# Add vlan on br interface in case of IRONIC_PROVISION_PROVIDER_NETWORK_TYPE==vlan
# otherwise assign ip to br interface directly.
sudo ip link set dev $OVS_PHYSICAL_BRIDGE up
if [[ "$IRONIC_IP_VERSION" == "4" ]]; then
if [[ "$IRONIC_PROVISION_PROVIDER_NETWORK_TYPE" == "vlan" ]]; then
sudo ip link add link $OVS_PHYSICAL_BRIDGE name $OVS_PHYSICAL_BRIDGE.$IRONIC_PROVISION_SEGMENTATION_ID type vlan id $IRONIC_PROVISION_SEGMENTATION_ID
sudo ip link set dev $OVS_PHYSICAL_BRIDGE.$IRONIC_PROVISION_SEGMENTATION_ID up
sudo ip -$IRONIC_IP_VERSION addr add dev $OVS_PHYSICAL_BRIDGE.$IRONIC_PROVISION_SEGMENTATION_ID $ironic_provision_network_ip/$provision_net_prefix
else
sudo ip -$IRONIC_IP_VERSION addr add dev $OVS_PHYSICAL_BRIDGE $ironic_provision_network_ip/$provision_net_prefix
fi
else
# Turn on the external/integration bridges, for IPV6.
sudo ip link set dev br-ex up
sudo ip link set dev br-int up
sudo ip6tables -I FORWARD -i brbm -j LOG || true
sudo ip6tables -I FORWARD -i br-ex -j LOG || true
fi
iniset $IRONIC_CONF_FILE neutron provisioning_network $IRONIC_PROVISION_NETWORK_NAME
}
function cleanup_ironic_provision_network {
# Cleanup OVS_PHYSICAL_BRIDGE subinterfaces
local bridge_subint
bridge_subint=$(cat /proc/net/dev | sed -n "s/^\(${OVS_PHYSICAL_BRIDGE}\.[0-9]*\).*/\1/p")
for sub_int in $bridge_subint; do
sudo ip link set dev $sub_int down
sudo ip link del dev $sub_int
done
}
function configure_neutron_l3_lower_v6_ra {
iniset $Q_L3_CONF_FILE DEFAULT min_rtr_adv_interval 5
}
function configure_neutron_dhcp_enable_addr6_list {
if is_service_enabled q-dhcp neutron-dhcp; then
iniset $Q_DHCP_CONF_FILE DEFAULT dnsmasq_enable_addr6_list true
restart_service devstack@q-dhcp.service || true
restart_service devstack@neutron-dhcp.service || true
fi
}
# configure_ironic() - Set config files, create data dirs, etc
function configure_ironic {
configure_ironic_dirs
# (re)create ironic configuration file and configure common parameters.
rm -f $IRONIC_CONF_FILE
iniset $IRONIC_CONF_FILE DEFAULT debug True
iniset $IRONIC_CONF_FILE api response_validation error
inicomment $IRONIC_CONF_FILE DEFAULT log_file
iniset $IRONIC_CONF_FILE database connection `database_connection_url ironic`
iniset $IRONIC_CONF_FILE DEFAULT state_path $IRONIC_STATE_PATH
iniset $IRONIC_CONF_FILE DEFAULT use_syslog $SYSLOG
# NOTE(vsaienko) with multinode each conductor should have its own host.
iniset $IRONIC_CONF_FILE DEFAULT host $LOCAL_HOSTNAME
# NOTE(TheJulia) Set a minimum amount of memory that is more in-line with
# OpenStack CI and the images deployed.
iniset $IRONIC_CONF_FILE DEFAULT minimum_required_memory 256
# Retrieve deployment logs
iniset $IRONIC_CONF_FILE agent deploy_logs_collect $IRONIC_DEPLOY_LOGS_COLLECT
iniset $IRONIC_CONF_FILE agent deploy_logs_storage_backend $IRONIC_DEPLOY_LOGS_STORAGE_BACKEND
iniset $IRONIC_CONF_FILE agent deploy_logs_local_path $IRONIC_DEPLOY_LOGS_LOCAL_PATH
# Allow ironic to set permissions for the tftp test usage.
iniset $IRONIC_CONF_FILE pxe dir_permission 493
# Set image_download_source for direct interface
if [[ -n "$IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE" ]]; then
iniset $IRONIC_CONF_FILE agent image_download_source $IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE
fi
# Configure JSON RPC backend
iniset $IRONIC_CONF_FILE DEFAULT rpc_transport $IRONIC_RPC_TRANSPORT
iniset $IRONIC_CONF_FILE json_rpc port $IRONIC_JSON_RPC_PORT
if [[ "$IRONIC_JSON_RPC_AUTH_STRATEGY" != "" ]]; then
iniset $IRONIC_CONF_FILE json_rpc auth_strategy $IRONIC_JSON_RPC_AUTH_STRATEGY
fi
if [[ "$IRONIC_JSON_RPC_AUTH_STRATEGY" == "http_basic" ]]; then
iniset $IRONIC_CONF_FILE json_rpc username myName
iniset $IRONIC_CONF_FILE json_rpc password myPassword
# json-rpc auth file with bcrypt hash of myPassword
echo 'myName:$2y$05$lE3eGtyj41jZwrzS87KTqe6.JETVCWBkc32C63UP2aYrGoYOEpbJm' > /etc/ironic/htpasswd-json-rpc
fi
if [[ "$IRONIC_JSON_RPC_AUTH_STRATEGY" == "" ]] || [[ "$IRONIC_JSON_RPC_AUTH_STRATEGY" == "keystone" ]]; then
configure_client_for json_rpc
fi
if [[ "$IRONIC_ENFORCE_SCOPE" == "False" ]]; then
iniset $IRONIC_CONF_FILE oslo_policy enforce_scope false
iniset $IRONIC_CONF_FILE oslo_policy enforce_new_defaults false
else
# NOTE(TheJulia): In devstack, services by default get service role
# accounts in a service project. Under normal circumstances, they
# need to be able to have elevated access if not explicitly
# configured for a system scoped account.
iniset $IRONIC_CONF_FILE DEFAULT rbac_service_role_elevated_access true
fi
if [[ "${SERVICE_PROJECT_NAME:-service}" != "service" ]]; then
iniset $IRONIC_CONF_FILE DEFAULT rbac_service_project_name $SERVICE_PROJECT_NAME
fi
# Set fast track options
iniset $IRONIC_CONF_FILE deploy fast_track $IRONIC_DEPLOY_FAST_TRACK
if is_service_enabled swift && [[ "$SWIFT_ENABLE_TEMPURLS" == "True" ]]; then
iniset $IRONIC_CONF_FILE deploy configdrive_use_object_store True
fi
# No need to check if RabbitMQ is enabled, this call does it in a smart way
if [[ "$IRONIC_RPC_TRANSPORT" == "oslo" ]]; then
iniset_rpc_backend ironic $IRONIC_CONF_FILE
fi
# Configure Ironic conductor, if it was enabled.
if is_service_enabled ir-cond; then
configure_ironic_conductor
fi
# Configure Ironic API, if it was enabled.
if is_service_enabled ir-api; then
configure_ironic_api
fi
# Configure Ironic noVNC proxy, if it was enabled.
if is_service_enabled ir-novnc; then
configure_ironic_novnc
fi
# Format logging
setup_logging $IRONIC_CONF_FILE
# Adds ironic site for IPXE and direct deploy
if is_http_server_required; then
_config_ironic_apache_additions
fi
# Adds uWSGI for Ironic API
write_uwsgi_config "$IRONIC_UWSGI_CONF" "$IRONIC_UWSGI" "/baremetal" "" "ironic-api"
if [[ "$os_VENDOR" =~ (Debian|Ubuntu) ]]; then
# The groups change with newer libvirt. Older Ubuntu used
# 'libvirtd', but now uses libvirt like Debian. Do a quick check
# to see if libvirtd group already exists to handle grenade's case.
LIBVIRT_GROUP=$(cut -d ':' -f 1 /etc/group | grep 'libvirtd$' || true)
LIBVIRT_GROUP=${LIBVIRT_GROUP:-libvirt}
else
LIBVIRT_GROUP=libvirtd
fi
if ! getent group $LIBVIRT_GROUP >/dev/null; then
sudo groupadd $LIBVIRT_GROUP
fi
# NOTE(vsaienko) Add stack to libvirt group when installing without nova.
if ! is_service_enabled nova; then
# Disable power state change callbacks to nova.
iniset $IRONIC_CONF_FILE nova send_power_notifications false
add_user_to_group $STACK_USER $LIBVIRT_GROUP
# This is the basic set of devices allowed / required by all virtual machines.
# Add /dev/net/tun to cgroup_device_acl, needed for type=ethernet interfaces
if ! sudo grep -q '^cgroup_device_acl' /etc/libvirt/qemu.conf; then
cat <<EOF | sudo tee -a /etc/libvirt/qemu.conf
cgroup_device_acl = [
"/dev/null", "/dev/full", "/dev/zero",
"/dev/random", "/dev/urandom",
"/dev/ptmx", "/dev/kvm", "/dev/kqemu",
"/dev/rtc", "/dev/hpet","/dev/net/tun",
"/dev/vfio/vfio",
]
EOF
restart_libvirt
fi
# Standalone jobs may use some different paths, that is okay
iniset $IRONIC_CONF_FILE conductor file_url_allowed_paths /var/lib/ironic,/shared/html,/templates,/opt/cache/files,/vagrant,/opt/stack/ironic
fi
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
if is_deployed_by_ipmi; then
configure_virtualbmc
start_virtualbmc
fi
if is_deployed_by_snmp; then
configure_virtualpdu
start_virtualpdu
fi
if is_deployed_by_redfish; then
configure_redfish
start_redfish
fi
fi
}
# configure_ironic_api() - Is used by configure_ironic(). Performs
# API specific configuration.
function configure_ironic_api {
iniset $IRONIC_CONF_FILE DEFAULT auth_strategy $IRONIC_AUTH_STRATEGY
configure_keystone_authtoken_middleware $IRONIC_CONF_FILE ironic
iniset $IRONIC_CONF_FILE keystone_authtoken service_token_roles_required True
iniset $IRONIC_CONF_FILE oslo_middleware enable_proxy_headers_parsing True
}
# configure_client_for() - is used by configure_ironic_conductor.
# Sets options to instantiate clients for other services
# single argument - config section to fill
function configure_client_for {
local service_config_section
service_config_section=$1
local use_system_scope="False"
# keystoneauth auth plugin options
iniset $IRONIC_CONF_FILE $service_config_section auth_type password
iniset $IRONIC_CONF_FILE $service_config_section auth_url $KEYSTONE_SERVICE_URI
# NOTE(TheJulia): Below are services which we know, as of late 2021, which support
# explicit scope based ops *and* have knobs.
if [[ "$service_config_section" == "inspector" ]] && [[ "$IRONIC_INSPECTOR_ENFORCE_SCOPE" == "True" ]]; then
use_system_scope="True"
fi
if [[ "$use_system_scope" == "True" ]]; then
iniset $IRONIC_CONF_FILE $service_config_section system_scope all
iniset $IRONIC_CONF_FILE $service_config_section username admin
iniset $IRONIC_CONF_FILE $service_config_section password $ADMIN_PASSWORD
else
iniset $IRONIC_CONF_FILE $service_config_section username ironic
iniset $IRONIC_CONF_FILE $service_config_section password $SERVICE_PASSWORD
iniset $IRONIC_CONF_FILE $service_config_section project_name $SERVICE_PROJECT_NAME
iniset $IRONIC_CONF_FILE $service_config_section project_domain_id default
fi
iniset $IRONIC_CONF_FILE $service_config_section user_domain_id default
# keystoneauth session options
iniset $IRONIC_CONF_FILE $service_config_section cafile $SSL_BUNDLE_FILE
# keystoneauth adapter options
# NOTE(pas-ha) relying on defaults for valid_interfaces being "internal,public" in ironic
iniset $IRONIC_CONF_FILE $service_config_section region_name $REGION_NAME
}
# configure_ironic_conductor() - Is used by configure_ironic().
# Sets conductor specific settings.
function configure_ironic_conductor {
# NOTE(pas-ha) service_catalog section is used to discover
# ironic API endpoint from keystone catalog
local client_sections="neutron swift glance inspector cinder service_catalog nova"
for conf_section in $client_sections; do
configure_client_for $conf_section
done
# set up drivers / hardware types
iniset $IRONIC_CONF_FILE DEFAULT enabled_hardware_types $IRONIC_ENABLED_HARDWARE_TYPES
iniset $IRONIC_CONF_FILE conductor automated_clean $IRONIC_AUTOMATED_CLEAN_ENABLED
iniset $IRONIC_CONF_FILE conductor log_step_flows_to_syslog $IRONIC_LOG_STEPS_TO_SYSLOG
# configure enabled and default interfaces
local iface
local iface_var
local iface_uppercase
local default_iface_var
for iface in ${IRONIC_DRIVER_INTERFACE_TYPES}; do
iface_uppercase="${iface^^}"
iface_var="IRONIC_ENABLED_${iface_uppercase}_INTERFACES"
iniset $IRONIC_CONF_FILE DEFAULT enabled_${iface}_interfaces ${!iface_var}
default_iface_var="IRONIC_DEFAULT_${iface_uppercase}_INTERFACE"
if [[ -n "${!default_iface_var}" ]]; then
if [[ ! "${!iface_var}" =~ ${!default_iface_var} ]]; then
die $LINENO "Default interface set for ${iface} interface is not enabled."
fi
iniset $IRONIC_CONF_FILE DEFAULT default_${iface}_interface ${!default_iface_var}
fi
done
if is_deployed_by_snmp; then
iniset $IRONIC_CONF_FILE pxe enable_netboot_fallback True
fi
if is_ansible_deploy_enabled; then
if is_ansible_with_tinyipa; then
if [[ ! -f $IRONIC_ANSIBLE_SSH_KEY ]]; then
# generate ssh key if absent as we will rebuild the ramdisk
# TODO(pas-ha) make it work for multi-node DevStack:
# - generate outside of this script
# - pass path in as $IRONIC_ANSIBLE_SSH_KEY
# - distribute private key to subnodes under the same path
# Similar to what we do for n-g-s, may be even reuse its key.
ssh-keygen -t rsa -N '' -f $IRONIC_ANSIBLE_SSH_KEY
chmod 600 $IRONIC_ANSIBLE_SSH_KEY
fi
if [[ -z $IRONIC_ANSIBLE_SSH_USER ]]; then
# we definitely know the default username to use for TinyIPA image
IRONIC_ANSIBLE_SSH_USER='devuser'
fi
# (rpittau) most recent tinyipa uses python3 natively so we need to change
# the default ansible python interpreter.
iniset $IRONIC_CONF_FILE ansible default_python_interpreter /usr/bin/python3
fi
iniset $IRONIC_CONF_FILE ansible default_key_file $IRONIC_ANSIBLE_SSH_KEY
if [[ -n $IRONIC_ANSIBLE_SSH_USER ]]; then
iniset $IRONIC_CONF_FILE ansible default_username $IRONIC_ANSIBLE_SSH_USER
fi
# TODO(pas-ha) find a way to include the CA bundle into the image during rebuild,
# at least for the tinyipa ramdisk
iniset $IRONIC_CONF_FILE ansible image_store_insecure "True"
fi
if [[ -n $IRONIC_LOADER_PATHS ]]; then
iniset $IRONIC_CONF_FILE pxe loader_file_paths $IRONIC_LOADER_PATHS
fi
iniset $IRONIC_CONF_FILE service_catalog endpoint_override "$IRONIC_SERVICE_PROTOCOL://$([[ $IRONIC_HTTP_SERVER =~ : ]] && echo "[$IRONIC_HTTP_SERVER]" || echo $IRONIC_HTTP_SERVER)/baremetal"
if [[ -n "$IRONIC_CALLBACK_TIMEOUT" ]]; then
iniset $IRONIC_CONF_FILE conductor deploy_callback_timeout $IRONIC_CALLBACK_TIMEOUT
fi
if [[ "$IRONIC_IP_VERSION" == "6" ]]; then
iniset $IRONIC_CONF_FILE pxe tftp_server $IRONIC_HOST_IPV6
else
iniset $IRONIC_CONF_FILE pxe tftp_server $IRONIC_TFTPSERVER_IP
fi
iniset $IRONIC_CONF_FILE pxe tftp_root $IRONIC_TFTPBOOT_DIR
iniset $IRONIC_CONF_FILE pxe tftp_master_path $IRONIC_TFTPBOOT_DIR/master_images
if [[ -n "$IRONIC_PXE_BOOT_RETRY_TIMEOUT" ]]; then
iniset $IRONIC_CONF_FILE pxe boot_retry_timeout $IRONIC_PXE_BOOT_RETRY_TIMEOUT
fi
if [[ "$IRONIC_TERMINAL_SSL" == "True" ]]; then
# Make sure the cert directory exist
sudo mkdir -p $IRONIC_TERMINAL_CERT_DIR
sudo chown $STACK_USER $IRONIC_TERMINAL_CERT_DIR
iniset $IRONIC_CONF_FILE console terminal_cert_dir $IRONIC_TERMINAL_CERT_DIR
# Generate the SSL certificate
openssl req \
-x509 \
-days 365 \
-newkey rsa:1024 \
-nodes \
-keyout $IRONIC_TERMINAL_CERT_DIR/certificate.pem.key \
-out $IRONIC_TERMINAL_CERT_DIR/certificate.pem \
-subj '/O=OpenStack/OU=DevStack Servers'
fi
local pxe_params="nofb console=${IRONIC_TTY_DEV}"
pxe_params+=" systemd.journald.forward_to_console=yes"
if is_service_enabled tls-proxy; then
pxe_params+=" ipa-insecure=1"
fi
pxe_params+=" $IRONIC_EXTRA_PXE_PARAMS"
if [[ -n "$pxe_params" ]]; then
iniset $IRONIC_CONF_FILE pxe kernel_append_params "$pxe_params"
fi
local kernel_append_params="nofb console=${IRONIC_TTY_DEV}"
kernel_append_params+=" systemd.journald.forward_to_console=yes"
# NOTE(dtantsur): avoid setting ipa-insecure for redfish, we have a way to
# pass the TLS certificate.
iniset $IRONIC_CONF_FILE redfish kernel_append_params "$kernel_append_params"
if is_service_enabled tls-proxy; then
deploy_int_CA "$IRONIC_STATE_PATH/ironic-ca.pem"
iniset $IRONIC_CONF_FILE agent api_ca_file "$IRONIC_STATE_PATH/ironic-ca.pem"
fi
# Set these options for scenarios in which the agent fetches the image
# directly from glance, and don't set them where the image is pushed
# over iSCSI.
if is_glance_configuration_required; then
if [[ "$SWIFT_ENABLE_TEMPURLS" == "False" ]] ; then
die $LINENO "SWIFT_ENABLE_TEMPURLS must be True. This is " \
"required either because " \
"IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE was set to swift " \
"OR configuration of Glance with Swift was explicitly " \
"requested with IRONIC_CONFIGURE_GLANCE_WITH_SWIFT=True"
fi
iniset $IRONIC_CONF_FILE glance swift_temp_url_duration 3600
fi
if is_deployed_by_agent; then
iniset $IRONIC_CONF_FILE api ramdisk_heartbeat_timeout 30
fi
# FIXME: this really needs to be tested in the gate. For now, any
# test using the agent ramdisk should skip the erase_devices clean
# step because it is too slow to run in the gate.
iniset $IRONIC_CONF_FILE deploy erase_devices_priority 0
if is_http_server_required; then
iniset $IRONIC_CONF_FILE deploy http_root $IRONIC_HTTP_DIR
iniset $IRONIC_CONF_FILE deploy http_url "http://$([[ $IRONIC_HTTP_SERVER =~ : ]] && echo "[$IRONIC_HTTP_SERVER]" || echo $IRONIC_HTTP_SERVER):$IRONIC_HTTP_PORT"
fi
if [[ "$IRONIC_IPXE_USE_SWIFT" == "True" ]]; then
iniset $IRONIC_CONF_FILE pxe ipxe_use_swift True
fi
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
iniset $IRONIC_CONF_FILE neutron port_setup_delay 15
fi
iniset $IRONIC_CONF_FILE dhcp dhcp_provider $IRONIC_DHCP_PROVIDER
isolinux=$(find -L /usr -type f -name "isolinux.bin" | head -1)
if [[ -n "$isolinux" ]]; then
iniset $IRONIC_CONF_FILE DEFAULT isolinux_bin "$isolinux"
fi
configure_inspection
}
# configure_inspection() - Set up in-band inspection
function configure_inspection {
local allow_unmanaged="$IRONIC_UNMANAGED_INSPECTION"
# Note: IRONIC_INSPECTOR_MANAGED_BOOT is defined in the ironic-inspector's devstack plugin
if [[ "$IRONIC_ENABLED_INSPECT_INTERFACES" =~ inspector ]] && [[ "${IRONIC_INSPECTOR_MANAGED_BOOT:-False}" != True ]]; then
allow_unmanaged=True
fi
if [[ "$allow_unmanaged" == False ]]; then
iniset "$IRONIC_CONF_FILE" inspector require_managed_boot True
fi
}
# configure_ironic_novnc() - Is used by configure_ironic(). Performs
# noVNC proxy specific configuration.
function configure_ironic_novnc {
local service_port=$IRONIC_NOVNCPROXY_PORT
# TODO(stevebaker) handle configuring tls-proxy
local service_protocol=http
# TODO(stevebaker) check for existence of vnc_lite.html vs vnc_auto.html
# from older NoVNC releases
novnc_url=$service_protocol://$SERVICE_HOST:$service_port/vnc_lite.html
iniset $IRONIC_CONF_FILE vnc enabled True
iniset $IRONIC_CONF_FILE vnc public_url $novnc_url
iniset $IRONIC_CONF_FILE vnc host_ip $SERVICE_HOST
iniset $IRONIC_CONF_FILE vnc port $service_port
iniset $IRONIC_CONF_FILE vnc novnc_web $NOVNC_WEB_DIR
iniset $IRONIC_CONF_FILE vnc container_provider systemd
iniset $IRONIC_CONF_FILE vnc console_image localhost/ironic-vnc-container
}
# create_ironic_cache_dir() - Part of the init_ironic() process
function create_ironic_cache_dir {
# Create cache dir
sudo mkdir -p $IRONIC_AUTH_CACHE_DIR/api
sudo chown $STACK_USER $IRONIC_AUTH_CACHE_DIR/api
rm -f $IRONIC_AUTH_CACHE_DIR/api/*
sudo mkdir -p $IRONIC_AUTH_CACHE_DIR/registry
sudo chown $STACK_USER $IRONIC_AUTH_CACHE_DIR/registry
rm -f $IRONIC_AUTH_CACHE_DIR/registry/*
}
# create_systemd_container_dir() - Part of the init_ironic() process
function create_systemd_container_dir {
local uid=$(id -u)
local user_dir=/etc/containers/systemd/users/$uid
if [ ! -d "$user_dir" ]; then
sudo mkdir -p $user_dir
sudo chown $STACK_USER $user_dir
# container files have BMC credentials, limit non stack user
sudo chmod 0750 $user_dir
fi
}
# create_ironic_accounts() - Set up common required ironic accounts
# Project User Roles
# ------------------------------------------------------------------
# service ironic admin
function create_ironic_accounts {
if [[ "$ENABLED_SERVICES" =~ "ir-api" && "$ENABLED_SERVICES" =~ "key" ]]; then
# Define service and endpoints in Keystone
get_or_create_service "ironic" "baremetal" "Ironic baremetal provisioning service"
get_or_create_endpoint "baremetal" \
"$REGION_NAME" \
"$IRONIC_SERVICE_PROTOCOL://$IRONIC_HOSTPORT"
# Create ironic service user
# TODO(tenbrae): make this work with the 'service' role
# https://bugs.launchpad.net/ironic/+bug/1605398
create_service_user "ironic" "admin"
# Create additional bare metal tenant and roles
if is_service_enabled nova; then
get_or_add_user_project_role admin nova $SERVICE_PROJECT_NAME
fi
fi
}
# init_ironic() - Initialize databases, etc.
function init_ironic {
if [[ "$HOST_TOPOLOGY_ROLE" != "subnode" ]]; then
# (Re)create ironic database
recreate_database ironic
# Migrate ironic database
$IRONIC_BIN_DIR/ironic-dbsync --config-file=$IRONIC_CONF_FILE
fi
create_ironic_cache_dir
# NOTE(rloo): We're not upgrading but want to make sure this command works,
# even though we're not parsing the output of this command.
$IRONIC_BIN_DIR/ironic-status upgrade check && ret_val=$? || ret_val=$?
if [ $ret_val -gt 1 ] ; then
# Allow continuing even with unsupported drivers if explicitly enabled
if [[ "${IRONIC_ALLOW_UNSUPPORTED_DRIVERS}" != "True" ]]; then
die $LINENO "The `ironic-status upgrade check` command returned an error. Cannot proceed."
else
echo "WARNING: Continuing despite upgrade check failure because IRONIC_ALLOW_UNSUPPORTED_DRIVERS=True"
fi
fi
if is_service_enabled ir-novnc; then
create_systemd_container_dir
fi
}
# _ironic_bm_vm_names() - Generates list of names for baremetal VMs.
function _ironic_bm_vm_names {
local idx
local num_vms
num_vms=$(($IRONIC_VM_COUNT - 1))
for idx in $(seq 0 $num_vms); do
echo "$(get_ironic_node_prefix)-${idx}"
done
}
# start_ironic() - Start running processes, including screen
function start_ironic {
# Start Ironic API server, if enabled.
if is_service_enabled ir-api; then
start_ironic_api
fi
# Start Ironic conductor, if enabled.
if is_service_enabled ir-cond; then
start_ironic_conductor
fi
# Start Ironic noVNC proxy server, if enabled.
if is_service_enabled ir-novnc; then
start_ironic_novnc
fi
# Start Apache if iPXE or agent+http is enabled
if is_http_server_required; then
restart_apache_server
fi
}
# start_ironic_api() - Used by start_ironic().
# Starts Ironic API server.
function start_ironic_api {
local service_port=$IRONIC_SERVICE_PORT
local service_protocol=$IRONIC_SERVICE_PROTOCOL
local ironic_url
# Get right service port for testing
if is_service_enabled tls-proxy; then
service_port=$IRONIC_SERVICE_PORT_INT
service_protocol="http"
fi
run_process "ir-api" "$(which uwsgi) --procname-prefix ironic-api --ini $IRONIC_UWSGI_CONF"
ironic_url=$service_protocol://$SERVICE_HOST/baremetal
echo "Waiting for ir-api ($ironic_url) to start..."
if ! timeout $SERVICE_TIMEOUT sh -c "while ! wget --no-proxy -q -O- $ironic_url; do sleep 1; done"; then
die $LINENO "ir-api did not start"
fi
if is_service_enabled tls-proxy; then
start_tls_proxy ironic '*' $IRONIC_SERVICE_PORT $SERVICE_HOST $IRONIC_SERVICE_PORT_INT
fi
}
# start_ironic_conductor() - Used by start_ironic().
# Starts Ironic conductor.
function start_ironic_conductor {
# NOTE(stevebaker) set DBUS_SESSION_BUS_ADDRESS so that systemd calls can be made
# for the systemd console container provider.
run_process ir-cond "$IRONIC_BIN_DIR/ironic-conductor --config-file=$IRONIC_CONF_FILE" "" "" "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$UID/bus"
# Unset variables which we shouldn't have... Grenade resets these :\
# in grenade/projects/60_nova/resources.sh as part of the resource
# creation tests.
unset OS_TENANT_NAME
unset OS_USERNAME
unset OS_PROJECT_NAME
unset OS_PASSWORD
# Wait up to 30 seconds for ironic-conductor to start and register itself
local attempt
local max_attempts=7
for attempt in $(seq 1 $max_attempts); do
if openstack --os-cloud $IRONIC_OS_CLOUD baremetal driver list | grep -q $IRONIC_DEPLOY_DRIVER; then
break
fi
if [ $attempt -eq $max_attempts ]; then
die $LINENO "Driver $IRONIC_DEPLOY_DRIVER did not appear in the driver list"
fi
echo "Still waiting for ironic-conductor to start, current state:"
openstack --os-cloud $IRONIC_OS_CLOUD baremetal driver list
sleep 5
done
}
# start_ironic_novnc() - Used by start_ironic().
# Starts Ironic noVNC proxy server.
function start_ironic_novnc {
run_process ir-novnc "$IRONIC_BIN_DIR/ironic-novncproxy --config-file=$IRONIC_CONF_FILE"
# TODO(stevebaker) confirm the web server is returning content
}
# stop_ironic() - Stop running processes
function stop_ironic {
stop_process ir-api
stop_process ir-cond
stop_process ir-novnc
stop_process ir-sw-sim
}
# create_ovs_taps is also called by the devstack/upgrade/resources.sh script
#
# create_ovs_taps ironic_network_id
# NOTE(vsaienko) Ironic supports only Flat Neutron network.
# create_ovs_taps are needed in order to provide connectivity from ironic-conductor
# to VM. With Neutron Flat network it will be not needed.
function create_ovs_taps {
local ironic_net_id
ironic_net_id=$1
die_if_not_set $LINENO ironic_net_id "Failed to get ironic network id"
# Work around: No netns exists on host until a Neutron port is created. We
# need to create one in Neutron to know what netns to tap into prior to the
# first node booting.
if [[ "$Q_AGENT" != "ovn" ]]; then
# NOTE(TheJulia): So.. Neutron doesn't allow a port to be created as a
# system scoped admin, which makes sense.
local port_id
port_id=$(openstack --os-cloud devstack-admin port create --network ${ironic_net_id} temp_port -c id -f value)
die_if_not_set $LINENO port_id "Failed to create neutron port"
local tapdev
local tapdev_cmd="sudo ip netns exec qdhcp-${ironic_net_id} ip link list | grep ' tap' | cut -d':' -f2 | cut -d'@' -f1 | cut -b2- | grep '^tap'"
# retry tap device discovery to make sure the tag has been set to port
tapdev=$(test_with_retry "$tapdev_cmd" "Failed to get tap device id" 20 1)
local tag_id
tag_id=$(sudo ovs-vsctl get port ${tapdev} tag)
die_if_not_set $LINENO tag_id "Failed to get tag id"
# Remove the port needed only for workaround.
openstack --os-cloud $OS_CLOUD port delete $port_id
else
# ovs-vsctl set Open_vSwitch . external-ids:ovn-cms-options=\"enable-chassis-as-gw\"
# should already be set -> external-ids:ovn-bridge-mappings=\"public:br-ex\"
# so the tl;dr is that the tag in this port, based on ovn config
# may be something like 841 on public, which in default devstack is
# br-ex. We don't care though, it is a vlan tag and previously we
# just used the integration bridge to make the connection with the
# tag. Basically the same.
# NOTE(TheJulia): Show the network data to ease troubleshooting,
# Normally, this will be the private network for devstack.
tag_id=$(openstack --os-cloud $OS_CLOUD network show $ironic_net_id -c "provider:segmentation_id" -f value)
die_if_not_set $LINENO tag_id "Failed to get tag id"
fi
local ovs_tap=ovs-tap
local brbm_tap=brbm-tap
# make sure veth pair is not existing, otherwise delete its links
sudo ip link show $ovs_tap && sudo ip link delete $ovs_tap
sudo ip link show $brbm_tap && sudo ip link delete $brbm_tap
# create veth pair for future interconnection between br-int and brbm
sudo ip link add $brbm_tap type veth peer name $ovs_tap
sudo ip link set dev $brbm_tap up
sudo ip link set dev $ovs_tap up
sudo ip link set dev br-int up
sudo ip link set dev br-ex up
# What is happening here:
# We are taking the vlan tag ID which is reserved, and physically cross connecting
# it over to be available on $ovs_tap, which later on would be bound to $brbm_tap,
# better known as brbm. If we're doing *anything* else for VM networking, we need
# to wire this all up differently.
if [[ "$Q_AGENT" != "ovn" ]]; then
sudo ovs-vsctl -- --if-exists del-port $ovs_tap -- add-port br-int $ovs_tap tag=$tag_id
else
# OVN defaults to everything on "public" which is br-ex
sudo ovs-vsctl -- --if-exists del-port $ovs_tap -- add-port br-ex $ovs_tap tag=$tag_id
fi
if [[ "${IRONIC_NETWORK_SIMULATOR:-ovs}" == "ovs" ]]; then
# This binds the $brbm_tap to the $IRONIC_VM_NETWORK_BRIDGE (typically brbm)
sudo ovs-vsctl -- --if-exists del-port $brbm_tap -- add-port $IRONIC_VM_NETWORK_BRIDGE $brbm_tap
fi
# In the event this doesn't match, then the physical network bridge is not attached
# And nodes *must* be attached somehow, but not inhernetly directly by default.
# Finally, share the fixed tenant network across all tenants. This allows the host
# to serve TFTP to a single network namespace via the tap device created above.
openstack --os-cloud $OS_CLOUD network set $ironic_net_id --share
}
function setup_qemu_log_hook {
# Make sure the libvirt hooks directory exist
sudo mkdir -p $IRONIC_LIBVIRT_HOOKS_PATH
# Copy the qemu hook to the right directory
sudo cp $IRONIC_DEVSTACK_FILES_DIR/hooks/qemu.py $IRONIC_LIBVIRT_HOOKS_PATH/qemu
sudo chmod -v +x $IRONIC_LIBVIRT_HOOKS_PATH/qemu
sudo sed -e "
s|%LOG_DIR%|$IRONIC_VM_LOG_DIR|g;
" -i $IRONIC_LIBVIRT_HOOKS_PATH/qemu
restart_libvirt
mkdir -p $IRONIC_VM_LOG_DIR
cat >${IRONIC_VM_LOG_DIR}/README << EOF
This directory contains the serial console log files from the virtual Ironic
bare-metal nodes. The *_console_* log files are the original log files and
include ANSI control codes which can make the output difficult to read. The
*_no_ansi_* log files have had ANSI control codes removed from the file and are
easier to read.
On some occasions there won't be a corresponding *_no_ansi_* log file, for
example if the job failed due to a time-out. You may see a log file without a
date/time in the file name. In that case you can display the logfile in your
console by doing:
$ curl URL_TO_LOGFILE
This will have your terminal process the ANSI escape codes.
Another option, if you have the 'pv' executable installed, is to simulate a
low-speed connection. In this example simulate a 300 Bytes/second connection.
$ curl URL_TO_LOGFILE | pv -q -L 300
This can allow you to see some of the content before the screen is cleared by
an ANSI escape sequence.
EOF
}
function initialize_libvirt_storage_pool {
[ -d $LIBVIRT_STORAGE_POOL_PATH ] || sudo mkdir -p $LIBVIRT_STORAGE_POOL_PATH
if ! sudo virsh pool-list --all | grep -q $LIBVIRT_STORAGE_POOL; then
sudo virsh pool-define-as --name $LIBVIRT_STORAGE_POOL dir \
--target $LIBVIRT_STORAGE_POOL_PATH >&2
sudo virsh pool-autostart $LIBVIRT_STORAGE_POOL >&2
sudo virsh pool-start $LIBVIRT_STORAGE_POOL >&2
fi
pool_state=$(sudo virsh pool-info $LIBVIRT_STORAGE_POOL | grep State | awk '{ print $2 }')
if [ "$pool_state" != "running" ] ; then
sudo virsh pool-start $LIBVIRT_STORAGE_POOL >&2
fi
}
function create_bridge_and_vms {
if [[ "${IRONIC_NETWORK_SIMULATOR:-ovs}" == "ovs" ]]; then
# Call libvirt setup scripts in a new shell to ensure any new group membership
sudo su $STACK_USER -c "$IRONIC_SCRIPTS_DIR/setup-network.sh $IRONIC_VM_NETWORK_BRIDGE $PUBLIC_BRIDGE_MTU ${IRONIC_NETWORK_SIMULATOR:-ovs}"
fi
if [[ "$IRONIC_VM_LOG_CONSOLE" == "True" ]] ; then
local log_arg="-l $IRONIC_VM_LOG_DIR"
if [[ "$IRONIC_VM_LOG_ROTATE" == "True" ]] ; then
setup_qemu_log_hook
fi
else
local log_arg=""
fi
if [ -x /etc/redhat-release ]; then
# We're on a rhel-ey distro, and default permissions
# restrict other users from reading these artifacts by
# default which prevents VMs from launching.
sudo chmod +r /var/lib/libvirt/images
fi
local vbmc_port=$IRONIC_VBMC_PORT_RANGE_START
local pdu_outlet=$IRONIC_VPDU_PORT_RANGE_START
local vm_name
local vm_opts=""
if [[ -n "$IRONIC_VM_EMULATOR" ]]; then
vm_opts+=" -e $IRONIC_VM_EMULATOR"
fi
vm_opts+=" -E $IRONIC_VM_ENGINE"
if [[ "$IRONIC_BOOT_MODE" == "uefi" ]]; then
vm_opts+=" -L $UEFI_LOADER_PATH -N $UEFI_NVRAM_PATH"
fi
if [[ -n "$LIBVIRT_NIC_DRIVER" ]]; then
vm_opts+=" -D $LIBVIRT_NIC_DRIVER"
elif [[ "$IRONIC_BOOT_MODE" == "uefi" ]]; then
# Note(derekh) UEFI for the moment doesn't work with the e1000 net driver
vm_opts+=" -D virtio"
fi
initialize_libvirt_storage_pool
local bridge_mac
bridge_mac=$(ip link show dev $IRONIC_VM_NETWORK_BRIDGE | grep -Eo "ether [A-Za-z0-9:]+"|sed "s/ether\ //")
for vm_name in $(_ironic_bm_vm_names); do
# pick up the $LIBVIRT_GROUP we have possibly joint
newgrp $LIBVIRT_GROUP <<SUBSHELL
$IRONIC_SCRIPTS_DIR/create-node.sh -n $vm_name \
-c $IRONIC_VM_SPECS_CPU -m $IRONIC_VM_SPECS_RAM -d $IRONIC_VM_SPECS_DISK \
-a $IRONIC_HW_ARCH -b $IRONIC_VM_NETWORK_BRIDGE $vm_opts -p $vbmc_port -o $pdu_outlet \
-i $IRONIC_VM_INTERFACE_COUNT -f $IRONIC_VM_SPECS_DISK_FORMAT -M $PUBLIC_BRIDGE_MTU $log_arg \
-v $IRONIC_VM_VOLUME_COUNT -P $LIBVIRT_STORAGE_POOL \
-t $IRONIC_MACHINE_TYPE -B $IRONIC_VM_BLOCK_SIZE \
-s ${IRONIC_NETWORK_SIMULATOR:-ovs} >> $IRONIC_VM_MACS_CSV_FILE
SUBSHELL
if is_deployed_by_ipmi; then
vbmc --no-daemon add $vm_name --port $vbmc_port
vbmc --no-daemon start $vm_name
fi
echo " ${bridge_mac} $IRONIC_VM_NETWORK_BRIDGE" >> $IRONIC_VM_MACS_CSV_FILE
vbmc_port=$((vbmc_port+1))
pdu_outlet=$((pdu_outlet+1))
# It is sometimes useful to dump out the VM configuration to validate it.
sudo virsh dumpxml $vm_name
done
if [[ -z "${IRONIC_PROVISION_NETWORK_NAME}" ]]; then
local ironic_net_id
ironic_net_id=$(openstack --os-cloud $OS_CLOUD network show "$PRIVATE_NETWORK_NAME" -c id -f value)
create_ovs_taps $ironic_net_id
# NOTE(vsaienko) Neutron no longer setup routing to private network.
# https://github.com/openstack-dev/devstack/commit/1493bdeba24674f6634160d51b8081c571df4017
# Add route here to have connection to VMs during provisioning.
local pub_router_id
local r_net_gateway
local dns_server
local replace_range
if [[ "$IRONIC_IP_VERSION" == '4' ]]; then
dns_server="8.8.8.8"
if [[ -z "${SUBNETPOOL_V4_ID}" ]]; then
replace_range=${FIXED_RANGE}
else
replace_range=${SUBNETPOOL_PREFIX_V4}
fi
else
dns_server="2001:4860:4860::8888"
if [[ -z "${SUBNETPOOL_V6_ID}" ]]; then
replace_range=${FIXED_RANGE_V6}
else
replace_range=${SUBNETPOOL_PREFIX_V6}
fi
fi
if [[ "$Q_AGENT" != "ovn" ]]; then
pub_router_id=$(openstack --os-cloud $OS_CLOUD router show $Q_ROUTER_NAME -f value -c id)
# Select the text starting at "src ", and grabbing the following field.
r_net_gateway=$(sudo ip netns exec qrouter-$pub_router_id ip -$IRONIC_IP_VERSION route get $dns_server |grep dev | sed s/^.*src\ // |awk '{ print $1 }')
sudo ip route replace $replace_range via $r_net_gateway
else
openstack router set --disable-snat --external-gateway public $Q_ROUTER_NAME
# Need to handle the json dict we get from the API (yeah, wut?!)
# and transform that so jq can do the needful. We also can't store
# it as a variable in ram, otherwise bash tries to escape it.
# Example gw_info (wrapped for readability)
# {'network_id': '3caae040-c0ac-4e8a-883b-12470f3fdeae',
# 'external_fixed_ips': [
# {'subnet_id': 'a8b6641e-53e1-4156-aa2f-2cda79dd4f4a',
# 'ip_address': '172.24.5.36'},
# {'subnet_id': 'bddf41dc-47f6-4124-9afd-744fa7343320',
# 'ip_address': '2001:db8::4d'}], 'enable_snat': False}
# Transform to json jq can parse, which includes replacing Python
# boolean strings.
external_gw_v4=$(openstack router show $Q_ROUTER_NAME -c external_gateway_info -f json | jq -r .external_gateway_info.external_fixed_ips[0].ip_address)
sudo ip addr
sudo ip link set dev br-ex up || true
# This route is only used *if* we actually provision with a
# dedicated ironic provisioning network which does not *always*
# happen. i.e. job specific config.
mtu=$(($PUBLIC_BRIDGE_MTU - 30))
v4mss=$(($PUBLIC_BRIDGE_MTU - 40 ))
# FIXME(TheJulia): We have a conundrum with MTUs.
# 1) OVN mtu handling is not great
# (https://bugs.launchpad.net/neutron/+bug/2032817)
# 2) We previously restricted the MTU down 100 bytes for
# VXLAN tunnel overhead for multinode jobs. This sort
# of conflicts. If we use the stock public bridge mtu,
# We would likely be okay, in the grand scheme of the
# world, but until we figure out that path, we need
# to clamp things, for now.
# NOTE(TheJulia): v6mss should be -60 once it is supported.
# NOTE(TheJulia): The route commands below set *and* lock a
# default MTU (disable PMTU discovery) and sets an Maximum Segment
# Size to advertise for packets to be sent along the path. Normally
# it is derived from the outbound interface MTU, which is wrong in
# this scenario. We're tracking this as LP#2032817.
sudo ip route add $IRONIC_PROVISION_SUBNET_PREFIX via $external_gw_v4 mtu lock $mtu advmss $v4mss
# This is the default space fallback for all neutron networking,
# since this is only if we create a dedicated provisioning
# network in the job config.
sudo ip route add $IPV4_ADDRS_SAFE_TO_USE via $external_gw_v4 mtu lock $mtu advmss $v4mss
fi
fi
# Here is a good place to restart tcpdump to begin capturing packets.
# See: https://docs.openstack.org/devstack/latest/debugging.html
# stop_tcpdump
# start_tcpdump
if [[ "$IRONIC_IP_VERSION" == "6" ]]; then
# route us back through the neutron router!
sudo ip -6 route add $IRONIC_PROVISION_SUBNET_PREFIX via $IPV6_ROUTER_GW_IP
sudo ip link set dev br-ex up || true
# Route back to our test subnet. Static should be safe for a while.
sudo ip -6 route add fd00::/8 via $IPV6_ROUTER_GW_IP
fi
# NOTE(TheJulia): Getting the subnet_id might need additional logic
# behind it, should also just generally work.
PUBLIC_SUBNET_ID=$(openstack router show $Q_ROUTER_NAME -c external_gateway_info -f json | jq -r .external_gateway_info.external_fixed_ips[0].subnet_id)
# Add a route for dhcp-less return path traffic
sudo ip route add 10.0.6.0/24 via $PUBLIC_SUBNET_IP
if [[ "${IRONIC_NETWORK_SIMULATOR:-ovs}" != "ovs" ]]; then
create_network_simulator
fi
} # End: create_bridge_and_vms
function create_network_simulator {
# Variables
local sim_trunk="sim-trunk"
local mgmt_int="sim-mgmt"
# *first* - Create the simulator. It needs to be bound to tap interfaces
create_network_simulator_vm $IRONIC_NETWORK_SIMULATOR $mgmt_int $sim_trunk
# *second* - Ensure our interfaces for the switch is up
# The VM Creation *creates* tap interfaces, which we then need to bind to.
sudo ip link set dev $sim_trunk up
sudo ip link set dev $mgmt_int up
# *third* - determine network bridge and attach that to the VM.
if [[ "$Q_AGENT" != "ovn" ]]; then
local network_bridge="br-int"
else
local network_bridge="br-ex"
fi
#
# Attach the trunk to a veth tap and all, and attach the whole thing.
sudo ovs-vsctl -- --if-exists del-port $sim_trunk -- add-port $network_bridge $sim_trunk
# Seems like br-infra might be right for the switch management access
sudo ovs-vsctl -- --if-exists del-port $mgmt_int -- add-port br-infra $mgmt_int
# *forth* - Join the rest of the interfaces
attach_switch_taps_to_vm_veth
# *fifth* - Apply NGS configuration
# This is done now because we can't do it earlier, as ngs gets pulled in
# and setup before ironic. So we will also need to restart neutron in this
# step to load the new configuration. This is actually for the best because
# you can't expect an operator to pre-configure neutron perfectly
# and the plugin needs to be able to handle "oh, I didn't know about that
# network cases as well.
configure_ngs_for_simulator
}
function configure_ngs_for_simulator {
# Ironic is using a network simulator which has been bolted into
# place. As such, configuration is going to be different, and we
# can begin configuring it from here.
if [ -f $DEST/networking-generic-switch/devstack/plugin.sh ]; then
source $DEST/networking-generic-switch/devstack/plugin.sh
fi
switch="genericswitch:$IRONIC_NETWORK_SIMULATOR"
case "$IRONIC_NETWORK_SIMULATOR" in
"force10_9")
switch_type="netmiko_dell_force10"
trunk_port="forty0/0"
switch_pass="admin_secret"
enable_pass=""
;;
"force10_10")
switch_type="netmiko_dell_os10"
trunk_port="ethernet1/1/1"
switch_pass="system_secret"
enable_pass=""
;;
"cisco_nexus9k")
switch_type="netmiko_cisco_nxos"
trunk_port="ethernet1/1"
switch_pass="system_s3cret!"
enable_pass=""
;;
sonic)
switch_type="netmiko_sonic"
trunk_port="Ethernet0"
switch_pass="YourPaSsWoRd"
enable_pass=""
;;
esac
# NOTE(TheJulia) This is for a dell force10 switch, and it may need
# to be broken up to be more in-line with per-type options.
add_generic_switch_to_ml2_config $switch "" admin 172.24.5.20 $switch_type "" "" "$switch_pass" "$enable_pass" "$trunk_port"
stop_neutron
# Start neutron and agents
start_neutron_service_and_check
start_neutron_agents
}
function attach_switch_taps_to_vm_veth {
# Count always starts at 2, since we pre-bind the switch interface
echo_summary "Creating bridges to bind the VM veths to the switch VM taps"
count=2
for i in $(ip link show up |cut -f1 -d "@" |grep "sim-node"|cut -f2 -d" "|cut -d":" -f1); do
local vm_int=$i
local sw_int=$( echo -n $i | sed s/sim/sw/ )
local sw_br_int="swbr-$count"
sudo ip link del dev $sw_br_int || true
sudo ip link add name $sw_br_int type bridge
sudo ip link set dev $sw_br_int up
sudo ip link set dev $vm_int up
sudo ip link set dev $sw_int up
sudo ip link set dev $vm_int master $sw_br_int
sudo ip link set dev $sw_int master $sw_br_int
count=$(( count + 1 ))
done
}
function create_network_simulator_vm {
# $1 is the simulator
# $2 is the management interface
# $3 is the network trunk
case $1 in
force10_9)
create_network_simulator_vm_force10_9 $2 $3
;;
force10_10)
create_network_simulator_vm_force10_10 $2 $3
;;
cisco_nexus9k)
create_network_simulator_vm_cisco_nexus $2 $3
;;
sonic)
create_network_simulator_vm_sonic $2 $3
;;
esac
}
function create_network_simulator_vm_force10_10 {
# Dell does not supply a easy to access download url for these assets.
# And blocks non-browsers from accessing the artifacts. So this requires
# some advance setup.
# 1) Download via https://www.dell.com/support/product-details/en-us/product/smartfabric-os10-emp-partner/drivers
# 2) Unzip files
# 3) Fix variables.
local base_image=/opt/stack/OS10_Virtualization_10.6.0.2.74V.zip
# Unzipping the package puts us in a clean state to get started.
unzip -d /opt/stack -o $base_image
local onie_disk=/opt/stack/OS10-Disk-1.0.0.vmdk
local installer_disk=/opt/stack/OS10-Installer-10.6.0.2.74.vmdk
local profile_disk=/opt/stack/OS10-platform-S5248F-10.6.0.2.74.vmdk
if ! test -f $onie_disk || ! test -f $installer_disk || ! test -f $profile_disk; then
die $LINENO "Failed to find required artifacts for Force 10 OS10 setup."
fi
# For extra context:
# * 4 GB of RAM
# * 2 vCPU
# * Must boot from ONIE disk as a SATA device, and all other devices must
# be attached as virtio devices for the installer to succeed.
# * BIOS mode/Q35 hardware.
# * e1000 for each port
# First port is mapped to management0/0
local emu_cmd="$IRONIC_VM_EMULATOR -boot order=b -enable-kvm -machine q35 -drive file=$onie_disk,id=diska,format=vmdk,if=none -device ich9-ahci,id=ahci -device ide-hd,drive=diska,bus=ahci.0 -drive file=$installer_disk,id=diskb,format=vmdk,if=none -device virtio-blk-pci,drive=diskb -drive file=$profile_disk,id=diskc,format=vmdk,if=none -device virtio-blk-pci,drive=diskc -m 4G -smp cpus=2 -display none -serial telnet:localhost:55001,server,nowait"
# Base interfaces, attaches the trunk and the management interfaces.
emu_cmd+=" -device e1000,netdev=net0 -netdev tap,id=net0,ifname=$1,script=no,downscript=no -device e1000,netdev=net1 -netdev tap,id=net1,ifname=$2,script=no,downscript=no,sndbuf=1048576"
# Get a list of links, cut everything *after* "@" since taps duplicate
# entries, limit to "sim-node" to match our nodes, and extract only
# the actual interface name.
local net_interface=2
for i in $(ip link show up |cut -f1 -d "@" |grep "sim-node"|cut -f2 -d" "|cut -d":" -f1|sed s/sim/sw/g); do
emu_cmd+=" -device e1000,netdev=net$net_interface -netdev tap,id=net$net_interface,ifname=$i,sndbuf=1048576"
net_interface=$(( net_interface + 1))
done
run_process ir-sw-sim "$emu_cmd" "root" "root"
# wait for the enable prompt
# Connect to localhost on 55001, wait 120 seconds to start,
# try 10 times, look for the "login prompt".
# Because we must start from a clean state, it takes the switch about
# 400-500 seconds to get into a deployed ready state.
wait_for_switch_prompt localhost 55001 500 10 "^OS10 login:" False
# NOTE(TheJulia): default credentials is admin/admin
# Start base configuration, but we also need to login and set a password
send_switch_config_line localhost 55001 "admin" # username
# This seems sufficient to wake up the VM's tty management service
sleep 6
send_switch_config_line localhost 55001 "admin" # username
send_switch_config_line localhost 55001 "admin" # password
# It takes a moment or six for the ability to execute the command shell to engage.
sleep 6
send_switch_config_line localhost 55001 "configure terminal"
# And it takes a moment for configuration mode to load...
sleep 2
# NOTE(TheJulia): Force10 OS doesn't let admin be in the password.
# Also, once this is set, a new console connection doesn't seem to work,
# however post-setup we can verify that NGS is configuring ports as we
# expect.
send_switch_config_line localhost 55001 "username admin password system_secret role sysadmin"
send_switch_config_line localhost 55001 "ip ssh server enable"
send_switch_config_line localhost 55001 "ip ssh server password-authentication"
send_switch_config_line localhost 55001 "int mgmt1/1/1"
send_switch_config_line localhost 55001 "no ip address dhcp"
send_switch_config_line localhost 55001 "no ipv6 address autoconfig"
send_switch_config_line localhost 55001 "ip address 172.24.5.20/24"
send_switch_config_line localhost 55001 "exit" # Must always exit each interface.
send_switch_config_line localhost 55001 "int ethernet1/1/1"
send_switch_config_line localhost 55001 "switchport mode trunk"
send_switch_config_line localhost 55001 "exit"
# Interfaces in *order*
# ethernet1/1/2 through ethernet 1/1/9
for interface in 2 3 4 5 6 7 8 9; do
send_switch_config_line localhost 55001 "int ethernet1/1/$interface"
send_switch_config_line localhost 55001 "lldp port-description-tlv advertise port-id"
send_switch_config_line localhost 55001 "exit"
done
send_switch_config_line localhost 55001 "exit"
sleep 10
send_switch_config_line localhost 55001 "exit"
send_switch_config_line localhost 55001 "write memory"
} # End of Force 10 OS10 setup
function create_network_simulator_vm_force10_9 {
# Pattern for this method is do the specific needful to get the emulator *up*.
# Download the OS
wget https://downloads.dell.com/translatedpdf/force10/os9/FTOS-SI-9.13.0.0.zip
unzip FTOS-SI-9.13.0.0.zip
mv FTOS-SI-9.13.0.0.iso /opt/stack
# Make a 30G disk, required.
qemu-img create -f qcow2 /opt/stack/FTOS.qcow2 30G
# Next step is to make the command line for the VM. Libvirt is so built
# around providing a graphical device, that it won't invoke qemu in the way
# necessary to create a telnet-able console to enable configuration.
# It works on direct launch via command line, likely because nothing in the
# stack is trying to do much more than basic serial IO on a console.
#
# For extra context:
# * 30G disk storage
# * 1 GB of RAM
# * 2 vCPU
# * Must boot from CD
# * BIOS Mode - No-display
# * e1000 for each port
# First port is mapped to management0/0
local emu_cmd="$IRONIC_VM_EMULATOR -boot d -cdrom /opt/stack/FTOS-SI-9.13.0.0.iso -enable-kvm -hda /opt/stack/FTOS.qcow2 -m 1G -smp cpus=2 -serial telnet:localhost:55001,server,nowait -display none"
# Base interfaces, attaches the trunk and the management interfaces.
emu_cmd+=" -device e1000,netdev=net0 -netdev tap,id=net0,ifname=$1,script=no,downscript=no -device e1000,netdev=net1 -netdev tap,id=net1,ifname=$2,script=no,downscript=no,sndbuf=1048576"
# Get a list of links, cut everything *after* "@" since taps duplicate
# entries, limit to "sim-node" to match our nodes, and extract only
# the actual interface name.
local net_interface=2
for i in $(ip link show up |cut -f1 -d "@" |grep "sim-node"|cut -f2 -d" "|cut -d":" -f1|sed s/sim/sw/g); do
emu_cmd+=" -device e1000,netdev=net$net_interface -netdev tap,id=net$net_interface,ifname=$i,sndbuf=1048576"
net_interface=$(( net_interface + 1))
done
# TODO, figure out if we can just run this as stack... We might not be able to
# due to permissions.
run_process ir-sw-sim "$emu_cmd" "root" "root"
# wait for the enable prompt
# Connect to localhost on 55001, wait 120 seconds to start,
# try 10 times, look for the "Dell#" prompt.
wait_for_switch_prompt localhost 55001 400 10 "^Dell#"
# NOTE(TheJulia): default credentials is admin/admin
# Start base configuration
send_switch_config_line localhost 55001 "conf t"
send_switch_config_line localhost 55001 "int management 0/0"
send_switch_config_line localhost 55001 "no shutdown"
send_switch_config_line localhost 55001 "no keepalive"
# FIXME(TheJulia) This won't work for a multinode job, but I doubt
# we will ever try this with multinode CI jobs as there is not really
# value in doing so for a simulated switch.
send_switch_config_line localhost 55001 "ip address 172.24.5.20 255.255.255.0"
send_switch_config_line localhost 55001 "exit"
# Interfaces in *order*
# fortygig0/0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60,
# 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124
for interface in 0/0 0/4 0/8 0/12 0/16 0/20 0/24 0/28 0/32 0/36; do
send_switch_config_line localhost 55001 "int fortygigE $interface"
send_switch_config_line localhost 55001 "no keepalive"
send_switch_config_line localhost 55001 "no shutdown"
# Setting switchport helps avoid errors like:
# % Error: Port is not in Layer-2 mode
send_switch_config_line localhost 55001 "switchport"
send_switch_config_line localhost 55001 "protocol lldp"
send_switch_config_line localhost 55001 "exit"
done
# Setup SSH server
send_switch_config_line localhost 55001 "exit"
send_switch_config_line localhost 55001 "username admin secret admin_secret"
send_switch_config_line localhost 55001 "enable secret admin_secret"
# Looks like setting up the ssh server automatically sets up
# the crypto key
send_switch_config_line localhost 55001 "ip ssh server enable"
# Need to wait before proceeding since crypto key generation
# takes a moment or ten.
sleep 10
send_switch_config_line localhost 55001 "exit"
send_switch_config_line localhost 55001 "wri mem"
}
function create_network_simulator_vm_cisco_nexus {
# Cisco's Nexus network switch simulator starts as a disk image,
# however if there are failures, the saved state on the disk image can
# be inconsistent state. As such, for sanity, we're going to copy the
# disk image.
# The TLDR how to use this, just download and set the variable below
# to one of the images.
local base_image=/opt/stack/nexus9300v64.10.3.7.M.qcow2
local use_disk=/opt/stack/nexus_disk_image.qcow2
local gns3_edk2_file=/opt/stack/OVMF-edk2-stable202305.fd
# The disk image stores persistent state, so we need to replace it.
cp $base_image $use_disk
# This is specifically so we have what seems to be a switch friendly
# edk2 firmware rom.
wget https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/OVMF-edk2-stable202305.fd.zip/download -O $gns3_edk2_file.zip
unzip $gns3_edk2_file.zip -d /opt/stack
# For extra context:
# * 8096 MB of RAM
# * 2 vCPU
# * UEFI mode/Q35 hardware.
# * SATA for the disk.
# * e1000 for each port
# First port is mapped to mgmt0.
# The VM will crash if the first interface is not present.
local emu_cmd="$IRONIC_VM_EMULATOR -enable-kvm -machine q35 -smbios type=0,uefi=on -drive if=pflash,format=raw,readonly=on,file=$gns3_edk2_file -drive file=$use_disk,id=disk0,format=qcow2,if=none -device ahci,id=ahci -device ide-hd,drive=disk0,bus=ahci.0 -m 8096M -smp cpus=2 -display none -serial telnet:localhost:55001,server,nowait "
# Base interfaces, attaches the trunk and the management interfaces.
emu_cmd+=" -device e1000,netdev=net0 -netdev tap,id=net0,ifname=$1,script=no,downscript=no -device e1000,netdev=net1 -netdev tap,id=net1,ifname=$2,script=no,downscript=no,sndbuf=1048576"
# Get a list of links, cut everything *after* "@" since taps duplicate
# entries, limit to "sim-node" to match our nodes, and extract only
# the actual interface name.
local net_interface=2
for i in $(ip link show up |cut -f1 -d "@" |grep "sim-node"|cut -f2 -d" "|cut -d":" -f1|sed s/sim/sw/g); do
emu_cmd+=" -device e1000,netdev=net$net_interface -netdev tap,id=net$net_interface,ifname=$i,sndbuf=1048576"
net_interface=$(( net_interface + 1))
done
run_process ir-sw-sim "$emu_cmd" "root" "root"
# wait for the enable prompt
# Connect to localhost on 55001, wait 120 seconds to start,
# try 10 times, look for the "login prompt".
# Because we must start from a clean state, it takes the switch about
# 400-500 seconds to get into a deployed ready state.
wait_for_switch_prompt localhost 55001 500 10 "Abort Power On Auto Provisioning" False
sleep 2
send_switch_config_line localhost 55001 "skip" # username
# When you skip POAP, it is noted by the CLI that doing os
# can take 5-15 minutes. In reality, this seems to only take
# ~120 seconds on the virtual switch simulator.
wait_for_switch_prompt localhost 55001 120 10 "login:" False
sleep 1
# Default credentials are "admin" and an empty password
send_switch_config_line localhost 55001 "admin" # username
sleep 1
send_switch_config_line localhost 55001 "" # blank password
sleep 4
send_switch_config_line localhost 55001 "configure"
sleep 1
send_switch_config_line localhost 55001 "feature lldp"
send_switch_config_line localhost 55001 "username admin password system_s3cret! role network-admin"
send_switch_config_line localhost 55001 "int mgmt0"
send_switch_config_line localhost 55001 "ip address 172.24.5.20/24"
send_switch_config_line localhost 55001 "lldp transmit"
send_switch_config_line localhost 55001 "exit"
send_switch_config_line localhost 55001 "int ethernet1/1"
send_switch_config_line localhost 55001 "switchport mode trunk"
# Just allow *everything*, but not required.
send_switch_config_line localhost 55001 "switchport trunk allowed vlan all"
send_switch_config_line localhost 55001 "exit"
# Interfaces in *order*
# ethernet1/2 through 1/7
for interface in 2 3 4 5 6 7 8 9; do
send_switch_config_line localhost 55001 "int ethernet1/$interface"
send_switch_config_line localhost 55001 "switchport mode access"
send_switch_config_line localhost 55001 "lldp transmit"
send_switch_config_line localhost 55001 "exit"
done
sleep 10
send_switch_config_line localhost 55001 "exit"
send_switch_config_line localhost 55001 "copy run start"
} # End of Cisco Nexus 9k simulator setup
function create_network_simulator_vm_sonic {
local sonic_image=/opt/stack/sonic-vs.img
if [ ! -f $sonic_image ]; then
sonic_url=$(curl -s https://sonic.software/builds.json | jq -r '.["master"]["sonic-vs.img.gz"]["url"]')
wget -O $sonic_image.gz $sonic_url
gzip -d $sonic_image.gz
fi
cp $sonic_image $sonic_image.qcow2
# For extra context:
# * 4GB of RAM
# * e1000 interfaces
# * virtio disk interface
# * boot_priority set to d
# First port is mapped to eth0.
# The VM will crash if the first interface is not present.
local emu_cmd="$IRONIC_VM_EMULATOR -enable-kvm -drive file=$sonic_image.qcow2,id=disk0,format=qcow2,if=none --device virtio-blk-pci,drive=disk0 -m 4096M -smp cpus=2 -display none -serial telnet:localhost:55001,server,nowait "
# Base interfaces, attaches the trunk and the management interfaces.
emu_cmd+=" -device e1000,netdev=net0 -netdev tap,id=net0,ifname=$1,script=no,downscript=no -device e1000,netdev=net1 -netdev tap,id=net1,ifname=$2,script=no,downscript=no,sndbuf=1048576"
# Get a list of links, cut everything *after* "@" since taps duplicate
# entries, limit to "sim-node" to match our nodes, and extract only
# the actual interface name.
local net_interface=2
for i in $(ip link show up |cut -f1 -d "@" |grep "sim-node"|cut -f2 -d" "|cut -d":" -f1|sed s/sim/sw/g); do
emu_cmd+=" -device e1000,netdev=net$net_interface -netdev tap,id=net$net_interface,ifname=$i,sndbuf=1048576"
net_interface=$(( net_interface + 1))
done
run_process ir-sw-sim "$emu_cmd" "root" "root"
# wait for the enable prompt
wait_for_switch_prompt localhost 55001 80 10 "sonic login:" False
sleep 4
send_switch_config_line localhost 55001 admin
sleep 2
send_switch_config_line localhost 55001 "YourPaSsWoRd"
sleep 10
# SONiC doesn't provide a means for this configuration through the CLI
# https://github.com/sonic-net/SONiC/blob/master/doc/SONiC-User-Manual.md#31-configuring-management-interface-and-loopback-interface
send_switch_config_line localhost 55001 "sudo /sbin/ifconfig eth0 172.24.5.20/24"
send_switch_config_line localhost 55001 "sonic-cli"
send_switch_config_line localhost 55001 "configure terminal"
send_switch_config_line localhost 55001 "sudo config interface ip remove Ethernet0 10.0.0.0/31"
send_switch_config_line localhost 55001 "sudo config switchport mode trunk Ethernet0"
send_switch_config_line localhost 55001 "sudo config interface ip remove Ethernet4 10.0.0.2/31"
send_switch_config_line localhost 55001 "sudo config switchport mode access Ethernet4"
send_switch_config_line localhost 55001 "sudo config interface ip remove Ethernet8 10.0.0.4/31"
send_switch_config_line localhost 55001 "sudo config switchport mode access Ethernet8"
send_switch_config_line localhost 55001 "sudo config interface ip remove Ethernet12 10.0.0.6/31"
send_switch_config_line localhost 55001 "sudo config switchport mode access Ethernet12"
send_switch_config_line localhost 55001 "sudo config interface ip remove Ethernet16 10.0.0.8/31"
send_switch_config_line localhost 55001 "sudo config switchport mode access Ethernet16"
send_switch_config_line localhost 55001 "sudo config interface ip remove Ethernet20 10.0.0.10/31"
send_switch_config_line localhost 55001 "sudo config switchport mode access Ethernet20"
send_switch_config_line localhost 55001 "sudo config interface ip remove Ethernet24 10.0.0.12/31"
send_switch_config_line localhost 55001 "sudo config switchport mode access Ethernet24"
} # End of SONiC configuration
function wait_for_switch_prompt {
local host=$1
local port=$2
local sleep_first=$3
local max_attempts=$4
local expeted_string=$5
local use_enable=$6
echo_summary "Waiting for $sleep_first seconds before polling the switch"
sleep $sleep_first
local attempt=1
if [[ -n "$use_enable" ]] && [[ "$use_enable" != "False" ]]; then
use_enable="True"
fi
for i in $(seq 1 $max_attempts); do
# NOTE(TheJulia): Strip non-ascii characters so we suppress the control
# characters which would otherwise make grep complain about binary output.
# NOTE(TheJulia): This will likely fail on the first attempt even if the
# switch is ready as the pre-written command prompt on the buffer has not
# yet updated from the command.
if [[ "$use_enable" == "True" ]]; then
if $(echo "en" | nc -w1 $host $port | strings |grep -q "$expected_string"); then
echo_summary "Got switch prompt - Switch ready for configuration."
return 0
else
echo_summary "Switch not online yet."
fi
else
if $(echo "" | nc -w1 $host $port | strings |grep -q "$expected_string"); then
echo_summary "Got switch prompt - Switch ready for configuration."
return 0
else
echo_summary "Switch not online yet."
fi
fi
if [ $attempt -gt $max_attempts ]; then
die $LINENO "Failed to find switch prompt in the allotted time."
return 1
fi
attempt=$(( attempt + 1))
sleep 15
done
}
function send_switch_config_line {
local host=$1
local port=$2
local cmd=$3
# FIXME(TheJulia): There has to be a better way to do this,
# but we also need to bootstrap some very early configuration...
# NOTE(TheJulia): *Always* strings the output, in case 0xFF can
# break devstack's executor.
echo "$cmd" | nc -w1 $host $port | strings
# Sleep briefy to allow the execution to occur and finish.
sleep 1
}
function wait_for_nova_resources {
# After nodes have been enrolled, we need to wait for both ironic and
# nova's periodic tasks to populate the resource tracker with available
# nodes and resources. Wait up to 2 minutes for a given resource before
# timing out.
local expected_count=$1
local resource_class=${IRONIC_DEFAULT_RESOURCE_CLASS^^}
# TODO(dtantsur): switch to Placement OSC plugin, once it exists
# Normally, we would want to use IRONIC_OS_CLOUD, but tokens need to be
# scoped. In this case, devstack-admin is the appropriate os-cloud to use.
local token
token=$(openstack --os-cloud devstack-admin token issue -f value -c id)
local endpoint
endpoint=$(openstack --os-cloud devstack-admin endpoint list --service placement --interface public -f value -c URL)
die_if_not_set $LINENO endpoint "Cannot find Placement API endpoint"
local i
local count
echo_summary "Waiting up to 3 minutes for placement to pick up $expected_count nodes"
for i in $(seq 1 12); do
# Fetch provider UUIDs from Placement
local providers
providers=$(curl --noproxy '*' -sH "X-Auth-Token: $token" $endpoint/resource_providers \
| jq -r '.resource_providers[].uuid')
local p
# Total count of the resource class, has to be equal to nodes count
count=0
for p in $providers; do
local amount
# A resource class inventory record looks something like
# {"max_unit": 1, "min_unit": 1, "step_size": 1, "reserved": 0, "total": 1, "allocation_ratio": 1}
# Subtrack reserved from total (defaulting both to 0)
amount=$(curl --noproxy '*' -sH "X-Auth-Token: $token" $endpoint/resource_providers/$p/inventories \
| jq ".inventories.CUSTOM_$resource_class as \$cls
| (\$cls.total // 0) - (\$cls.reserved // 0)")
# Check whether the resource provider has all expected traits
# registered against it.
rp_traits=$(curl --noproxy '*' -sH "X-Auth-Token: $token" \
-H "OpenStack-API-Version: placement 1.6" \
$endpoint/resource_providers/$p/traits)
for trait in $IRONIC_DEFAULT_TRAITS; do
if [[ $(echo "$rp_traits" | jq ".traits | contains([\"$trait\"])") == false ]]; then
amount=0
fi
done
if [ $amount -gt 0 ]; then
count=$(( count + $amount ))
fi
done
if [ $count -ge $expected_count ]; then
return 0
fi
if is_service_enabled n-api; then
$TOP_DIR/tools/discover_hosts.sh
fi
sleep 15
done
die $LINENO "Timed out waiting for Nova to track $expected_count nodes"
}
function _clean_ncpu_failure {
SCREEN_NAME=${SCREEN_NAME:-stack}
SERVICE_DIR=${SERVICE_DIR:-${DEST}/status}
n_cpu_failure="$SERVICE_DIR/$SCREEN_NAME/n-cpu.failure"
if [ -f ${n_cpu_failure} ]; then
mv ${n_cpu_failure} "${n_cpu_failure}.before-restart-by-ironic"
fi
}
function provide_nodes {
local nodes=$@
for node_id in $nodes; do
$IRONIC_CMD node provide $node_id
done
local attempt
for attempt in $(seq 1 $IRONIC_CLEANING_ATTEMPTS); do
local available
available=$(openstack --os-cloud $IRONIC_OS_CLOUD baremetal node list --provision-state available -f value -c UUID)
local nodes_not_finished=
for node_id in $nodes; do
if ! echo $available | grep -q $node_id; then
nodes_not_finished+=" $node_id"
fi
done
nodes=$nodes_not_finished
if [[ "$nodes" == "" ]]; then
break
fi
echo "Waiting for nodes to become available: $nodes"
echo "Currently available: $available"
sleep $IRONIC_CLEANING_DELAY
done
if [[ "$nodes" != "" ]]; then
die $LINENO "Some nodes did not finish cleaning: $nodes"
fi
}
function wait_for_ironic_neutron_agent_report_state_for_all_nodes {
local nodes=$@
echo "Waiting for ironic-neutron-agent to report state for nodes: $nodes"
local attempt
for attempt in $(seq 1 $IRONIC_NEUTRON_AGENT_REPORT_STATE_ATTEMPTS); do
local reported
reported=$(openstack --os-cloud $OS_CLOUD network agent list -f value -c Host -c Binary | grep ironic-neutron-agent | cut -d ' ' -f 1 | paste -s -d ' ')
echo "Currently reported nodes: $reported"
local can_break
for node_id in $nodes; do
if echo $reported | grep -q $node_id; then
can_break="True"
else
can_break="False"
break
fi
done
if [[ $can_break == "True" ]]; then
break
fi
sleep $IRONIC_NEUTRON_AGENT_REPORT_STATE_DELAY
done
if [[ "$can_break" == "False" ]]; then
die $LINENO "ironic-neutron-agent did not report some nodes."
fi
}
function enroll_nodes {
local chassis_id
chassis_id=$($IRONIC_CMD chassis create --description "ironic test chassis" -f value -c uuid)
die_if_not_set $LINENO chassis_id "Failed to create chassis"
local node_prefix
node_prefix=$(get_ironic_node_prefix)
local interface_info
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
local ironic_node_cpu=$IRONIC_VM_SPECS_CPU
local ironic_node_ram=$IRONIC_VM_SPECS_RAM
local ironic_node_disk=$IRONIC_VM_SPECS_DISK
local ironic_ephemeral_disk=$IRONIC_VM_EPHEMERAL_DISK
local ironic_node_arch=$IRONIC_HW_ARCH
if [[ ! -f $IRONIC_VM_MACS_CSV_FILE ]]; then
touch $IRONIC_VM_MACS_CSV_FILE
fi
local ironic_hwinfo_file=$IRONIC_VM_MACS_CSV_FILE
if is_deployed_by_ipmi; then
local deploy_options="\
--driver-info ipmi_address=${HOST_IP} \
--driver-info ipmi_username=admin \
--driver-info ipmi_password=password"
elif is_deployed_by_snmp; then
local deploy_options="\
--driver-info snmp_driver=${IRONIC_VPDU_SNMPDRIVER} \
--driver-info snmp_address=${HOST_IP} \
--driver-info snmp_port=${IRONIC_VPDU_LISTEN_PORT} \
--driver-info snmp_protocol=2c \
--driver-info snmp_community=${IRONIC_VPDU_COMMUNITY}"
elif is_deployed_by_redfish; then
local deploy_options="\
--driver-info redfish_address=http://${HOST_IP}:${IRONIC_REDFISH_EMULATOR_PORT} \
--driver-info redfish_username=admin \
--driver-info redfish_password=password"
fi
else
local ironic_node_cpu=$IRONIC_HW_NODE_CPU
local ironic_node_ram=$IRONIC_HW_NODE_RAM
local ironic_node_disk=$IRONIC_HW_NODE_DISK
local ironic_ephemeral_disk=$IRONIC_HW_EPHEMERAL_DISK
local ironic_node_arch=$IRONIC_HW_ARCH
local ironic_hwinfo_file=$IRONIC_HWINFO_FILE
fi
local total_nodes=0
local total_cpus=0
local node_uuids=
local node_id
while read hardware_info; do
echo "Processing $hardware_info"
local node_name
node_name=$node_prefix-$total_nodes
local node_options="$deploy_options"
local node_capabilities=""
if [[ "$IRONIC_BOOT_MODE" == "uefi" ]]; then
node_capabilities+=" --property capabilities=boot_mode:uefi"
fi
if [[ "$IRONIC_BOOT_MODE" == "bios" ]]; then
node_capabilities+=" --property capabilities=boot_mode:bios"
fi
if [[ "$IRONIC_SECURE_BOOT" == "True" ]]; then
if [[ -n "$node_capabilities" ]]; then
node_capabilities+=",secure_boot:true"
else
node_capabilities+=" --property capabilities=secure_boot:true"
fi
fi
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
interface_info=$(echo $hardware_info | awk '{print $1}')
if is_deployed_by_ipmi; then
local vbmc_port
vbmc_port=$(echo $hardware_info | awk '{print $2}')
node_options+=" --driver-info ipmi_port=$vbmc_port"
elif is_deployed_by_snmp; then
local pdu_outlet
pdu_outlet=$(echo $hardware_info | awk '{print $3}')
node_options+=" --driver-info snmp_outlet=$pdu_outlet"
elif is_deployed_by_redfish; then
node_options+=" --driver-info redfish_system_id=/redfish/v1/Systems/$node_name"
fi
# Local-link-connection options
local llc_opts=""
if [[ "${IRONIC_USE_LINK_LOCAL}" == "True" ]]; then
local switch_info
local switch_id
if [[ "${IRONIC_NETWORK_SIMULATOR:-ovs}" == "ovs" ]]; then
switch_id=$(echo $hardware_info |awk '{print $4}')
if [[ "$SUBNODE_ID" == "" ]]; then
switch_info=$(echo $hardware_info |awk '{print $5}')
else
switch_info="sub$SUBNODE_ID$(echo $hardware_info |awk '{print $5}')"
fi
else
switch_id="00:00:00:00:00:00"
switch_info=${IRONIC_NETWORK_SIMULATOR:-brbm}
fi
# NOTE(vsaienko) we will add port_id later in the code.
llc_opts="--local-link-connection switch_id=${switch_id} \
--local-link-connection switch_info=${switch_info} "
fi
if [[ "${IRONIC_STORAGE_INTERFACE}" == "cinder" ]]; then
local connector_iqn="iqn.2017-05.org.openstack.$node_prefix-$total_nodes"
if [[ -n "$node_capabilities" ]]; then
node_capabilities+=",iscsi_boot:True"
else
node_capabilities+=" --property capabilities=iscsi_boot:True"
fi
fi
else
# Currently we require all hardware platform have same CPU/RAM/DISK info
# in future, this can be enhanced to support different type, and then
# we create the bare metal flavor with minimum value
local bmc_address
bmc_address=$(echo $hardware_info |awk '{print $1}')
local mac_address
mac_address=$(echo $hardware_info |awk '{print $2}')
local bmc_username
bmc_username=$(echo $hardware_info |awk '{print $3}')
local bmc_passwd
bmc_passwd=$(echo $hardware_info |awk '{print $4}')
local node_options=""
if is_deployed_by_ipmi; then
node_options+=" --driver-info ipmi_address=$bmc_address \
--driver-info ipmi_password=$bmc_passwd \
--driver-info ipmi_username=$bmc_username"
elif is_deployed_by_ilo; then
node_options+=" --driver-info ilo_address=$bmc_address \
--driver-info ilo_password=$bmc_passwd \
--driver-info ilo_username=$bmc_username"
if [[ $IRONIC_ENABLED_BOOT_INTERFACES == *"ilo-virtual-media"* ]]; then
node_options+=" --driver-info deploy_iso=$IRONIC_DEPLOY_ISO_ID"
fi
elif is_deployed_by_drac; then
node_options+=" --driver-info drac_address=$bmc_address \
--driver-info drac_password=$bmc_passwd \
--driver-info drac_username=$bmc_username"
elif is_deployed_by_redfish; then
local bmc_redfish_system_id
bmc_redfish_system_id=$(echo $hardware_info |awk '{print $5}')
node_options+=" --driver-info redfish_address=https://$bmc_address \
--driver-info redfish_system_id=$bmc_redfish_system_id \
--driver-info redfish_password=$bmc_passwd \
--driver-info redfish_username=$bmc_username \
--driver-info redfish_verify_ca=False"
elif is_deployed_by_irmc; then
node_options+=" --driver-info irmc_address=$bmc_address \
--driver-info irmc_password=$bmc_passwd \
--driver-info irmc_username=$bmc_username"
if [[ -n "$IRONIC_DEPLOY_ISO_ID" ]]; then
node_options+=" --driver-info deploy_iso=$IRONIC_DEPLOY_ISO_ID"
fi
fi
interface_info="${mac_address}"
fi
# First node created will be used for testing in ironic w/o glance
# scenario, so we need to know its UUID.
local standalone_node_uuid=""
if [ $total_nodes -eq 0 ]; then
standalone_node_uuid="--uuid $IRONIC_NODE_UUID"
fi
# TODO(dtantsur): it would be cool to test with different resource
# classes, but for now just use the same.
node_id=$($IRONIC_CMD node create $standalone_node_uuid \
--chassis $chassis_id \
--driver $IRONIC_DEPLOY_DRIVER \
--name $node_name \
--resource-class $IRONIC_DEFAULT_RESOURCE_CLASS \
--property cpu_arch=$ironic_node_arch \
$node_capabilities \
$node_options \
-f value -c uuid)
die_if_not_set $LINENO node_id "Failed to create node"
node_uuids+=" $node_id"
if [[ -n $IRONIC_DEFAULT_TRAITS ]]; then
$IRONIC_CMD node add trait $node_id $IRONIC_DEFAULT_TRAITS
fi
if [[ -n "$IRONIC_SET_NODE_OWNER" ]]; then
# If set, apply an owner project project ID to the node,
# to allow tools like metalsmith to be abel to view and
# allocate the node.
owner_project_id=$(openstack project show $IRONIC_SET_NODE_OWNER -f value -c id)
$IRONIC_CMD node set --owner $owner_project_id $node_id
fi
$IRONIC_CMD node manage $node_id --wait $IRONIC_MANAGE_TIMEOUT || \
die $LINENO "Node did not reach manageable state in $IRONIC_MANAGE_TIMEOUT seconds"
# NOTE(vsaienko) IPA didn't automatically recognize root devices less than 4Gb.
# Setting root hint allows to install OS on such devices.
# 0x1af4 is VirtIO vendor device ID.
if [[ "$ironic_node_disk" -lt "4" && is_deployed_by_agent ]]; then
$IRONIC_CMD node set $node_id --property \
root_device='{"vendor": "0x1af4"}'
fi
if [[ $IRONIC_SHARDS == "1" ]]; then
openstack --os-cloud devstack-system-admin baremetal node set $node_id --shard $IRONIC_NODE_SHARD_NAME
fi
# In case we using portgroups, we should API version that support them.
# Otherwise API will return 406 ERROR
# NOTE(vsaienko) interface_info is in the following format here:
# mac1,tap-node0i1;mac2,tap-node0i2;...;macN,tap-node0iN
if [[ $IRONIC_USE_PORT_GROUP == "True" ]]; then
pg_mac_address=$(echo ${interface_info} | awk -F ';' '{print $1}' | awk -F ',' '{print $1}')
pg_id=$($IRONIC_CMD port group create --node $node_id --address $pg_mac_address --mode ${IRONIC_PORT_GROUP_MODE} -f value -c uuid)
die_if_not_set $LINENO pg_id "Failed to create portgroup"
fi
for info in ${interface_info//;/ }; do
local mac_address=""
local port_id=""
local llc_port_opt=""
local physical_network=""
local portgroup=""
echo "Processing Interface Info $interface_info"
mac_address=$(echo $info| awk -F ',' '{print $1}')
port_id=$(echo $info| awk -F ',' '{print $2}')
if [[ "${IRONIC_USE_LINK_LOCAL}" == "True" ]]; then
if [[ "${IRONIC_NETWORK_SIMULATOR:-ovs}" == "ovs" ]]; then
llc_port_opt+=" --local-link-connection port_id=${port_id} "
else
llc_port_opt+=" --local-link-connection port_id=$(identify_port_for_switch $IRONIC_NETWORK_SIMULATOR $port_id) "
fi
fi
if [[ "${IRONIC_USE_NEUTRON_SEGMENTS}" == "True" ]]; then
physical_network=" --physical-network ${PHYSICAL_NETWORK} "
fi
if [[ $IRONIC_USE_PORT_GROUP == "True" ]]; then
portgroup="--port-group ${pg_id}"
fi
$IRONIC_CMD port create --node $node_id $llc_opts $llc_port_opt $mac_address $physical_network $portgroup
done
# NOTE(vsaienko) use node-update instead of specifying network_interface
# during node creation. If node is added with latest version of API it
# will NOT go to available state automatically.
if [[ -n "${IRONIC_NETWORK_INTERFACE}" ]]; then
$IRONIC_CMD node set $node_id --network-interface $IRONIC_NETWORK_INTERFACE || \
die $LINENO "Failed to update network interface for node"
fi
if [[ -n "${IRONIC_STORAGE_INTERFACE}" ]]; then
$IRONIC_CMD node set $node_id --storage-interface $IRONIC_STORAGE_INTERFACE || \
die $LINENO "Failed to update storage interface for node $node_id"
if [[ -n "${connector_iqn}" ]]; then
$IRONIC_CMD volume connector create --node $node_id --type iqn \
--connector-id $connector_iqn || \
die $LINENO "Failed to create volume connector for node $node_id"
fi
fi
total_nodes=$((total_nodes+1))
done < $ironic_hwinfo_file
# NOTE(hjensas): ensure ironic-neutron-agent has done report_state for all
# nodes we attempt cleaning.
if [[ "${IRONIC_USE_NEUTRON_SEGMENTS}" == "True" ]]; then
wait_for_ironic_neutron_agent_report_state_for_all_nodes $node_uuids
fi
# NOTE(dtantsur): doing it outside of the loop, because of cleaning
provide_nodes $node_uuids
if is_service_enabled nova && [[ "$VIRT_DRIVER" == "ironic" ]]; then
if [[ "$HOST_TOPOLOGY_ROLE" != "subnode" ]]; then
local adjusted_disk
adjusted_disk=$(($ironic_node_disk - $ironic_ephemeral_disk))
openstack --os-cloud $OS_CLOUD flavor create --ephemeral $ironic_ephemeral_disk --ram $ironic_node_ram --disk $adjusted_disk --vcpus $ironic_node_cpu baremetal
local resource_class=${IRONIC_DEFAULT_RESOURCE_CLASS^^}
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "resources:CUSTOM_$resource_class"="1"
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "resources:DISK_GB"="0"
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "resources:MEMORY_MB"="0"
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "resources:VCPU"="0"
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "cpu_arch"="$ironic_node_arch"
if [[ "$IRONIC_BOOT_MODE" == "uefi" ]]; then
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "capabilities:boot_mode"="uefi"
fi
if [[ "$IRONIC_BOOT_MODE" == "bios" ]]; then
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "capabilities:boot_mode"="bios"
fi
for trait in $IRONIC_DEFAULT_TRAITS; do
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "trait:$trait"="required"
done
if [[ "$IRONIC_SECURE_BOOT" == "True" ]]; then
openstack --os-cloud $OS_CLOUD flavor set baremetal --property "capabilities:secure_boot"="true"
fi
# NOTE(dtantsur): sometimes nova compute fails to start with ironic due
# to keystone restarting and not being able to authenticate us.
# Restart it just to be sure (and avoid gate problems like bug 1537076)
stop_nova_compute || /bin/true
# NOTE(pas-ha) if nova compute failed before restart, .failure file
# that was created will fail the service_check in the end of the deployment
_clean_ncpu_failure
start_nova_compute
fi
if [[ "$IRONIC_SHARD_1_NAME" != "$IRONIC_NODE_SHARD_NAME" ]]; then
# If we're running in a disjointed shard configuration, we may see
# only one node, the other compute services are the ones in use,
# which means our total local count is invalid and can't be used.
# So, we just make sure we can see *one* node successfully.
if [[ "$HOST_TOPOLOGY_ROLE" == "subnode" ]]; then
# If we're on a subnode, just wait for at least one node.
# If we're on the controller, we won't see any nodes.
wait_for_nova_resources $total_nodes
fi
# TODO(TheJulia): We should check the primary/controller node
# to ensure that nova-compute doesn't see the node configured
# for it if we are so configured. We would do that as the else
# of the above logic, which *should* never see the ironic baremetal
# node entry by default.
else
# In this case, we're looking to match all the nodes on this host,
# itself. Meaning the tests focus on the use of this node and we
# need to ensure we have all the nodes.
wait_for_nova_resources $total_nodes
fi
fi
}
function die_if_module_not_loaded {
if ! grep -q $1 /proc/modules; then
die $LINENO "$1 kernel module is not loaded"
fi
}
function identify_port_for_switch {
# TODO(TheJulia): The exisence of this function is kind of awful.
# tl;dr, we don't have the port id for ngs, so we *should wire things
# up so inspection can trigger and we can discover these via lldp...
# Ideall, before the fixme so the fixme is just a bug.
# FIXME(TheJulia): this only maps up with 2 ports per VM.
local simulator=$1
local vm_port=$2
case $simulator in
force10_9)
identify_force10_9_port $vm_port
;; # end of force10_9
force10_10)
identify_force10_10_port $vm_port
;;
cisco_nexus9k)
identify_cisco_nexus_port $vm_port
;;
sonic)
identify_sonic_port $vm_port
;;
esac
}
function identify_force10_port {
case $1 in
tap-node-0i1)
echo -n "forty0/4"
;;
tap-node-0i2)
echo -n "forty0/8"
;;
tap-node-1i1)
echo -n "forty0/12"
;;
tap-node-1i2)
echo -n "forty0/16"
;;
tap-node-2i1)
echo -n "forty0/20"
;;
tap-node-2i2)
echo -n "forty0/24"
;;
esac
}
function identify_force10_10_port {
case $1 in
tap-node-0i1)
echo -n "ethernet1/1/2"
;;
tap-node-0i2)
echo -n "ethernet1/1/3"
;;
tap-node-1i1)
echo -n "ethernet1/1/4"
;;
tap-node-1i2)
echo -n "ethernet1/1/5"
;;
tap-node-2i1)
echo -n "ethernet1/1/6"
;;
tap-node-2i2)
echo -n "ethernet1/1/7"
;;
esac
}
function identify_cisco_nexus_port {
case $1 in
tap-node-0i1)
echo -n "ethernet1/2"
;;
tap-node-0i2)
echo -n "ethernet1/3"
;;
tap-node-1i1)
echo -n "ethernet1/4"
;;
tap-node-1i2)
echo -n "ethernet1/5"
;;
tap-node-2i1)
echo -n "ethernet1/6"
;;
tap-node-2i2)
echo -n "ethernet1/7"
;;
esac
}
function identify_sonic_port {
case $1 in
tap-node-0i1)
echo -n "Ethernet4"
;;
tap-node-0i2)
echo -n "Ethernet8"
;;
tap-node-1i1)
echo -n "Ethernet12"
;;
tap-node-1i2)
echo -n "Ethernet16"
;;
tap-node-2i1)
echo -n "Ethernet20"
;;
tap-node-2i2)
echo -n "Ethernet24"
;;
esac
}
function configure_iptables {
# enable tftp natting for allowing connections to HOST_IP's tftp server
if ! running_in_container; then
sudo modprobe nf_conntrack_tftp
sudo modprobe nf_nat_tftp
else
die_if_module_not_loaded nf_conntrack_tftp
die_if_module_not_loaded nf_nat_tftp
fi
################ NETWORK DHCP
# explicitly allow DHCP - packets are occasionally being dropped here
sudo iptables -I INPUT -p udp --dport 67:68 --sport 67:68 -j ACCEPT || true
# nodes boot from TFTP and callback to the API server listening on $HOST_IP
sudo iptables -I INPUT -d $IRONIC_TFTPSERVER_IP -p udp --dport 69 -j ACCEPT || true
# dhcpv6 which is the only way to transmit boot options
sudo ip6tables -I INPUT -d $IRONIC_HOST_IPV6 -p udp --dport 546:547 --sport 546:547 -j ACCEPT || true
sudo ip6tables -I INPUT -d $IRONIC_HOST_IPV6 -p udp --dport 69 -j ACCEPT || true
################ Webserver/API
# To use named /baremetal endpoint we should open default apache port
sudo iptables -I INPUT -d $HOST_IP -p tcp --dport 80 -j ACCEPT || true
sudo iptables -I INPUT -d $HOST_IP -p tcp --dport 443 -j ACCEPT || true
# open ironic API on baremetal network
sudo iptables -I INPUT -d $IRONIC_HTTP_SERVER -p tcp --dport 80 -j ACCEPT || true
sudo ip6tables -I INPUT -d $IRONIC_HTTP_SERVER -p tcp --dport 80 -j ACCEPT || true
sudo iptables -I INPUT -d $IRONIC_HTTP_SERVER -p tcp --dport 443 -j ACCEPT || true
sudo ip6tables -I INPUT -d $IRONIC_HTTP_SERVER -p tcp --dport 443 -j ACCEPT || true
if is_deployed_by_agent; then
# agent ramdisk gets instance image from swift
sudo iptables -I INPUT -d $HOST_IP -p tcp --dport ${SWIFT_DEFAULT_BIND_PORT:-8080} -j ACCEPT || true
sudo iptables -I INPUT -d $HOST_IP -p tcp --dport $GLANCE_SERVICE_PORT -j ACCEPT || true
fi
if is_http_server_required; then
sudo iptables -I INPUT -d $IRONIC_HTTP_SERVER -p tcp --dport $IRONIC_HTTP_PORT -j ACCEPT || true
sudo ip6tables -I INPUT -d $IRONIC_HOST_IPV6 -p tcp --dport $IRONIC_HTTP_PORT -j ACCEPT || true
fi
if [[ "${IRONIC_STORAGE_INTERFACE}" == "cinder" ]]; then
sudo iptables -I INPUT -d $HOST_IP -p tcp --dport $ISCSI_SERVICE_PORT -s $FLOATING_RANGE -j ACCEPT || true
fi
# (rpittau) workaround to allow TFTP traffic on ubuntu bionic with conntrack helper disabled
local qrouter
qrouter=$(sudo ip netns list | grep qrouter | awk '{print $1;}')
if [[ ! -z "$qrouter" ]]; then
sudo ip netns exec $qrouter iptables -A PREROUTING -t raw -p udp --dport 69 -j CT --helper tftp
sudo ip netns exec $qrouter ip6tables -A PREROUTING -t raw -p udp --dport 69 -j CT --helper tftp || true
fi
}
function configure_tftpd {
if [[ "${IRONIC_ENABLE_TFTP:-True}" == "True" ]]; then
# stop tftpd and setup serving via xinetd
stop_service tftpd-hpa || true
[ -f /etc/init/tftpd-hpa.conf ] && echo "manual" | sudo tee /etc/init/tftpd-hpa.override
sudo cp $IRONIC_TEMPLATES_DIR/tftpd-xinetd.template /etc/xinetd.d/tftp
sudo sed -e "s|%TFTPBOOT_DIR%|$IRONIC_TFTPBOOT_DIR|g" -i /etc/xinetd.d/tftp
sudo sed -e "s|%MAX_BLOCKSIZE%|$IRONIC_TFTP_BLOCKSIZE|g" -i /etc/xinetd.d/tftp
if [[ "$IRONIC_IP_VERSION" == '6' ]]; then
sudo sed -e "s|IPv4|IPv6|g" -i /etc/xinetd.d/tftp
fi
# setup tftp file mapping to satisfy requests at the root (booting) and
# /tftpboot/ sub-dir (as per deploy-ironic elements)
# this section is only for ubuntu and fedora
if [[ "$IRONIC_IPXE_ENABLED" == "False" && \
( "$IRONIC_BOOT_MODE" == "uefi" || "$IRONIC_SECURE_BOOT" == "True" ) && \
"$IRONIC_UEFI_BOOT_LOADER" == "grub2" ]]; then
echo "re ^($IRONIC_TFTPBOOT_DIR/) $IRONIC_TFTPBOOT_DIR/\2" >$IRONIC_TFTPBOOT_DIR/map-file
echo "re ^$IRONIC_TFTPBOOT_DIR/ $IRONIC_TFTPBOOT_DIR/" >>$IRONIC_TFTPBOOT_DIR/map-file
echo "re ^(^/) $IRONIC_TFTPBOOT_DIR/\1" >>$IRONIC_TFTPBOOT_DIR/map-file
echo "re ^([^/]) $IRONIC_TFTPBOOT_DIR/\1" >>$IRONIC_TFTPBOOT_DIR/map-file
else
echo "r ^([^/]) $IRONIC_TFTPBOOT_DIR/\1" >$IRONIC_TFTPBOOT_DIR/map-file
echo "r ^(/tftpboot/) $IRONIC_TFTPBOOT_DIR/\2" >>$IRONIC_TFTPBOOT_DIR/map-file
fi
sudo chmod -R 0755 $IRONIC_TFTPBOOT_DIR
restart_service xinetd
fi
}
function build_ipa_ramdisk {
local kernel_path=$1
local ramdisk_path=$2
local iso_path=$3
case $IRONIC_RAMDISK_TYPE in
'tinyipa')
build_tinyipa_ramdisk $kernel_path $ramdisk_path $iso_path
;;
'dib')
build_ipa_dib_ramdisk $kernel_path $ramdisk_path $iso_path
;;
*)
die $LINENO "Unrecognised IRONIC_RAMDISK_TYPE: $IRONIC_RAMDISK_TYPE. Expected either of 'dib' or 'tinyipa'."
;;
esac
}
function setup_ipa_builder {
git_clone $IRONIC_PYTHON_AGENT_BUILDER_REPO $IRONIC_PYTHON_AGENT_BUILDER_DIR $IRONIC_PYTHON_AGENT_BUILDER_BRANCH
}
function build_tinyipa_ramdisk {
echo "Building ironic-python-agent deploy ramdisk"
local kernel_path=$1
local ramdisk_path=$2
local iso_path=$3
cd $IRONIC_PYTHON_AGENT_BUILDER_DIR/tinyipa
export BUILD_AND_INSTALL_TINYIPA=true
if is_ansible_deploy_enabled; then
export AUTHORIZE_SSH=true
export SSH_PUBLIC_KEY=$IRONIC_ANSIBLE_SSH_KEY.pub
fi
make
cp tinyipa.gz $ramdisk_path
cp tinyipa.vmlinuz $kernel_path
if is_deploy_iso_required; then
make iso
cp tinyipa.iso $iso_path
fi
make clean
cd -
}
function rebuild_tinyipa_for_ansible {
local ansible_tinyipa_ramdisk_name
pushd $IRONIC_PYTHON_AGENT_BUILDER_DIR/tinyipa
export TINYIPA_RAMDISK_FILE=$IRONIC_DEPLOY_RAMDISK
export SSH_PUBLIC_KEY=$IRONIC_ANSIBLE_SSH_KEY.pub
make addssh
ansible_tinyipa_ramdisk_name="ansible-$(basename $IRONIC_DEPLOY_RAMDISK)"
mv $ansible_tinyipa_ramdisk_name $TOP_DIR/files
make clean
popd
IRONIC_DEPLOY_RAMDISK=$TOP_DIR/files/$ansible_tinyipa_ramdisk_name
}
# install_diskimage_builder() - Collect source and prepare or install from pip
function install_diskimage_builder {
if use_library_from_git "diskimage-builder"; then
git_clone_by_name "diskimage-builder"
setup_dev_lib -bindep "diskimage-builder"
else
local bindep_file
bindep_file=$(mktemp)
curl -o "$bindep_file" "$IRONIC_DIB_BINDEP_FILE"
install_bindep "$bindep_file"
pip_install_gr "diskimage-builder"
fi
}
function build_ipa_dib_ramdisk {
local kernel_path=$1
local ramdisk_path=$2
local iso_path=$3
local tempdir
tempdir=$(mktemp -d --tmpdir=${DEST})
# install diskimage-builder if not present
if ! $(type -P disk-image-create > /dev/null); then
install_diskimage_builder
fi
echo "Building IPA ramdisk with DIB options: $IRONIC_DIB_RAMDISK_OPTIONS"
if is_deploy_iso_required; then
IRONIC_DIB_RAMDISK_OPTIONS+=" iso"
fi
if is_ansible_deploy_enabled; then
IRONIC_DIB_RAMDISK_OPTIONS+=" devuser"
fi
git_clone $IRONIC_PYTHON_AGENT_BUILDER_REPO $IRONIC_PYTHON_AGENT_BUILDER_DIR $IRONIC_PYTHON_AGENT_BUILDER_BRANCH
ELEMENTS_PATH="$IRONIC_PYTHON_AGENT_BUILDER_DIR/dib" \
DIB_CLOUD_INIT_DATASOURCES="ConfigDrive,openstack" \
DIB_DHCP_TIMEOUT=$IRONIC_DIB_DHCP_TIMEOUT \
DIB_RELEASE=$IRONIC_DIB_RAMDISK_RELEASE \
DIB_REPOLOCATION_ironic_python_agent="$IRONIC_PYTHON_AGENT_DIR" \
DIB_REPOREF_ironic_python_agent=$TARGET_BRANCH \
DIB_REPOLOCATION_requirements="$DEST/requirements" \
DIB_REPOREF_requirements=$TARGET_BRANCH \
DIB_DEVUSER_PWDLESS_SUDO=yes \
DIB_DEVUSER_AUTHORIZED_KEYS=$IRONIC_ANSIBLE_SSH_KEY.pub \
disk-image-create "$IRONIC_DIB_RAMDISK_OPTIONS" \
-x -o "$tempdir/ironic-agent" \
ironic-python-agent-ramdisk
chmod -R +r $tempdir
mv "$tempdir/ironic-agent.kernel" "$kernel_path"
mv "$tempdir/ironic-agent.initramfs" "$ramdisk_path"
if is_deploy_iso_required; then
mv "$tempdir/ironic-agent.iso" "$iso_path"
fi
rm -rf $tempdir
}
function build_wholedisk_image {
local img_path=$1
local tempdir
tempdir=$(mktemp -d --tmpdir=${DEST})
# install diskimage-builder if not present
if ! $(type -P disk-image-create > /dev/null); then
install_diskimage_builder
fi
echo "Building wholedisk DIB image options: $IRONIC_DIB_OPTIONS block-device-efi bootloader"
# Build a bootable UEFI image iniit tooling .
DIB_DHCP_TIMEOUT=$IRONIC_DIB_DHCP_TIMEOUT \
DIB_RELEASE=$IRONIC_DIB_RELEASE \
DIB_SIMPLE_INIT_NO_DHCP_FALLBACK=1 \
DIB_CLOUD_INIT_DATASOURCES="ConfigDrive,openstack" \
disk-image-create "$IRONIC_DIB_OPTIONS" block-device-efi bootloader \
-x -o "$tempdir/wholedisk"
chmod -R +r $tempdir
mv "$tempdir/wholedisk.qcow2" "$img_path"
rm -rf $tempdir
}
function upload_baremetal_ironic_wholedisk {
disk_img=$1
disk_img_name=$2
if [[ "$HOST_TOPOLOGY_ROLE" != "subnode" ]]; then
echo_summary "Uploading baremetal images for ironic"
wholedisk_image_id=$(openstack --os-cloud devstack-admin \
image create \
$(basename $disk_img_name) \
--public --disk-format=qcow2 \
--container-format=bare \
--file $(readlink -f "$disk_img") -f value -c id)
die_if_not_set $LINENO wholedisk_image_id "Failed to load wholedisk image into glance"
fi
}
function build_dib_image {
local target_path=$1
local tempdir
tempdir=$(mktemp -d --tmpdir=${DEST})
# install diskimage-builder if not present
if ! $(type -P disk-image-create > /dev/null); then
install_diskimage_builder
fi
DIB_GROWVOLS_TRIGGER=systemd \
DIB_GROWVOLS_ARGS="img-rootfs=60%" \
DIB_BLOCK_SIZE=$IRONIC_VM_BLOCK_SIZE \
DIB_RELEASE=$IRONIC_DIB_IMAGE_RELEASE \
DIB_BLOCK_DEVICE_CONFIG=file://$IRONIC_DEVSTACK_FILES_DIR/ci-block-device-for-lvm.yaml \
disk-image-create "$IRONIC_DIB_IMAGE_OPTIONS" \
-x -t qcow2 -o "$tempdir/target-os-image" --image-size 7
chmod -R +r $tempdir
mv "$tempdir/target-os-image.qcow2" "$target_path"
rm -rf $tempdir
}
function upload_dib_image_if_needed {
if [[ "$IRONIC_BUILD_DIB_IMAGE" == "True" ]] && is_service_enabled glance; then
echo "Building a DIB image suitable for local boot"
IRONIC_IMAGE_NAME="ironic-dib-image.img"
local dest
dest="$FILES/$IRONIC_IMAGE_NAME"
build_dib_image "$dest"
openstack image create $IRONIC_IMAGE_NAME \
--public --disk-format qcow2 --container-format bare \
--file $(readlink -f "$dest")
# Change the default image only if the provided settings prevent the
# default cirros image from working.
if [[ "$IRONIC_TEMPEST_WHOLE_DISK_IMAGE" != True ]]; then
IRONIC_IMAGE_NAME=$IRONIC_IMAGE_NAME
DEFAULT_IMAGE_NAME=$IRONIC_IMAGE_NAME
IRONIC_WHOLEDISK_IMAGE_NAME=$IRONIC_IMAGE_NAME
fi
fi
}
# download EFI boot loader image and upload it to glance
# this function sets ``IRONIC_EFIBOOT_ID``
function upload_baremetal_ironic_efiboot {
declare -g IRONIC_EFIBOOT_ID
local efiboot_name
efiboot_name=$(basename $IRONIC_EFIBOOT)
echo_summary "Building and uploading EFI boot image for ironic"
if [ ! -e "$IRONIC_EFIBOOT" ]; then
# NOTE(dtantsur): update doc/source/admin/drivers/redfish.rst when
# changing this procedure.
local efiboot_path
efiboot_path=$(mktemp -d --tmpdir=${DEST})/$efiboot_name
local efiboot_mount
efiboot_mount=$(mktemp -d --tmpdir=${DEST})
dd if=/dev/zero \
of=$efiboot_path \
bs=4096 count=1024
mkfs.fat -s 4 -r 512 -S 4096 $efiboot_path
sudo mount $efiboot_path $efiboot_mount
sudo mkdir -p $efiboot_mount/efi/boot
if [[ "$IRONIC_GRUB2_SHIM_FILE" =~ "http".* ]]; then
sudo wget "$IRONIC_GRUB2_SHIM_FILE" -O $efiboot_mount/efi/boot/boot${GRUB_ARCH}.efi
else
sudo cp "$IRONIC_GRUB2_SHIM_FILE" $efiboot_mount/efi/boot/boot${GRUB_ARCH}.efi
fi
if [[ "$IRONIC_GRUB2_FILE" =~ "http".* ]]; then
sudo wget "$IRONIC_GRUB2_FILE" -O $efiboot_mount/efi/boot/grub${GRUB_ARCH}.efi
else
sudo cp "$IRONIC_GRUB2_FILE" $efiboot_mount/efi/boot/grub${GRUB_ARCH}.efi
fi
sudo umount $efiboot_mount
mv $efiboot_path $IRONIC_EFIBOOT
fi
# load efiboot into glance
# NOTE(TheJulia): Glance requires a project ID be submitted with the
# request *or* we just do it as the project scoped admin using the admin
# project which in devstack's case is the demo project.
# In other words, we can't use devstack-system-admin to upload the image
# unless we set the project_id in the create request.
IRONIC_EFIBOOT_ID=$(openstack --os-cloud devstack-admin \
image create \
$efiboot_name \
--public --disk-format=raw \
--container-format=bare \
-f value -c id \
--file $(readlink -f $IRONIC_EFIBOOT))
die_if_not_set $LINENO IRONIC_EFIBOOT_ID "Failed to load EFI bootloader image into glance"
iniset $IRONIC_CONF_FILE conductor bootloader $IRONIC_EFIBOOT_ID
local efi_grub_path
if is_ubuntu; then
efi_grub_path=EFI/ubuntu/grub.cfg
elif is_fedora; then
if grep -qi CentOS /etc/redhat-release; then
efi_grub_path=EFI/centos/grub.cfg
else
efi_grub_path=EFI/fedora/grub.cfg
fi
else
# NOTE(dtantsur): this is likely incorrect
efi_grub_path=EFI/BOOT/grub.cfg
fi
iniset $IRONIC_CONF_FILE DEFAULT grub_config_path ${IRONIC_GRUB2_CONFIG_PATH:-$efi_grub_path}
}
# build deploy kernel+ramdisk, then upload them to glance
# this function sets ``IRONIC_DEPLOY_KERNEL_ID``, ``IRONIC_DEPLOY_RAMDISK_ID``
function upload_baremetal_ironic_deploy {
declare -g IRONIC_DEPLOY_KERNEL_ID IRONIC_DEPLOY_RAMDISK_ID
local ironic_deploy_kernel_name
local ironic_deploy_ramdisk_name
ironic_deploy_kernel_name=$(basename $IRONIC_DEPLOY_KERNEL)
ironic_deploy_ramdisk_name=$(basename $IRONIC_DEPLOY_RAMDISK)
if [[ "$HOST_TOPOLOGY_ROLE" != "subnode" ]]; then
echo_summary "Creating and uploading baremetal images for ironic"
if [ ! -e "$IRONIC_DEPLOY_RAMDISK" ] || \
[ ! -e "$IRONIC_DEPLOY_KERNEL" ] || \
( is_deploy_iso_required && [ ! -e "$IRONIC_DEPLOY_ISO" ] ); then
# setup IRONIC_PYTHON_AGENT_BUILDER_DIR
setup_ipa_builder
# files don't exist, need to build them
if [ "$IRONIC_BUILD_DEPLOY_RAMDISK" = "True" ]; then
# we can build them only if we're not offline
if [ "$OFFLINE" != "True" ]; then
build_ipa_ramdisk $IRONIC_DEPLOY_KERNEL $IRONIC_DEPLOY_RAMDISK $IRONIC_DEPLOY_ISO
else
die $LINENO "Deploy kernel+ramdisk or iso files don't exist and cannot be built in OFFLINE mode"
fi
else
# Grab the agent image tarball, either from a local file or remote URL
if [[ "$IRONIC_AGENT_KERNEL_URL" =~ "file://" ]]; then
cp ${IRONIC_AGENT_KERNEL_URL:7} $IRONIC_DEPLOY_KERNEL
else
download_with_retry "$IRONIC_AGENT_KERNEL_URL" $IRONIC_DEPLOY_KERNEL
fi
if [[ "$IRONIC_AGENT_RAMDISK_URL" =~ "file://" ]]; then
cp ${IRONIC_AGENT_RAMDISK_URL:7} $IRONIC_DEPLOY_RAMDISK
else
download_with_retry "$IRONIC_AGENT_RAMDISK_URL" $IRONIC_DEPLOY_RAMDISK
fi
if is_ansible_with_tinyipa; then
# NOTE(pas-ha) if using ansible-deploy and tinyipa,
# this will rebuild ramdisk and override $IRONIC_DEPLOY_RAMDISK
rebuild_tinyipa_for_ansible
fi
fi
fi
# load them into glance
if ! is_deploy_iso_required; then
IRONIC_DEPLOY_KERNEL_ID=$(openstack --os-cloud devstack-admin \
image create \
$ironic_deploy_kernel_name \
--public --disk-format=raw \
--container-format=bare \
--file $(readlink -f $IRONIC_DEPLOY_KERNEL) | \
grep ' id ' | get_field 2)
die_if_not_set $LINENO IRONIC_DEPLOY_KERNEL_ID "Failed to load kernel image into glance"
IRONIC_DEPLOY_RAMDISK_ID=$(openstack --os-cloud devstack-admin \
image create \
$ironic_deploy_ramdisk_name \
--public --disk-format=raw \
--container-format=bare \
--file $(readlink -f $IRONIC_DEPLOY_RAMDISK) | \
grep ' id ' | get_field 2)
die_if_not_set $LINENO IRONIC_DEPLOY_RAMDISK_ID "Failed to load ramdisk image into glance"
else
IRONIC_DEPLOY_ISO_ID=$(openstack --os-cloud devstack-admin \
image create \
$(basename $IRONIC_DEPLOY_ISO) \
--public --disk-format=iso \
--container-format=bare \
--file $(readlink -f $IRONIC_DEPLOY_ISO) -f value -c id)
die_if_not_set $LINENO IRONIC_DEPLOY_ISO_ID "Failed to load deploy iso into glance"
fi
else
if is_ansible_with_tinyipa; then
ironic_deploy_ramdisk_name="ansible-$ironic_deploy_ramdisk_name"
fi
IRONIC_DEPLOY_KERNEL_ID=$(openstack --os-cloud $OS_CLOUD image show $ironic_deploy_kernel_name -f value -c id)
IRONIC_DEPLOY_RAMDISK_ID=$(openstack --os-cloud $OS_CLOUD image show $ironic_deploy_ramdisk_name -f value -c id)
fi
iniset $IRONIC_CONF_FILE conductor deploy_kernel $IRONIC_DEPLOY_KERNEL_ID
iniset $IRONIC_CONF_FILE conductor deploy_ramdisk $IRONIC_DEPLOY_RAMDISK_ID
iniset $IRONIC_CONF_FILE conductor rescue_kernel $IRONIC_DEPLOY_KERNEL_ID
iniset $IRONIC_CONF_FILE conductor rescue_ramdisk $IRONIC_DEPLOY_RAMDISK_ID
if [[ "${IRONIC_ANACONDA_INSECURE_HEARTBEAT:-}" != "" ]]; then
iniset $IRONIC_CONF_FILE anaconda insecure_heartbeat ${IRONIC_ANACONDA_INSECURE_HEARTBEAT:-}
fi
# NOTE(TheJulia): Compared to an image deploy, anaconda is relatively
# slow as it installs packages one at a time. As such, we need an option
# to extend.
if [[ "${IRONIC_DEPLOY_CALLBACK_WAIT_TIMEOUT:-}" != "" ]]; then
iniset $IRONIC_CONF_FILE conductor deploy_callback_timeout ${IRONIC_DEPLOY_CALLBACK_WAIT_TIMEOUT:-}
fi
}
function prepare_baremetal_basic_ops {
if [[ "$IRONIC_BAREMETAL_BASIC_OPS" != "True" ]]; then
return 0
fi
if [[ "${IRONIC_BUILD_WHOLEDISK:-False}" == "True" ]]; then
local dib_file_path
dib_file_path=$FILES/ironic-wholedisk-image.qcow2
build_wholedisk_image $dib_file_path
echo "WARNING: Overriding supplied IRONIC_WHOLEDISK_IMAGE_NAME as we have built an image as requested."
IRONIC_WHOLEDISK_IMAGE_NAME="ironic-wholedisk-image.qcow2"
upload_baremetal_ironic_wholedisk $dib_file_path $IRONIC_WHOLEDISK_IMAGE_NAME
fi
if ! is_service_enabled nova && is_http_server_required; then
local image_file_path
if [[ ${IRONIC_WHOLEDISK_IMAGE_NAME} =~ \.img$ ]] || [ -f $FILES/${IRONIC_WHOLEDISK_IMAGE_NAME} ]; then
image_file_path=$FILES/${IRONIC_WHOLEDISK_IMAGE_NAME}
else
image_file_path=$FILES/${IRONIC_WHOLEDISK_IMAGE_NAME}.img
fi
sudo install -g $LIBVIRT_GROUP -o $STACK_USER -m 644 $image_file_path $IRONIC_HTTP_DIR
fi
upload_baremetal_ironic_deploy
if [[ "$IRONIC_BOOT_MODE" == "uefi" && is_deployed_by_redfish ]]; then
upload_baremetal_ironic_efiboot
fi
upload_dib_image_if_needed
configure_tftpd
configure_iptables
}
function cleanup_baremetal_basic_ops {
if [[ "$IRONIC_BAREMETAL_BASIC_OPS" != "True" ]]; then
return 0
fi
rm -f $IRONIC_VM_MACS_CSV_FILE
sudo rm -rf $IRONIC_DATA_DIR $IRONIC_STATE_PATH
local vm_name
for vm_name in $(_ironic_bm_vm_names); do
# Delete the Virtual BMCs
if is_deployed_by_ipmi; then
vbmc --no-daemon list | grep -a $vm_name && vbmc --no-daemon delete $vm_name || /bin/true
fi
# pick up the $LIBVIRT_GROUP we have possibly joint
newgrp $LIBVIRT_GROUP <<SUBSHELL
$IRONIC_SCRIPTS_DIR/cleanup-node.sh $vm_name
SUBSHELL
# Cleanup node bridge/interfaces
for i in $(seq 1 $IRONIC_VM_INTERFACE_COUNT); do
sudo ip link del dev tap-${vm_name}i${i}
done
done
sudo ovs-vsctl --if-exists del-br $IRONIC_VM_NETWORK_BRIDGE
sudo rm -rf /etc/xinetd.d/tftp /etc/init/tftpd-hpa.override
restart_service xinetd
sudo iptables -D INPUT -d $HOST_IP -p udp --dport 69 -j ACCEPT || true
sudo iptables -D INPUT -d $HOST_IP -p tcp --dport $IRONIC_SERVICE_PORT -j ACCEPT || true
sudo iptables -D INPUT -d $HOST_IP -p tcp --dport 80 -j ACCEPT || true
sudo iptables -D INPUT -d $HOST_IP -p tcp --dport 443 -j ACCEPT || true
if is_deployed_by_agent; then
# agent ramdisk gets instance image from swift
sudo iptables -D INPUT -d $HOST_IP -p tcp --dport ${SWIFT_DEFAULT_BIND_PORT:-8080} -j ACCEPT || true
fi
sudo rmmod nf_conntrack_tftp || true
sudo rmmod nf_nat_tftp || true
}
function ironic_configure_tempest {
iniset $TEMPEST_CONFIG service_available ironic True
if [[ -n "$TEMPEST_BAREMETAL_MIN_MICROVERSION" ]]; then
iniset $TEMPEST_CONFIG baremetal min_microversion $TEMPEST_BAREMETAL_MIN_MICROVERSION
fi
if [[ -n "$TEMPEST_BAREMETAL_MAX_MICROVERSION" ]]; then
iniset $TEMPEST_CONFIG baremetal max_microversion $TEMPEST_BAREMETAL_MAX_MICROVERSION
fi
if [[ -n "$IRONIC_PING_TIMEOUT" ]]; then
iniset $TEMPEST_CONFIG validation ping_timeout $IRONIC_PING_TIMEOUT
fi
if [[ -n "$IRONIC_IP_VERSION" ]]; then
iniset $TEMPEST_CONFIG validation ip_version_for_ssh $IRONIC_IP_VERSION
fi
if [[ -n "$IRONIC_BOOT_MODE" ]]; then
iniset $TEMPEST_CONFIG baremetal boot_mode $IRONIC_BOOT_MODE
fi
if [[ "$IRONIC_IP_VERSION" == "6" ]]; then
# No FIPs in V6 and we dynamically create networks...
# network_for_ssh is defaulted to public
iniset $TEMPEST_CONFIG validation network_for_ssh
iniset $TEMPEST_CONFIG validation connect_method fixed
iniset $TEMPEST_CONFIG network ipv6-private-subnet
if [ -n "${PUBLIC_ROUTER_ID:-}" ] ; then
# For IPv6 tempest is going to use a precreated router for
# access to the tenant networks (as we have set up routes to it)
# it needs to know the ID of the router and be admin to attach to it
iniset $TEMPEST_CONFIG network public_router_id $PUBLIC_ROUTER_ID
iniset $TEMPEST_CONFIG auth tempest_roles "admin"
fi
fi
if is_service_enabled nova; then
local bm_flavor_id
bm_flavor_id=$(openstack --os-cloud devstack-admin flavor show baremetal -f value -c id)
die_if_not_set $LINENO bm_flavor_id "Failed to get id of baremetal flavor"
iniset $TEMPEST_CONFIG compute flavor_ref $bm_flavor_id
iniset $TEMPEST_CONFIG compute flavor_ref_alt $bm_flavor_id
iniset $TEMPEST_CONFIG compute build_interval 10
iniset $TEMPEST_CONFIG compute-feature-enabled disk_config False
if [[ "$IRONIC_NETWORK_INTERFACE" == "neutron" && $IRONIC_VM_INTERFACE_COUNT -gt 1 ]]; then
iniset $TEMPEST_CONFIG compute-feature-enabled interface_attach True
else
iniset $TEMPEST_CONFIG compute-feature-enabled interface_attach False
fi
fi
# NOTE(jlvillal): If IRONIC_PROVISION_NETWORK_NAME is set it means that
# nodes are using the neutron network driver / multi-tenant networking.
# Otherwise we are using a flat-network.
if [[ -n "${IRONIC_PROVISION_NETWORK_NAME}" ]]; then
# multi-tenant networking
iniset $TEMPEST_CONFIG baremetal use_provision_network True
else
# flat-network
iniset $TEMPEST_CONFIG compute fixed_network_name $PRIVATE_NETWORK_NAME
# NOTE(jroll) this disables multitenant network tests from tempest's
# tree, but not from our tree. This is a bit inconsistent, we should
# fix it.
iniset $TEMPEST_CONFIG auth create_isolated_networks False
iniset $TEMPEST_CONFIG network shared_physical_network True
fi
if is_service_enabled glance; then
local image_uuid
image_uuid=$(openstack --os-cloud $OS_CLOUD image show $IRONIC_IMAGE_NAME -f value -c id)
iniset $TEMPEST_CONFIG compute image_ref $image_uuid
iniset $TEMPEST_CONFIG compute image_ref_alt $image_uuid
image_uuid=$(openstack --os-cloud $OS_CLOUD image show $IRONIC_WHOLEDISK_IMAGE_NAME -f value -c id)
iniset $TEMPEST_CONFIG baremetal whole_disk_image_ref $image_uuid
image_uuid=$(openstack --os-cloud $OS_CLOUD image show $IRONIC_PARTITIONED_IMAGE_NAME -f value -c id)
iniset $TEMPEST_CONFIG baremetal partition_image_ref $image_uuid
fi
# Our cirros images cannot do local boot and we don't support
# Netboot anymore, but the conditional is still used because
# some boot constraints exist around cirros. i.e. the setting
# likely should just be "don't attempt the partition boot test.
if [[ "${IRONIC_PARTITIONED_IMAGE_NAME}" =~ cirros ]]; then
iniset $TEMPEST_CONFIG baremetal partition_netboot True
else
iniset $TEMPEST_CONFIG baremetal partition_netboot False
fi
if [[ "$IRONIC_IP_VERSION" == "6" ]]; then
iniset $TEMPEST_CONFIG baremetal whole_disk_image_url "http://$IRONIC_HOST_IPV6:$IRONIC_HTTP_PORT/${IRONIC_WHOLEDISK_IMAGE_NAME}.img"
else
iniset $TEMPEST_CONFIG baremetal whole_disk_image_url "http://$IRONIC_HTTP_SERVER:$IRONIC_HTTP_PORT/${IRONIC_WHOLEDISK_IMAGE_NAME}.img"
fi
iniset $TEMPEST_CONFIG baremetal whole_disk_image_checksum $(sha256sum $FILES/${IRONIC_WHOLEDISK_IMAGE_NAME}.img)
if [[ "$IRONIC_RAMDISK_IMAGE" != "" ]]; then
iniset $TEMPEST_CONFIG baremetal ramdisk_iso_image_ref "$IRONIC_RAMDISK_IMAGE"
fi
if [[ "${IRONIC_ANACONDA_IMAGE_REF:-}" != "" ]]; then
# In a perfect world we would use *just* the opendev repo
# mirror, and let things be magical, but OpenDev Infra cannot
# mirror the /images path with the limited storage space.
iniset $TEMPEST_CONFIG baremetal anaconda_image_ref ${IRONIC_ANACONDA_IMAGE_REF:-}
fi
if [[ "${IRONIC_ANACONDA_KERNEL_REF:-}" != "" ]]; then
iniset $TEMPEST_CONFIG baremetal anaconda_kernel_ref ${IRONIC_ANACONDA_KERNEL_REF:-}
fi
if [[ "${IRONIC_ANACONDA_RAMDISK_REF:-}" != "" ]]; then
iniset $TEMPEST_CONFIG baremetal anaconda_initial_ramdisk_ref ${IRONIC_ANACONDA_RAMDISK_REF:-}
fi
if [[ "${IRONIC_ANACONDA_STAGE2_REF:-}" != "" ]]; then
iniset $TEMPEST_CONFIG baremetal anaconda_stage2_ramdisk_ref ${IRONIC_ANACONDA_STAGE2_REF:-}
fi
# NOTE(dtantsur): keep this option here until the defaults change in
# ironic-tempest-plugin to disable classic drivers testing.
iniset $TEMPEST_CONFIG baremetal enabled_drivers ""
iniset $TEMPEST_CONFIG baremetal enabled_hardware_types $IRONIC_ENABLED_HARDWARE_TYPES
iniset $TEMPEST_CONFIG baremetal enabled_bios_interfaces $IRONIC_ENABLED_BIOS_INTERFACES
iniset $TEMPEST_CONFIG baremetal enabled_deploy_interfaces $IRONIC_ENABLED_DEPLOY_INTERFACES
iniset $TEMPEST_CONFIG baremetal enabled_boot_interfaces $IRONIC_ENABLED_BOOT_INTERFACES
iniset $TEMPEST_CONFIG baremetal enabled_rescue_interfaces $IRONIC_ENABLED_RESCUE_INTERFACES
iniset $TEMPEST_CONFIG baremetal enabled_inspect_interfaces $IRONIC_ENABLED_INSPECT_INTERFACES
iniset $TEMPEST_CONFIG baremetal default_rescue_interface $IRONIC_DEFAULT_RESCUE_INTERFACE
# Driver for API tests
iniset $TEMPEST_CONFIG baremetal driver fake-hardware
# NOTE(dtantsur): remove this when the tempest plugin no longer supports
# netboot (i.e. when Zed is the oldest supported branch).
iniset $TEMPEST_CONFIG baremetal default_boot_option local
local adjusted_root_disk_size_gb
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
adjusted_root_disk_size_gb=$(( ${IRONIC_VM_SPECS_DISK} - ${IRONIC_VM_EPHEMERAL_DISK} ))
else
adjusted_root_disk_size_gb=$(( ${IRONIC_HW_NODE_DISK} - ${IRONIC_HW_EPHEMERAL_DISK} ))
fi
iniset $TEMPEST_CONFIG baremetal adjusted_root_disk_size_gb $adjusted_root_disk_size_gb
if [[ -n "${IRONIC_TEMPEST_BUILD_TIMEOUT}" ]]; then
iniset $TEMPEST_CONFIG baremetal unprovision_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT
iniset $TEMPEST_CONFIG baremetal active_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT
iniset $TEMPEST_CONFIG baremetal deploywait_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT
iniset $TEMPEST_CONFIG baremetal power_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT
iniset $TEMPEST_CONFIG baremetal rescue_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT
iniset $TEMPEST_CONFIG baremetal unrescue_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT
iniset $TEMPEST_CONFIG baremetal inspect_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT
fi
# Set networking parameters for dhcp-less vmedia testing
iniset $TEMPEST_CONFIG baremetal public_subnet_id $PUBLIC_SUBNET_ID
iniset $TEMPEST_CONFIG baremetal public_subnet_ip $PUBLIC_SUBNET_IP
if [[ $IRONIC_VM_VOLUME_COUNT -gt 1 ]]; then
iniset $TEMPEST_CONFIG baremetal_feature_enabled software_raid True
iniset $TEMPEST_CONFIG baremetal_feature_enabled deploy_time_raid True
fi
# Enabled features
iniset $TEMPEST_CONFIG baremetal_feature_enabled ipxe_enabled $IRONIC_IPXE_ENABLED
iniset $TEMPEST_CONFIG baremetal_feature_enabled fast_track_discovery $IRONIC_DEPLOY_FAST_TRACK
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
# Adoption requires reading or guessing the BMC credentials, so let's
# not enable it for real hardware, at least for now.
iniset $TEMPEST_CONFIG baremetal_feature_enabled adoption True
fi
if [[ "$IRONIC_ENFORCE_SCOPE" == "True" ]]; then
iniset $TEMPEST_CONFIG enforce_scope ironic True
iniset $TEMPEST_CONFIG enforce_scope ironic_inspector True
fi
}
function get_ironic_node_prefix {
local node_prefix="node"
if [[ "$HOST_TOPOLOGY_ROLE" == "subnode" ]]; then
node_prefix="$(cat /etc/devstack-host|sed s/compute/c/)node"
fi
echo $node_prefix
}
function setup_vxlan_network {
sudo ip link add phy-brbm-infra type bridge
sudo ip link set dev phy-brbm-infra up
sudo ovs-vsctl add-port $IRONIC_VM_NETWORK_BRIDGE phy-brbm-infra
sudo ip link add phy-infra-brbm type bridge
sudo ip link set dev phy-infra-brbm up
sudo ovs-vsctl add-port $PUBLIC_BRIDGE phy-infra-brbm
sudo ovs-vsctl set interface phy-brbm-infra type=patch
sudo ovs-vsctl set interface phy-infra-brbm type=patch
sudo ovs-vsctl set interface phy-infra-brbm options:peer=phy-brbm-infra
sudo ovs-vsctl set interface phy-brbm-infra options:peer=phy-infra-brbm
}
function downgrade_dnsmasq {
if is_ubuntu; then
# NOTE(JayF): This checks out the unreleased dnsmasq commit which
# includes the fix for the crash issue described in
# https://bugs.launchpad.net/ironic/+bug/2026757
# This patch has yet to be included in a release.
if [[ -f /opt/stack/.patched-dnsmasq ]]; then
# NOTE(JayF) Keep Ironic devstack plugin idempotent by skipping
# downgrade if we already have the desired version.
echo "Dnsmasq already downgraded, continuing..."
else
echo "Downgrading dnsmasq..."
sudo dpkg -r dnsmasq dnsmasq-base
# NOTE(JayF) Cannot shallow clone; need to checkout a commit that
# is not part of a named tag
DNSMASQ_DIR=$(mktemp --tmpdir -u dnsmasq.XXXXXX)
git clone http://thekelleys.org.uk/git/dnsmasq.git $DNSMASQ_DIR
pushd $DNSMASQ_DIR
# https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commitdiff;h=f006be7842104a9f86fbf419326b7aad08ade61d
# above is the specific patch we need; but using the 2.91 RC which
# includes that (and more)
git checkout v2.91rc5
sudo make PREFIX=/usr install
sudo touch /opt/stack/.patched-dnsmasq
popd
rm -rf $DNSMASQ_DIR
fi
fi
}
# Restore xtrace + pipefail
$_XTRACE_IRONIC
$_PIPEFAIL_IRONIC