Merge "ipxe boot interface"
This commit is contained in:
commit
ab1b117ee4
@ -20,6 +20,7 @@ from ironic_lib import utils as ironic_utils
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
|
from oslo_utils import importutils
|
||||||
|
|
||||||
from ironic.common import dhcp_factory
|
from ironic.common import dhcp_factory
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
@ -58,14 +59,22 @@ def get_root_dir():
|
|||||||
return CONF.pxe.tftp_root
|
return CONF.pxe.tftp_root
|
||||||
|
|
||||||
|
|
||||||
def _ensure_config_dirs_exist(node_uuid):
|
def get_ipxe_root_dir():
|
||||||
|
return CONF.deploy.http_root
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_config_dirs_exist(task, ipxe_enabled=False):
|
||||||
"""Ensure that the node's and PXE configuration directories exist.
|
"""Ensure that the node's and PXE configuration directories exist.
|
||||||
|
|
||||||
:param node_uuid: the UUID of the node.
|
:param task: A TaskManager instance
|
||||||
|
:param ipxe_enabled: Default false boolean to indicate if ipxe
|
||||||
|
is in use by the caller.
|
||||||
"""
|
"""
|
||||||
root_dir = get_root_dir()
|
if ipxe_enabled:
|
||||||
node_dir = os.path.join(root_dir, node_uuid)
|
root_dir = get_ipxe_root_dir()
|
||||||
|
else:
|
||||||
|
root_dir = get_root_dir()
|
||||||
|
node_dir = os.path.join(root_dir, task.node.uuid)
|
||||||
pxe_dir = os.path.join(root_dir, PXE_CFG_DIR_NAME)
|
pxe_dir = os.path.join(root_dir, PXE_CFG_DIR_NAME)
|
||||||
# NOTE: We should only change the permissions if the folder
|
# NOTE: We should only change the permissions if the folder
|
||||||
# does not exist. i.e. if defined, an operator could have
|
# does not exist. i.e. if defined, an operator could have
|
||||||
@ -78,11 +87,12 @@ def _ensure_config_dirs_exist(node_uuid):
|
|||||||
os.chmod(directory, CONF.pxe.dir_permission)
|
os.chmod(directory, CONF.pxe.dir_permission)
|
||||||
|
|
||||||
|
|
||||||
def _link_mac_pxe_configs(task):
|
def _link_mac_pxe_configs(task, ipxe_enabled=False):
|
||||||
"""Link each MAC address with the PXE configuration file.
|
"""Link each MAC address with the PXE configuration file.
|
||||||
|
|
||||||
:param task: A TaskManager instance.
|
:param task: A TaskManager instance.
|
||||||
|
:param ipxe_enabled: Default false boolean to indicate if ipxe
|
||||||
|
is in use by the caller.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def create_link(mac_path):
|
def create_link(mac_path):
|
||||||
@ -91,26 +101,32 @@ def _link_mac_pxe_configs(task):
|
|||||||
pxe_config_file_path, os.path.dirname(mac_path))
|
pxe_config_file_path, os.path.dirname(mac_path))
|
||||||
utils.create_link_without_raise(relative_source_path, mac_path)
|
utils.create_link_without_raise(relative_source_path, mac_path)
|
||||||
|
|
||||||
pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
|
pxe_config_file_path = get_pxe_config_file_path(
|
||||||
|
task.node.uuid, ipxe_enabled=ipxe_enabled)
|
||||||
for port in task.ports:
|
for port in task.ports:
|
||||||
client_id = port.extra.get('client-id')
|
client_id = port.extra.get('client-id')
|
||||||
# Syslinux, ipxe, depending on settings.
|
# Syslinux, ipxe, depending on settings.
|
||||||
create_link(_get_pxe_mac_path(port.address, client_id=client_id))
|
create_link(_get_pxe_mac_path(port.address, client_id=client_id,
|
||||||
|
ipxe_enabled=ipxe_enabled))
|
||||||
# Grub2 MAC address only
|
# Grub2 MAC address only
|
||||||
create_link(_get_pxe_grub_mac_path(port.address))
|
create_link(_get_pxe_grub_mac_path(port.address))
|
||||||
|
|
||||||
|
|
||||||
def _link_ip_address_pxe_configs(task, hex_form):
|
def _link_ip_address_pxe_configs(task, hex_form, ipxe_enabled=False):
|
||||||
"""Link each IP address with the PXE configuration file.
|
"""Link each IP address with the PXE configuration file.
|
||||||
|
|
||||||
:param task: A TaskManager instance.
|
:param task: A TaskManager instance.
|
||||||
:param hex_form: Boolean value indicating if the conf file name should be
|
:param hex_form: Boolean value indicating if the conf file name should be
|
||||||
hexadecimal equivalent of supplied ipv4 address.
|
hexadecimal equivalent of supplied ipv4 address.
|
||||||
|
:param ipxe_enabled: Default false boolean to indicate if ipxe
|
||||||
|
is in use by the caller.
|
||||||
:raises: FailedToGetIPAddressOnPort
|
:raises: FailedToGetIPAddressOnPort
|
||||||
:raises: InvalidIPv4Address
|
:raises: InvalidIPv4Address
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
|
pxe_config_file_path = get_pxe_config_file_path(
|
||||||
|
task.node.uuid,
|
||||||
|
ipxe_enabled=ipxe_enabled)
|
||||||
|
|
||||||
api = dhcp_factory.DHCPFactory().provider
|
api = dhcp_factory.DHCPFactory().provider
|
||||||
ip_addrs = api.get_ip_addresses(task)
|
ip_addrs = api.get_ip_addresses(task)
|
||||||
@ -132,23 +148,29 @@ def _get_pxe_grub_mac_path(mac):
|
|||||||
return os.path.join(get_root_dir(), mac + '.conf')
|
return os.path.join(get_root_dir(), mac + '.conf')
|
||||||
|
|
||||||
|
|
||||||
def _get_pxe_mac_path(mac, delimiter='-', client_id=None):
|
def _get_pxe_mac_path(mac, delimiter='-', client_id=None,
|
||||||
|
ipxe_enabled=False):
|
||||||
"""Convert a MAC address into a PXE config file name.
|
"""Convert a MAC address into a PXE config file name.
|
||||||
|
|
||||||
:param mac: A MAC address string in the format xx:xx:xx:xx:xx:xx.
|
:param mac: A MAC address string in the format xx:xx:xx:xx:xx:xx.
|
||||||
:param delimiter: The MAC address delimiter. Defaults to dash ('-').
|
:param delimiter: The MAC address delimiter. Defaults to dash ('-').
|
||||||
:param client_id: client_id indicate InfiniBand port.
|
:param client_id: client_id indicate InfiniBand port.
|
||||||
Defaults is None (Ethernet)
|
Defaults is None (Ethernet)
|
||||||
|
:param ipxe_enabled: A default False boolean value to tell the method
|
||||||
|
if the caller is using iPXE.
|
||||||
:returns: the path to the config file.
|
:returns: the path to the config file.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mac_file_name = mac.replace(':', delimiter).lower()
|
mac_file_name = mac.replace(':', delimiter).lower()
|
||||||
if not CONF.pxe.ipxe_enabled:
|
if not ipxe_enabled:
|
||||||
hw_type = '01-'
|
hw_type = '01-'
|
||||||
if client_id:
|
if client_id:
|
||||||
hw_type = '20-'
|
hw_type = '20-'
|
||||||
mac_file_name = hw_type + mac_file_name
|
mac_file_name = hw_type + mac_file_name
|
||||||
return os.path.join(get_root_dir(), PXE_CFG_DIR_NAME, mac_file_name)
|
return os.path.join(get_root_dir(), PXE_CFG_DIR_NAME,
|
||||||
|
mac_file_name)
|
||||||
|
return os.path.join(get_ipxe_root_dir(), PXE_CFG_DIR_NAME,
|
||||||
|
mac_file_name)
|
||||||
|
|
||||||
|
|
||||||
def _get_pxe_ip_address_path(ip_address, hex_form):
|
def _get_pxe_ip_address_path(ip_address, hex_form):
|
||||||
@ -173,7 +195,8 @@ def _get_pxe_ip_address_path(ip_address, hex_form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_kernel_ramdisk_info(node_uuid, driver_info, mode='deploy'):
|
def get_kernel_ramdisk_info(node_uuid, driver_info, mode='deploy',
|
||||||
|
ipxe_enabled=False):
|
||||||
"""Get href and tftp path for deploy or rescue kernel and ramdisk.
|
"""Get href and tftp path for deploy or rescue kernel and ramdisk.
|
||||||
|
|
||||||
:param node_uuid: UUID of the node
|
:param node_uuid: UUID of the node
|
||||||
@ -182,13 +205,18 @@ def get_kernel_ramdisk_info(node_uuid, driver_info, mode='deploy'):
|
|||||||
ramdisk are being requested. Supported values are 'deploy'
|
ramdisk are being requested. Supported values are 'deploy'
|
||||||
'rescue'. Defaults to 'deploy', indicating deploy paths will
|
'rescue'. Defaults to 'deploy', indicating deploy paths will
|
||||||
be returned.
|
be returned.
|
||||||
|
:param ipxe_enabled: A default False boolean value to tell the method
|
||||||
|
if the caller is using iPXE.
|
||||||
:returns: a dictionary whose keys are deploy_kernel and deploy_ramdisk or
|
:returns: a dictionary whose keys are deploy_kernel and deploy_ramdisk or
|
||||||
rescue_kernel and rescue_ramdisk and whose values are the
|
rescue_kernel and rescue_ramdisk and whose values are the
|
||||||
absolute paths to them.
|
absolute paths to them.
|
||||||
|
|
||||||
Note: driver_info should be validated outside of this method.
|
Note: driver_info should be validated outside of this method.
|
||||||
"""
|
"""
|
||||||
root_dir = get_root_dir()
|
if ipxe_enabled:
|
||||||
|
root_dir = get_ipxe_root_dir()
|
||||||
|
else:
|
||||||
|
root_dir = get_root_dir()
|
||||||
image_info = {}
|
image_info = {}
|
||||||
labels = KERNEL_RAMDISK_LABELS[mode]
|
labels = KERNEL_RAMDISK_LABELS[mode]
|
||||||
for label in labels:
|
for label in labels:
|
||||||
@ -199,17 +227,22 @@ def get_kernel_ramdisk_info(node_uuid, driver_info, mode='deploy'):
|
|||||||
return image_info
|
return image_info
|
||||||
|
|
||||||
|
|
||||||
def get_pxe_config_file_path(node_uuid):
|
def get_pxe_config_file_path(node_uuid, ipxe_enabled=False):
|
||||||
"""Generate the path for the node's PXE configuration file.
|
"""Generate the path for the node's PXE configuration file.
|
||||||
|
|
||||||
:param node_uuid: the UUID of the node.
|
:param node_uuid: the UUID of the node.
|
||||||
|
:param ipxe_enabled: A default False boolean value to tell the method
|
||||||
|
if the caller is using iPXE.
|
||||||
:returns: The path to the node's PXE configuration file.
|
:returns: The path to the node's PXE configuration file.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return os.path.join(get_root_dir(), node_uuid, 'config')
|
if ipxe_enabled:
|
||||||
|
return os.path.join(get_ipxe_root_dir(), node_uuid, 'config')
|
||||||
|
else:
|
||||||
|
return os.path.join(get_root_dir(), node_uuid, 'config')
|
||||||
|
|
||||||
|
|
||||||
def create_pxe_config(task, pxe_options, template=None):
|
def create_pxe_config(task, pxe_options, template=None, ipxe_enabled=False):
|
||||||
"""Generate PXE configuration file and MAC address links for it.
|
"""Generate PXE configuration file and MAC address links for it.
|
||||||
|
|
||||||
This method will generate the PXE configuration file for the task's
|
This method will generate the PXE configuration file for the task's
|
||||||
@ -231,13 +264,14 @@ def create_pxe_config(task, pxe_options, template=None):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
LOG.debug("Building PXE config for node %s", task.node.uuid)
|
LOG.debug("Building PXE config for node %s", task.node.uuid)
|
||||||
|
|
||||||
if template is None:
|
if template is None:
|
||||||
template = deploy_utils.get_pxe_config_template(task.node)
|
template = deploy_utils.get_pxe_config_template(task.node)
|
||||||
|
|
||||||
_ensure_config_dirs_exist(task.node.uuid)
|
_ensure_config_dirs_exist(task, ipxe_enabled)
|
||||||
|
|
||||||
pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
|
pxe_config_file_path = get_pxe_config_file_path(
|
||||||
|
task.node.uuid,
|
||||||
|
ipxe_enabled=ipxe_enabled)
|
||||||
is_uefi_boot_mode = (boot_mode_utils.get_boot_mode_for_deploy(task.node)
|
is_uefi_boot_mode = (boot_mode_utils.get_boot_mode_for_deploy(task.node)
|
||||||
== 'uefi')
|
== 'uefi')
|
||||||
|
|
||||||
@ -269,10 +303,10 @@ def create_pxe_config(task, pxe_options, template=None):
|
|||||||
utils.write_to_file(pxe_config_file_path, pxe_config)
|
utils.write_to_file(pxe_config_file_path, pxe_config)
|
||||||
|
|
||||||
# Always write the mac addresses
|
# Always write the mac addresses
|
||||||
_link_mac_pxe_configs(task)
|
_link_mac_pxe_configs(task, ipxe_enabled=ipxe_enabled)
|
||||||
if is_uefi_boot_mode and not CONF.pxe.ipxe_enabled:
|
if is_uefi_boot_mode and not ipxe_enabled:
|
||||||
try:
|
try:
|
||||||
_link_ip_address_pxe_configs(task, hex_form)
|
_link_ip_address_pxe_configs(task, hex_form, ipxe_enabled)
|
||||||
# NOTE(TheJulia): The IP address support will fail if the
|
# NOTE(TheJulia): The IP address support will fail if the
|
||||||
# dhcp_provider interface is set to none. This will result
|
# dhcp_provider interface is set to none. This will result
|
||||||
# in the MAC addresses and DHCP files being written, and
|
# in the MAC addresses and DHCP files being written, and
|
||||||
@ -312,7 +346,9 @@ def clean_up_pxe_config(task):
|
|||||||
|
|
||||||
is_uefi_boot_mode = (boot_mode_utils.get_boot_mode_for_deploy(task.node)
|
is_uefi_boot_mode = (boot_mode_utils.get_boot_mode_for_deploy(task.node)
|
||||||
== 'uefi')
|
== 'uefi')
|
||||||
if is_uefi_boot_mode and not CONF.pxe.ipxe_enabled:
|
ipxe_enabled = is_ipxe_enabled(task)
|
||||||
|
|
||||||
|
if is_uefi_boot_mode and not ipxe_enabled:
|
||||||
api = dhcp_factory.DHCPFactory().provider
|
api = dhcp_factory.DHCPFactory().provider
|
||||||
ip_addresses = api.get_ip_addresses(task)
|
ip_addresses = api.get_ip_addresses(task)
|
||||||
if not ip_addresses:
|
if not ip_addresses:
|
||||||
@ -341,12 +377,17 @@ def clean_up_pxe_config(task):
|
|||||||
client_id = port.extra.get('client-id')
|
client_id = port.extra.get('client-id')
|
||||||
# syslinux, ipxe, etc.
|
# syslinux, ipxe, etc.
|
||||||
ironic_utils.unlink_without_raise(
|
ironic_utils.unlink_without_raise(
|
||||||
_get_pxe_mac_path(port.address, client_id=client_id))
|
_get_pxe_mac_path(port.address, client_id=client_id,
|
||||||
|
ipxe_enabled=ipxe_enabled))
|
||||||
# Grub2 MAC address based confiuration
|
# Grub2 MAC address based confiuration
|
||||||
ironic_utils.unlink_without_raise(
|
ironic_utils.unlink_without_raise(
|
||||||
_get_pxe_grub_mac_path(port.address))
|
_get_pxe_grub_mac_path(port.address))
|
||||||
utils.rmtree_without_raise(os.path.join(get_root_dir(),
|
if ipxe_enabled:
|
||||||
task.node.uuid))
|
utils.rmtree_without_raise(os.path.join(get_ipxe_root_dir(),
|
||||||
|
task.node.uuid))
|
||||||
|
else:
|
||||||
|
utils.rmtree_without_raise(os.path.join(get_root_dir(),
|
||||||
|
task.node.uuid))
|
||||||
|
|
||||||
|
|
||||||
def dhcp_options_for_instance(task):
|
def dhcp_options_for_instance(task):
|
||||||
@ -358,7 +399,7 @@ def dhcp_options_for_instance(task):
|
|||||||
|
|
||||||
boot_file = deploy_utils.get_pxe_boot_file(task.node)
|
boot_file = deploy_utils.get_pxe_boot_file(task.node)
|
||||||
|
|
||||||
if CONF.pxe.ipxe_enabled:
|
if is_ipxe_enabled(task):
|
||||||
script_name = os.path.basename(CONF.pxe.ipxe_boot_script)
|
script_name = os.path.basename(CONF.pxe.ipxe_boot_script)
|
||||||
ipxe_script_url = '/'.join([CONF.deploy.http_url, script_name])
|
ipxe_script_url = '/'.join([CONF.deploy.http_url, script_name])
|
||||||
dhcp_provider_name = CONF.dhcp.dhcp_provider
|
dhcp_provider_name = CONF.dhcp.dhcp_provider
|
||||||
@ -441,16 +482,16 @@ def is_ipxe_enabled(task):
|
|||||||
:returns: boolean true if ``[pxe]ipxe_enabled`` is configured
|
:returns: boolean true if ``[pxe]ipxe_enabled`` is configured
|
||||||
or if the task driver instance is the iPXE driver.
|
or if the task driver instance is the iPXE driver.
|
||||||
"""
|
"""
|
||||||
# TODO(TheJulia): Due to the order being shuffled of the patches,
|
|
||||||
# I'm mostly leaving this in place.
|
|
||||||
# NOTE(TheJulia): importutils used here as we seem to get in circular
|
# NOTE(TheJulia): importutils used here as we seem to get in circular
|
||||||
# import weirdness otherwise, specifically when the classes that use
|
# import weirdness otherwise, specifically when the classes that use
|
||||||
# the pxe interface as their parent.
|
# the pxe interface as their parent.
|
||||||
# iPXEBoot = importutils.import_class(
|
# TODO(TheJulia): We should remove this as soon as it is no longer
|
||||||
# 'ironic.drivers.modules.ipxe.iPXEBoot')
|
# required to help us bridge the split of the interfaces and helper
|
||||||
# return CONF.pxe.ipxe_enabled or isinstance(task.driver.boot,
|
# methods.
|
||||||
# iPXEBoot)
|
iPXEBoot = importutils.import_class(
|
||||||
return CONF.pxe.ipxe_enabled
|
'ironic.drivers.modules.ipxe.iPXEBoot')
|
||||||
|
return CONF.pxe.ipxe_enabled or isinstance(task.driver.boot,
|
||||||
|
iPXEBoot)
|
||||||
|
|
||||||
|
|
||||||
def parse_driver_info(node, mode='deploy'):
|
def parse_driver_info(node, mode='deploy'):
|
||||||
@ -479,28 +520,33 @@ def parse_driver_info(node, mode='deploy'):
|
|||||||
return d_info
|
return d_info
|
||||||
|
|
||||||
|
|
||||||
def get_instance_image_info(node, ctx):
|
def get_instance_image_info(task, ipxe_enabled=False):
|
||||||
"""Generate the paths for TFTP files for instance related images.
|
"""Generate the paths for TFTP files for instance related images.
|
||||||
|
|
||||||
This method generates the paths for instance kernel and
|
This method generates the paths for instance kernel and
|
||||||
instance ramdisk. This method also updates the node, so caller should
|
instance ramdisk. This method also updates the node, so caller should
|
||||||
already have a non-shared lock on the node.
|
already have a non-shared lock on the node.
|
||||||
|
|
||||||
:param node: a node object
|
:param task: A TaskManager instance containing node and context.
|
||||||
:param ctx: context
|
:param ipxe_enabled: Default false boolean to indicate if ipxe
|
||||||
|
is in use by the caller.
|
||||||
:returns: a dictionary whose keys are the names of the images (kernel,
|
:returns: a dictionary whose keys are the names of the images (kernel,
|
||||||
ramdisk) and values are the absolute paths of them. If it's a whole
|
ramdisk) and values are the absolute paths of them. If it's a whole
|
||||||
disk image or node is configured for localboot,
|
disk image or node is configured for localboot,
|
||||||
it returns an empty dictionary.
|
it returns an empty dictionary.
|
||||||
"""
|
"""
|
||||||
|
ctx = task.context
|
||||||
|
node = task.node
|
||||||
image_info = {}
|
image_info = {}
|
||||||
# NOTE(pas-ha) do not report image kernel and ramdisk for
|
# NOTE(pas-ha) do not report image kernel and ramdisk for
|
||||||
# local boot or whole disk images so that they are not cached
|
# local boot or whole disk images so that they are not cached
|
||||||
if (node.driver_internal_info.get('is_whole_disk_image')
|
if (node.driver_internal_info.get('is_whole_disk_image')
|
||||||
or deploy_utils.get_boot_option(node) == 'local'):
|
or deploy_utils.get_boot_option(node) == 'local'):
|
||||||
return image_info
|
return image_info
|
||||||
|
if ipxe_enabled:
|
||||||
root_dir = get_root_dir()
|
root_dir = get_ipxe_root_dir()
|
||||||
|
else:
|
||||||
|
root_dir = get_root_dir()
|
||||||
i_info = node.instance_info
|
i_info = node.instance_info
|
||||||
labels = ('kernel', 'ramdisk')
|
labels = ('kernel', 'ramdisk')
|
||||||
d_info = deploy_utils.get_image_instance_info(node)
|
d_info = deploy_utils.get_image_instance_info(node)
|
||||||
@ -617,7 +663,8 @@ def build_extra_pxe_options():
|
|||||||
'ipxe_timeout': CONF.pxe.ipxe_timeout * 1000}
|
'ipxe_timeout': CONF.pxe.ipxe_timeout * 1000}
|
||||||
|
|
||||||
|
|
||||||
def build_pxe_config_options(task, pxe_info, service=False):
|
def build_pxe_config_options(task, pxe_info, service=False,
|
||||||
|
ipxe_enabled=False):
|
||||||
"""Build the PXE config options for a node
|
"""Build the PXE config options for a node
|
||||||
|
|
||||||
This method builds the PXE boot options for a node,
|
This method builds the PXE boot options for a node,
|
||||||
@ -631,6 +678,8 @@ def build_pxe_config_options(task, pxe_info, service=False):
|
|||||||
:param service: if True, build "service mode" pxe config for netboot-ed
|
:param service: if True, build "service mode" pxe config for netboot-ed
|
||||||
user image and skip adding deployment image kernel and ramdisk info
|
user image and skip adding deployment image kernel and ramdisk info
|
||||||
to PXE options.
|
to PXE options.
|
||||||
|
:param ipxe_enabled: Default false boolean to indicate if ipxe
|
||||||
|
is in use by the caller.
|
||||||
:returns: A dictionary of pxe options to be used in the pxe bootfile
|
:returns: A dictionary of pxe options to be used in the pxe bootfile
|
||||||
template.
|
template.
|
||||||
"""
|
"""
|
||||||
@ -639,7 +688,7 @@ def build_pxe_config_options(task, pxe_info, service=False):
|
|||||||
if service:
|
if service:
|
||||||
pxe_options = {}
|
pxe_options = {}
|
||||||
elif (node.driver_internal_info.get('boot_from_volume')
|
elif (node.driver_internal_info.get('boot_from_volume')
|
||||||
and is_ipxe_enabled(task)):
|
and ipxe_enabled):
|
||||||
pxe_options = get_volume_pxe_options(task)
|
pxe_options = get_volume_pxe_options(task)
|
||||||
else:
|
else:
|
||||||
pxe_options = build_deploy_pxe_options(task, pxe_info, mode=mode)
|
pxe_options = build_deploy_pxe_options(task, pxe_info, mode=mode)
|
||||||
@ -658,7 +707,8 @@ def build_pxe_config_options(task, pxe_info, service=False):
|
|||||||
|
|
||||||
def build_service_pxe_config(task, instance_image_info,
|
def build_service_pxe_config(task, instance_image_info,
|
||||||
root_uuid_or_disk_id,
|
root_uuid_or_disk_id,
|
||||||
ramdisk_boot=False):
|
ramdisk_boot=False,
|
||||||
|
ipxe_enabled=False):
|
||||||
node = task.node
|
node = task.node
|
||||||
pxe_config_path = get_pxe_config_file_path(node.uuid)
|
pxe_config_path = get_pxe_config_file_path(node.uuid)
|
||||||
# NOTE(pas-ha) if it is takeover of ACTIVE node or node performing
|
# NOTE(pas-ha) if it is takeover of ACTIVE node or node performing
|
||||||
@ -667,17 +717,18 @@ def build_service_pxe_config(task, instance_image_info,
|
|||||||
if (node.provision_state in [states.ACTIVE, states.UNRESCUING]
|
if (node.provision_state in [states.ACTIVE, states.UNRESCUING]
|
||||||
and not os.path.isfile(pxe_config_path)):
|
and not os.path.isfile(pxe_config_path)):
|
||||||
pxe_options = build_pxe_config_options(task, instance_image_info,
|
pxe_options = build_pxe_config_options(task, instance_image_info,
|
||||||
service=True)
|
service=True,
|
||||||
|
ipxe_enabled=ipxe_enabled)
|
||||||
pxe_config_template = deploy_utils.get_pxe_config_template(node)
|
pxe_config_template = deploy_utils.get_pxe_config_template(node)
|
||||||
create_pxe_config(task, pxe_options, pxe_config_template)
|
create_pxe_config(task, pxe_options, pxe_config_template,
|
||||||
|
ipxe_enabled=ipxe_enabled)
|
||||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||||
deploy_utils.switch_pxe_config(
|
deploy_utils.switch_pxe_config(
|
||||||
pxe_config_path, root_uuid_or_disk_id,
|
pxe_config_path, root_uuid_or_disk_id,
|
||||||
boot_mode_utils.get_boot_mode_for_deploy(node),
|
boot_mode_utils.get_boot_mode_for_deploy(node),
|
||||||
iwdi, deploy_utils.is_trusted_boot_requested(node),
|
iwdi, deploy_utils.is_trusted_boot_requested(node),
|
||||||
deploy_utils.is_iscsi_boot(task), ramdisk_boot)
|
deploy_utils.is_iscsi_boot(task), ramdisk_boot,
|
||||||
# TODO(TheJulia): Add with ipxe interface
|
ipxe_enabled=ipxe_enabled)
|
||||||
# ipxe_enabled=is_ipxe_enabled(task))
|
|
||||||
|
|
||||||
|
|
||||||
def get_volume_pxe_options(task):
|
def get_volume_pxe_options(task):
|
||||||
@ -772,7 +823,8 @@ def validate_boot_parameters_for_trusted_boot(node):
|
|||||||
|
|
||||||
def prepare_instance_pxe_config(task, image_info,
|
def prepare_instance_pxe_config(task, image_info,
|
||||||
iscsi_boot=False,
|
iscsi_boot=False,
|
||||||
ramdisk_boot=False):
|
ramdisk_boot=False,
|
||||||
|
ipxe_enabled=False):
|
||||||
"""Prepares the config file for PXE boot
|
"""Prepares the config file for PXE boot
|
||||||
|
|
||||||
:param task: a task from TaskManager.
|
:param task: a task from TaskManager.
|
||||||
@ -780,6 +832,8 @@ def prepare_instance_pxe_config(task, image_info,
|
|||||||
metadata to set on the configuration file.
|
metadata to set on the configuration file.
|
||||||
:param iscsi_boot: if boot is from an iSCSI volume or not.
|
:param iscsi_boot: if boot is from an iSCSI volume or not.
|
||||||
:param ramdisk_boot: if the boot is to a ramdisk configuration.
|
:param ramdisk_boot: if the boot is to a ramdisk configuration.
|
||||||
|
:param ipxe_enabled: Default false boolean to indicate if ipxe
|
||||||
|
is in use by the caller.
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -791,13 +845,15 @@ def prepare_instance_pxe_config(task, image_info,
|
|||||||
node.uuid)
|
node.uuid)
|
||||||
if not os.path.isfile(pxe_config_path):
|
if not os.path.isfile(pxe_config_path):
|
||||||
pxe_options = build_pxe_config_options(
|
pxe_options = build_pxe_config_options(
|
||||||
task, image_info, service=ramdisk_boot)
|
task, image_info, service=ramdisk_boot,
|
||||||
|
ipxe_enabled=ipxe_enabled)
|
||||||
pxe_config_template = (
|
pxe_config_template = (
|
||||||
deploy_utils.get_pxe_config_template(node))
|
deploy_utils.get_pxe_config_template(node))
|
||||||
create_pxe_config(
|
create_pxe_config(
|
||||||
task, pxe_options, pxe_config_template)
|
task, pxe_options, pxe_config_template,
|
||||||
|
ipxe_enabled=ipxe_enabled)
|
||||||
deploy_utils.switch_pxe_config(
|
deploy_utils.switch_pxe_config(
|
||||||
pxe_config_path, None,
|
pxe_config_path, None,
|
||||||
boot_mode_utils.get_boot_mode_for_deploy(node), False,
|
boot_mode_utils.get_boot_mode_for_deploy(node), False,
|
||||||
iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot,
|
iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot,
|
||||||
ipxe_enabled=is_ipxe_enabled(task))
|
ipxe_enabled=ipxe_enabled)
|
||||||
|
@ -108,7 +108,15 @@ opts = [
|
|||||||
'For example: aarch64:grubaa64.efi')),
|
'For example: aarch64:grubaa64.efi')),
|
||||||
cfg.BoolOpt('ipxe_enabled',
|
cfg.BoolOpt('ipxe_enabled',
|
||||||
default=False,
|
default=False,
|
||||||
help=_('Enable iPXE boot.')),
|
help=_('Defaults the PXE interface to only use iPXE.'),
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason=_("This global setting has been "
|
||||||
|
"superseded by an 'ipxe' boot "
|
||||||
|
"interface. Set the "
|
||||||
|
"[default]default_boot_interface "
|
||||||
|
"to 'ipxe' and/or manually set the node "
|
||||||
|
"boot interface to 'ipxe' to maintain "
|
||||||
|
"the same functionality.")),
|
||||||
cfg.StrOpt('ipxe_boot_script',
|
cfg.StrOpt('ipxe_boot_script',
|
||||||
default=os.path.join(
|
default=os.path.join(
|
||||||
'$pybasedir', 'drivers/modules/boot.ipxe'),
|
'$pybasedir', 'drivers/modules/boot.ipxe'),
|
||||||
|
@ -21,6 +21,7 @@ from ironic.drivers.modules import agent
|
|||||||
from ironic.drivers.modules.ansible import deploy as ansible_deploy
|
from ironic.drivers.modules.ansible import deploy as ansible_deploy
|
||||||
from ironic.drivers.modules import fake
|
from ironic.drivers.modules import fake
|
||||||
from ironic.drivers.modules import inspector
|
from ironic.drivers.modules import inspector
|
||||||
|
from ironic.drivers.modules import ipxe
|
||||||
from ironic.drivers.modules import iscsi_deploy
|
from ironic.drivers.modules import iscsi_deploy
|
||||||
from ironic.drivers.modules.network import flat as flat_net
|
from ironic.drivers.modules.network import flat as flat_net
|
||||||
from ironic.drivers.modules.network import neutron
|
from ironic.drivers.modules.network import neutron
|
||||||
@ -41,7 +42,7 @@ class GenericHardware(hardware_type.AbstractHardwareType):
|
|||||||
@property
|
@property
|
||||||
def supported_boot_interfaces(self):
|
def supported_boot_interfaces(self):
|
||||||
"""List of supported boot interfaces."""
|
"""List of supported boot interfaces."""
|
||||||
return [pxe.PXEBoot]
|
return [ipxe.iPXEBoot, pxe.PXEBoot]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_deploy_interfaces(self):
|
def supported_deploy_interfaces(self):
|
||||||
|
@ -287,7 +287,7 @@ def _replace_root_uuid(path, root_uuid):
|
|||||||
|
|
||||||
def _replace_boot_line(path, boot_mode, is_whole_disk_image,
|
def _replace_boot_line(path, boot_mode, is_whole_disk_image,
|
||||||
trusted_boot=False, iscsi_boot=False,
|
trusted_boot=False, iscsi_boot=False,
|
||||||
ramdisk_boot=False):
|
ramdisk_boot=False, ipxe_enabled=False):
|
||||||
if is_whole_disk_image:
|
if is_whole_disk_image:
|
||||||
boot_disk_type = 'boot_whole_disk'
|
boot_disk_type = 'boot_whole_disk'
|
||||||
elif trusted_boot:
|
elif trusted_boot:
|
||||||
@ -299,11 +299,11 @@ def _replace_boot_line(path, boot_mode, is_whole_disk_image,
|
|||||||
else:
|
else:
|
||||||
boot_disk_type = 'boot_partition'
|
boot_disk_type = 'boot_partition'
|
||||||
|
|
||||||
if boot_mode == 'uefi' and not CONF.pxe.ipxe_enabled:
|
if boot_mode == 'uefi' and not ipxe_enabled:
|
||||||
pattern = '^((set )?default)=.*$'
|
pattern = '^((set )?default)=.*$'
|
||||||
boot_line = '\\1=%s' % boot_disk_type
|
boot_line = '\\1=%s' % boot_disk_type
|
||||||
else:
|
else:
|
||||||
pxe_cmd = 'goto' if CONF.pxe.ipxe_enabled else 'default'
|
pxe_cmd = 'goto' if ipxe_enabled else 'default'
|
||||||
pattern = '^%s .*$' % pxe_cmd
|
pattern = '^%s .*$' % pxe_cmd
|
||||||
boot_line = '%s %s' % (pxe_cmd, boot_disk_type)
|
boot_line = '%s %s' % (pxe_cmd, boot_disk_type)
|
||||||
|
|
||||||
@ -315,9 +315,11 @@ def _replace_disk_identifier(path, disk_identifier):
|
|||||||
_replace_lines_in_file(path, pattern, disk_identifier)
|
_replace_lines_in_file(path, pattern, disk_identifier)
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(TheJulia): This should likely be migrated to pxe_utils.
|
||||||
def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
|
def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
|
||||||
is_whole_disk_image, trusted_boot=False,
|
is_whole_disk_image, trusted_boot=False,
|
||||||
iscsi_boot=False, ramdisk_boot=False):
|
iscsi_boot=False, ramdisk_boot=False,
|
||||||
|
ipxe_enabled=False):
|
||||||
"""Switch a pxe config from deployment mode to service mode.
|
"""Switch a pxe config from deployment mode to service mode.
|
||||||
|
|
||||||
:param path: path to the pxe config file in tftpboot.
|
:param path: path to the pxe config file in tftpboot.
|
||||||
@ -330,6 +332,8 @@ def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
|
|||||||
have one or neither, but not both.
|
have one or neither, but not both.
|
||||||
:param iscsi_boot: if boot is from an iSCSI volume or not.
|
:param iscsi_boot: if boot is from an iSCSI volume or not.
|
||||||
:param ramdisk_boot: if the boot is to be to a ramdisk configuration.
|
:param ramdisk_boot: if the boot is to be to a ramdisk configuration.
|
||||||
|
:param ipxe_enabled: A default False boolean value to tell the method
|
||||||
|
if the caller is using iPXE.
|
||||||
"""
|
"""
|
||||||
if not ramdisk_boot:
|
if not ramdisk_boot:
|
||||||
if not is_whole_disk_image:
|
if not is_whole_disk_image:
|
||||||
@ -338,7 +342,7 @@ def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
|
|||||||
_replace_disk_identifier(path, root_uuid_or_disk_id)
|
_replace_disk_identifier(path, root_uuid_or_disk_id)
|
||||||
|
|
||||||
_replace_boot_line(path, boot_mode, is_whole_disk_image, trusted_boot,
|
_replace_boot_line(path, boot_mode, is_whole_disk_image, trusted_boot,
|
||||||
iscsi_boot, ramdisk_boot)
|
iscsi_boot, ramdisk_boot, ipxe_enabled)
|
||||||
|
|
||||||
|
|
||||||
def get_dev(address, port, iqn, lun):
|
def get_dev(address, port, iqn, lun):
|
||||||
|
@ -68,6 +68,10 @@ class FakePower(base.PowerInterface):
|
|||||||
class FakeBoot(base.BootInterface):
|
class FakeBoot(base.BootInterface):
|
||||||
"""Example implementation of a simple boot interface."""
|
"""Example implementation of a simple boot interface."""
|
||||||
|
|
||||||
|
# NOTE(TheJulia): default capabilities to make unit tests
|
||||||
|
# happy with the fake boot interface.
|
||||||
|
capabilities = ['ipxe_boot', 'pxe_boot']
|
||||||
|
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
358
ironic/drivers/modules/ipxe.py
Normal file
358
ironic/drivers/modules/ipxe.py
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
"""
|
||||||
|
iPXE Boot Interface
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ironic_lib import metrics_utils
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import strutils
|
||||||
|
|
||||||
|
from ironic.common import boot_devices
|
||||||
|
from ironic.common import dhcp_factory
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common.glance_service import service_utils
|
||||||
|
from ironic.common.i18n import _
|
||||||
|
from ironic.common import pxe_utils as common_pxe_utils
|
||||||
|
from ironic.common import states
|
||||||
|
from ironic.conductor import utils as manager_utils
|
||||||
|
from ironic.conf import CONF
|
||||||
|
from ironic.drivers import base
|
||||||
|
from ironic.drivers.modules import boot_mode_utils
|
||||||
|
from ironic.drivers.modules import deploy_utils
|
||||||
|
from ironic.drivers.modules import pxe
|
||||||
|
from ironic.drivers import utils as driver_utils
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||||
|
|
||||||
|
# TODO(TheJulia): Lets rip these out ASAP and move them to a pxe_common.
|
||||||
|
# One chunk moving at a time for sanity.
|
||||||
|
REQUIRED_PROPERTIES = {
|
||||||
|
'deploy_kernel': _("UUID (from Glance) of the deployment kernel. "
|
||||||
|
"Required."),
|
||||||
|
'deploy_ramdisk': _("UUID (from Glance) of the ramdisk that is "
|
||||||
|
"mounted at boot time. Required."),
|
||||||
|
}
|
||||||
|
OPTIONAL_PROPERTIES = {
|
||||||
|
'force_persistent_boot_device': _("True to enable persistent behavior "
|
||||||
|
"when the boot device is set during "
|
||||||
|
"deploy and cleaning operations. "
|
||||||
|
"Defaults to False. Optional."),
|
||||||
|
}
|
||||||
|
RESCUE_PROPERTIES = {
|
||||||
|
'rescue_kernel': _('UUID (from Glance) of the rescue kernel. This value '
|
||||||
|
'is required for rescue mode.'),
|
||||||
|
'rescue_ramdisk': _('UUID (from Glance) of the rescue ramdisk with agent '
|
||||||
|
'that is used at node rescue time. This value is '
|
||||||
|
'required for rescue mode.'),
|
||||||
|
}
|
||||||
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||||
|
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||||
|
COMMON_PROPERTIES.update(RESCUE_PROPERTIES)
|
||||||
|
# TODO(TheJulia): Use these as the copy to move, no reason to touch pxe.py at
|
||||||
|
# the same time as doing the initial split out as deduplication goes on.
|
||||||
|
|
||||||
|
|
||||||
|
class iPXEBoot(base.BootInterface):
|
||||||
|
|
||||||
|
capabilities = ['iscsi_volume_boot', 'ramdisk_boot', 'ipxe_boot']
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
common_pxe_utils.create_ipxe_boot_script()
|
||||||
|
|
||||||
|
def get_properties(self):
|
||||||
|
"""Return the properties of the interface.
|
||||||
|
|
||||||
|
:returns: dictionary of <property name>:<property description> entries.
|
||||||
|
"""
|
||||||
|
# TODO(stendulker): COMMON_PROPERTIES should also include rescue
|
||||||
|
# related properties (RESCUE_PROPERTIES). We can add them in Rocky,
|
||||||
|
# when classic drivers get removed.
|
||||||
|
return COMMON_PROPERTIES
|
||||||
|
|
||||||
|
@METRICS.timer('iPXEBoot.validate')
|
||||||
|
def validate(self, task):
|
||||||
|
"""Validate the PXE-specific info for booting deploy/instance images.
|
||||||
|
|
||||||
|
This method validates the PXE-specific info for booting the
|
||||||
|
ramdisk and instance on the node. If invalid, raises an
|
||||||
|
exception; otherwise returns None.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:returns: None
|
||||||
|
:raises: InvalidParameterValue, if some parameters are invalid.
|
||||||
|
:raises: MissingParameterValue, if some required parameters are
|
||||||
|
missing.
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
|
||||||
|
if not driver_utils.get_node_mac_addresses(task):
|
||||||
|
raise exception.MissingParameterValue(
|
||||||
|
_("Node %s does not have any port associated with it.")
|
||||||
|
% node.uuid)
|
||||||
|
|
||||||
|
if not CONF.deploy.http_url or not CONF.deploy.http_root:
|
||||||
|
raise exception.MissingParameterValue(_(
|
||||||
|
"iPXE boot is enabled but no HTTP URL or HTTP "
|
||||||
|
"root was specified."))
|
||||||
|
|
||||||
|
# Check the trusted_boot capabilities value.
|
||||||
|
deploy_utils.validate_capabilities(node)
|
||||||
|
if deploy_utils.is_trusted_boot_requested(node):
|
||||||
|
# Check if 'boot_option' and boot mode is compatible with
|
||||||
|
# trusted boot.
|
||||||
|
# NOTE(TheJulia): So in theory (huge theory here, not put to
|
||||||
|
# practice or tested), that one can define the kernel as tboot
|
||||||
|
# and define the actual kernel and ramdisk as appended data.
|
||||||
|
# Similar to how one can iPXE load the XEN hypervisor.
|
||||||
|
# tboot mailing list seem to indicate pxe/ipxe support, or
|
||||||
|
# more specifically avoiding breaking the scenarios of use,
|
||||||
|
# but there is also no definitive documentation on the subject.
|
||||||
|
LOG.warning('Trusted boot has been requested for %(node)s in '
|
||||||
|
'concert with iPXE. This is not a supported '
|
||||||
|
'configuration for an ironic deployment.',
|
||||||
|
{'node': node.uuid})
|
||||||
|
pxe.validate_boot_parameters_for_trusted_boot(node)
|
||||||
|
|
||||||
|
pxe._parse_driver_info(node)
|
||||||
|
# NOTE(TheJulia): If we're not writing an image, we can skip
|
||||||
|
# the remainder of this method.
|
||||||
|
if (not task.driver.storage.should_write_image(task)):
|
||||||
|
return
|
||||||
|
|
||||||
|
d_info = deploy_utils.get_image_instance_info(node)
|
||||||
|
if (node.driver_internal_info.get('is_whole_disk_image')
|
||||||
|
or deploy_utils.get_boot_option(node) == 'local'):
|
||||||
|
props = []
|
||||||
|
elif service_utils.is_glance_image(d_info['image_source']):
|
||||||
|
props = ['kernel_id', 'ramdisk_id']
|
||||||
|
else:
|
||||||
|
props = ['kernel', 'ramdisk']
|
||||||
|
deploy_utils.validate_image_properties(task.context, d_info, props)
|
||||||
|
|
||||||
|
@METRICS.timer('iPXEBoot.prepare_ramdisk')
|
||||||
|
def prepare_ramdisk(self, task, ramdisk_params):
|
||||||
|
"""Prepares the boot of Ironic ramdisk using PXE.
|
||||||
|
|
||||||
|
This method prepares the boot of the deploy or rescue kernel/ramdisk
|
||||||
|
after reading relevant information from the node's driver_info and
|
||||||
|
instance_info.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:param ramdisk_params: the parameters to be passed to the ramdisk.
|
||||||
|
pxe driver passes these parameters as kernel command-line
|
||||||
|
arguments.
|
||||||
|
:param mode: Label indicating a deploy or rescue operation
|
||||||
|
being carried out on the node. Supported values are
|
||||||
|
'deploy' and 'rescue'. Defaults to 'deploy', indicating
|
||||||
|
deploy operation is being carried out.
|
||||||
|
:returns: None
|
||||||
|
:raises: MissingParameterValue, if some information is missing in
|
||||||
|
node's driver_info or instance_info.
|
||||||
|
:raises: InvalidParameterValue, if some information provided is
|
||||||
|
invalid.
|
||||||
|
:raises: IronicException, if some power or set boot boot device
|
||||||
|
operation failed on the node.
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
mode = deploy_utils.rescue_or_deploy_mode(node)
|
||||||
|
|
||||||
|
# NOTE(mjturek): At this point, the ipxe boot script should
|
||||||
|
# already exist as it is created at startup time. However, we
|
||||||
|
# call the boot script create method here to assert its
|
||||||
|
# existence and handle the unlikely case that it wasn't created
|
||||||
|
# or was deleted.
|
||||||
|
common_pxe_utils.create_ipxe_boot_script()
|
||||||
|
|
||||||
|
dhcp_opts = common_pxe_utils.dhcp_options_for_instance(task)
|
||||||
|
provider = dhcp_factory.DHCPFactory()
|
||||||
|
provider.update_dhcp(task, dhcp_opts)
|
||||||
|
|
||||||
|
pxe_info = pxe._get_image_info(node, mode=mode)
|
||||||
|
|
||||||
|
# NODE: Try to validate and fetch instance images only
|
||||||
|
# if we are in DEPLOYING state.
|
||||||
|
if node.provision_state == states.DEPLOYING:
|
||||||
|
pxe_info.update(
|
||||||
|
pxe._get_instance_image_info(task, ipxe_enabled=True))
|
||||||
|
boot_mode_utils.sync_boot_mode(task)
|
||||||
|
|
||||||
|
pxe_options = pxe._build_pxe_config_options(task, pxe_info)
|
||||||
|
pxe_options.update(ramdisk_params)
|
||||||
|
|
||||||
|
pxe_config_template = deploy_utils.get_pxe_config_template(node)
|
||||||
|
|
||||||
|
common_pxe_utils.create_pxe_config(task, pxe_options,
|
||||||
|
pxe_config_template,
|
||||||
|
ipxe_enabled=True)
|
||||||
|
persistent = strutils.bool_from_string(
|
||||||
|
node.driver_info.get('force_persistent_boot_device',
|
||||||
|
False))
|
||||||
|
manager_utils.node_set_boot_device(task, boot_devices.PXE,
|
||||||
|
persistent=persistent)
|
||||||
|
|
||||||
|
if CONF.pxe.ipxe_use_swift:
|
||||||
|
kernel_label = '%s_kernel' % mode
|
||||||
|
ramdisk_label = '%s_ramdisk' % mode
|
||||||
|
pxe_info.pop(kernel_label, None)
|
||||||
|
pxe_info.pop(ramdisk_label, None)
|
||||||
|
|
||||||
|
if pxe_info:
|
||||||
|
pxe._cache_ramdisk_kernel(task, pxe_info)
|
||||||
|
|
||||||
|
@METRICS.timer('iPXEBoot.clean_up_ramdisk')
|
||||||
|
def clean_up_ramdisk(self, task):
|
||||||
|
"""Cleans up the boot of ironic ramdisk.
|
||||||
|
|
||||||
|
This method cleans up the PXE environment that was setup for booting
|
||||||
|
the deploy or rescue ramdisk. It unlinks the deploy/rescue
|
||||||
|
kernel/ramdisk in the node's directory in tftproot and removes it's PXE
|
||||||
|
config.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:param mode: Label indicating a deploy or rescue operation
|
||||||
|
was carried out on the node. Supported values are 'deploy' and
|
||||||
|
'rescue'. Defaults to 'deploy', indicating deploy operation was
|
||||||
|
carried out.
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
mode = deploy_utils.rescue_or_deploy_mode(node)
|
||||||
|
try:
|
||||||
|
images_info = pxe._get_image_info(node, mode=mode)
|
||||||
|
except exception.MissingParameterValue as e:
|
||||||
|
LOG.warning('Could not get %(mode)s image info '
|
||||||
|
'to clean up images for node %(node)s: %(err)s',
|
||||||
|
{'mode': mode, 'node': node.uuid, 'err': e})
|
||||||
|
else:
|
||||||
|
pxe._clean_up_pxe_env(task, images_info)
|
||||||
|
|
||||||
|
@METRICS.timer('iPXEBoot.prepare_instance')
|
||||||
|
def prepare_instance(self, task):
|
||||||
|
"""Prepares the boot of instance.
|
||||||
|
|
||||||
|
This method prepares the boot of the instance after reading
|
||||||
|
relevant information from the node's instance_info. In case of netboot,
|
||||||
|
it updates the dhcp entries and switches the PXE config. In case of
|
||||||
|
localboot, it cleans up the PXE config.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
boot_mode_utils.sync_boot_mode(task)
|
||||||
|
|
||||||
|
node = task.node
|
||||||
|
boot_option = deploy_utils.get_boot_option(node)
|
||||||
|
boot_device = None
|
||||||
|
instance_image_info = {}
|
||||||
|
|
||||||
|
if boot_option == "ramdisk":
|
||||||
|
instance_image_info = pxe._get_instance_image_info(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
pxe._cache_ramdisk_kernel(task, instance_image_info)
|
||||||
|
|
||||||
|
if deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk":
|
||||||
|
pxe._prepare_instance_pxe_config(
|
||||||
|
task, instance_image_info,
|
||||||
|
iscsi_boot=deploy_utils.is_iscsi_boot(task),
|
||||||
|
ramdisk_boot=(boot_option == "ramdisk"),
|
||||||
|
ipxe_enabled=True)
|
||||||
|
boot_device = boot_devices.PXE
|
||||||
|
|
||||||
|
elif boot_option != "local":
|
||||||
|
if task.driver.storage.should_write_image(task):
|
||||||
|
# Make sure that the instance kernel/ramdisk is cached.
|
||||||
|
# This is for the takeover scenario for active nodes.
|
||||||
|
instance_image_info = pxe._get_instance_image_info(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
pxe._cache_ramdisk_kernel(task, instance_image_info)
|
||||||
|
|
||||||
|
# If it's going to PXE boot we need to update the DHCP server
|
||||||
|
dhcp_opts = common_pxe_utils.dhcp_options_for_instance(task)
|
||||||
|
provider = dhcp_factory.DHCPFactory()
|
||||||
|
provider.update_dhcp(task, dhcp_opts)
|
||||||
|
|
||||||
|
iwdi = task.node.driver_internal_info.get('is_whole_disk_image')
|
||||||
|
try:
|
||||||
|
root_uuid_or_disk_id = task.node.driver_internal_info[
|
||||||
|
'root_uuid_or_disk_id'
|
||||||
|
]
|
||||||
|
except KeyError:
|
||||||
|
if not task.driver.storage.should_write_image(task):
|
||||||
|
pass
|
||||||
|
elif not iwdi:
|
||||||
|
LOG.warning("The UUID for the root partition can't be "
|
||||||
|
"found, unable to switch the pxe config from "
|
||||||
|
"deployment mode to service (boot) mode for "
|
||||||
|
"node %(node)s", {"node": task.node.uuid})
|
||||||
|
else:
|
||||||
|
LOG.warning("The disk id for the whole disk image can't "
|
||||||
|
"be found, unable to switch the pxe config "
|
||||||
|
"from deployment mode to service (boot) mode "
|
||||||
|
"for node %(node)s. Booting the instance "
|
||||||
|
"from disk.", {"node": task.node.uuid})
|
||||||
|
common_pxe_utils.clean_up_pxe_config(task)
|
||||||
|
boot_device = boot_devices.DISK
|
||||||
|
else:
|
||||||
|
pxe._build_service_pxe_config(task, instance_image_info,
|
||||||
|
root_uuid_or_disk_id,
|
||||||
|
ipxe_enabled=True)
|
||||||
|
boot_device = boot_devices.PXE
|
||||||
|
else:
|
||||||
|
# If it's going to boot from the local disk, we don't need
|
||||||
|
# PXE config files. They still need to be generated as part
|
||||||
|
# of the prepare() because the deployment does PXE boot the
|
||||||
|
# deploy ramdisk
|
||||||
|
common_pxe_utils.clean_up_pxe_config(task)
|
||||||
|
boot_device = boot_devices.DISK
|
||||||
|
|
||||||
|
# NOTE(pas-ha) do not re-set boot device on ACTIVE nodes
|
||||||
|
# during takeover
|
||||||
|
if boot_device and task.node.provision_state != states.ACTIVE:
|
||||||
|
manager_utils.node_set_boot_device(task, boot_device,
|
||||||
|
persistent=True)
|
||||||
|
|
||||||
|
@METRICS.timer('iPXEBoot.clean_up_instance')
|
||||||
|
def clean_up_instance(self, task):
|
||||||
|
"""Cleans up the boot of instance.
|
||||||
|
|
||||||
|
This method cleans up the environment that was setup for booting
|
||||||
|
the instance. It unlinks the instance kernel/ramdisk in node's
|
||||||
|
directory in tftproot and removes the PXE config.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
|
||||||
|
try:
|
||||||
|
images_info = pxe._get_instance_image_info(task,
|
||||||
|
ipxe_enabled=True)
|
||||||
|
except exception.MissingParameterValue as e:
|
||||||
|
LOG.warning('Could not get instance image info '
|
||||||
|
'to clean up images for node %(node)s: %(err)s',
|
||||||
|
{'node': node.uuid, 'err': e})
|
||||||
|
else:
|
||||||
|
pxe._clean_up_pxe_env(task, images_info)
|
||||||
|
|
||||||
|
@METRICS.timer('iPXEBoot.validate_rescue')
|
||||||
|
def validate_rescue(self, task):
|
||||||
|
"""Validate that the node has required properties for rescue.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance with the node being checked
|
||||||
|
:raises: MissingParameterValue if node is missing one or more required
|
||||||
|
parameters
|
||||||
|
"""
|
||||||
|
pxe._parse_driver_info(task.node, mode='rescue')
|
@ -100,10 +100,15 @@ class TFTPImageCache(image_cache.ImageCache):
|
|||||||
cache_ttl=CONF.pxe.image_cache_ttl * 60)
|
cache_ttl=CONF.pxe.image_cache_ttl * 60)
|
||||||
|
|
||||||
|
|
||||||
def _cache_ramdisk_kernel(ctx, node, pxe_info):
|
def _cache_ramdisk_kernel(task, pxe_info):
|
||||||
"""Fetch the necessary kernels and ramdisks for the instance."""
|
"""Fetch the necessary kernels and ramdisks for the instance."""
|
||||||
fileutils.ensure_tree(
|
ctx = task.context
|
||||||
os.path.join(pxe_utils.get_root_dir(), node.uuid))
|
node = task.node
|
||||||
|
if CONF.pxe.ipxe_enabled:
|
||||||
|
path = os.path.join(pxe_utils.get_ipxe_root_dir(), node.uuid)
|
||||||
|
else:
|
||||||
|
path = os.path.join(pxe_utils.get_root_dir(), node.uuid)
|
||||||
|
fileutils.ensure_tree(path)
|
||||||
LOG.debug("Fetching necessary kernel and ramdisk for node %s",
|
LOG.debug("Fetching necessary kernel and ramdisk for node %s",
|
||||||
node.uuid)
|
node.uuid)
|
||||||
deploy_utils.fetch_images(ctx, TFTPImageCache(), list(pxe_info.values()),
|
deploy_utils.fetch_images(ctx, TFTPImageCache(), list(pxe_info.values()),
|
||||||
@ -131,9 +136,12 @@ def _clean_up_pxe_env(task, images_info):
|
|||||||
|
|
||||||
class PXEBoot(base.BootInterface):
|
class PXEBoot(base.BootInterface):
|
||||||
|
|
||||||
capabilities = ['iscsi_volume_boot', 'ramdisk_boot']
|
capabilities = ['iscsi_volume_boot', 'ramdisk_boot', 'ipxe_boot',
|
||||||
|
'pxe_boot']
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# TODO(TheJulia): Once the pxe/ipxe interfaces split is complete,
|
||||||
|
# this can be removed.
|
||||||
if CONF.pxe.ipxe_enabled:
|
if CONF.pxe.ipxe_enabled:
|
||||||
pxe_utils.create_ipxe_boot_script()
|
pxe_utils.create_ipxe_boot_script()
|
||||||
|
|
||||||
@ -168,6 +176,8 @@ class PXEBoot(base.BootInterface):
|
|||||||
_("Node %s does not have any port associated with it.")
|
_("Node %s does not have any port associated with it.")
|
||||||
% node.uuid)
|
% node.uuid)
|
||||||
|
|
||||||
|
# TODO(TheJulia): Once ipxe support is remove from the pxe
|
||||||
|
# interface, this can be removed.
|
||||||
if CONF.pxe.ipxe_enabled:
|
if CONF.pxe.ipxe_enabled:
|
||||||
if (not CONF.deploy.http_url
|
if (not CONF.deploy.http_url
|
||||||
or not CONF.deploy.http_root):
|
or not CONF.deploy.http_root):
|
||||||
@ -242,7 +252,7 @@ class PXEBoot(base.BootInterface):
|
|||||||
# NODE: Try to validate and fetch instance images only
|
# NODE: Try to validate and fetch instance images only
|
||||||
# if we are in DEPLOYING state.
|
# if we are in DEPLOYING state.
|
||||||
if node.provision_state == states.DEPLOYING:
|
if node.provision_state == states.DEPLOYING:
|
||||||
pxe_info.update(_get_instance_image_info(node, task.context))
|
pxe_info.update(_get_instance_image_info(task))
|
||||||
boot_mode_utils.sync_boot_mode(task)
|
boot_mode_utils.sync_boot_mode(task)
|
||||||
|
|
||||||
pxe_options = _build_pxe_config_options(task, pxe_info)
|
pxe_options = _build_pxe_config_options(task, pxe_info)
|
||||||
@ -251,7 +261,8 @@ class PXEBoot(base.BootInterface):
|
|||||||
pxe_config_template = deploy_utils.get_pxe_config_template(node)
|
pxe_config_template = deploy_utils.get_pxe_config_template(node)
|
||||||
|
|
||||||
pxe_utils.create_pxe_config(task, pxe_options,
|
pxe_utils.create_pxe_config(task, pxe_options,
|
||||||
pxe_config_template)
|
pxe_config_template,
|
||||||
|
ipxe_enabled=CONF.pxe.ipxe_enabled)
|
||||||
persistent = strutils.bool_from_string(
|
persistent = strutils.bool_from_string(
|
||||||
node.driver_info.get('force_persistent_boot_device',
|
node.driver_info.get('force_persistent_boot_device',
|
||||||
False))
|
False))
|
||||||
@ -265,7 +276,7 @@ class PXEBoot(base.BootInterface):
|
|||||||
pxe_info.pop(ramdisk_label, None)
|
pxe_info.pop(ramdisk_label, None)
|
||||||
|
|
||||||
if pxe_info:
|
if pxe_info:
|
||||||
_cache_ramdisk_kernel(task.context, node, pxe_info)
|
_cache_ramdisk_kernel(task, pxe_info)
|
||||||
|
|
||||||
@METRICS.timer('PXEBoot.clean_up_ramdisk')
|
@METRICS.timer('PXEBoot.clean_up_ramdisk')
|
||||||
def clean_up_ramdisk(self, task):
|
def clean_up_ramdisk(self, task):
|
||||||
@ -294,37 +305,6 @@ class PXEBoot(base.BootInterface):
|
|||||||
else:
|
else:
|
||||||
_clean_up_pxe_env(task, images_info)
|
_clean_up_pxe_env(task, images_info)
|
||||||
|
|
||||||
def _prepare_instance_pxe_config(self, task, image_info,
|
|
||||||
iscsi_boot=False,
|
|
||||||
ramdisk_boot=False):
|
|
||||||
"""Prepares the config file for PXE boot
|
|
||||||
|
|
||||||
:param task: a task from TaskManager.
|
|
||||||
:param image_info: a dict of values of instance image
|
|
||||||
metadata to set on the configuration file.
|
|
||||||
:param iscsi_boot: if boot is from an iSCSI volume or not.
|
|
||||||
:param ramdisk_boot: if the boot is to a ramdisk configuration.
|
|
||||||
:returns: None
|
|
||||||
"""
|
|
||||||
|
|
||||||
node = task.node
|
|
||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
|
||||||
provider = dhcp_factory.DHCPFactory()
|
|
||||||
provider.update_dhcp(task, dhcp_opts)
|
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
|
||||||
node.uuid)
|
|
||||||
if not os.path.isfile(pxe_config_path):
|
|
||||||
pxe_options = _build_pxe_config_options(
|
|
||||||
task, image_info, service=ramdisk_boot)
|
|
||||||
pxe_config_template = (
|
|
||||||
deploy_utils.get_pxe_config_template(node))
|
|
||||||
pxe_utils.create_pxe_config(
|
|
||||||
task, pxe_options, pxe_config_template)
|
|
||||||
deploy_utils.switch_pxe_config(
|
|
||||||
pxe_config_path, None,
|
|
||||||
boot_mode_utils.get_boot_mode_for_deploy(node), False,
|
|
||||||
iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot)
|
|
||||||
|
|
||||||
@METRICS.timer('PXEBoot.prepare_instance')
|
@METRICS.timer('PXEBoot.prepare_instance')
|
||||||
def prepare_instance(self, task):
|
def prepare_instance(self, task):
|
||||||
"""Prepares the boot of instance.
|
"""Prepares the boot of instance.
|
||||||
@ -337,6 +317,7 @@ class PXEBoot(base.BootInterface):
|
|||||||
:param task: a task from TaskManager.
|
:param task: a task from TaskManager.
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
ipxe_enabled = CONF.pxe.ipxe_enabled
|
||||||
boot_mode_utils.sync_boot_mode(task)
|
boot_mode_utils.sync_boot_mode(task)
|
||||||
|
|
||||||
node = task.node
|
node = task.node
|
||||||
@ -344,25 +325,24 @@ class PXEBoot(base.BootInterface):
|
|||||||
boot_device = None
|
boot_device = None
|
||||||
instance_image_info = {}
|
instance_image_info = {}
|
||||||
if boot_option == "ramdisk":
|
if boot_option == "ramdisk":
|
||||||
instance_image_info = _get_instance_image_info(
|
instance_image_info = _get_instance_image_info(task)
|
||||||
task.node, task.context)
|
_cache_ramdisk_kernel(task,
|
||||||
_cache_ramdisk_kernel(task.context, task.node,
|
|
||||||
instance_image_info)
|
instance_image_info)
|
||||||
|
|
||||||
if deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk":
|
if deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk":
|
||||||
self._prepare_instance_pxe_config(
|
_prepare_instance_pxe_config(
|
||||||
task, instance_image_info,
|
task, instance_image_info,
|
||||||
iscsi_boot=deploy_utils.is_iscsi_boot(task),
|
iscsi_boot=deploy_utils.is_iscsi_boot(task),
|
||||||
ramdisk_boot=(boot_option == "ramdisk"))
|
ramdisk_boot=(boot_option == "ramdisk"),
|
||||||
|
ipxe_enabled=CONF.pxe.ipxe_enabled)
|
||||||
boot_device = boot_devices.PXE
|
boot_device = boot_devices.PXE
|
||||||
|
|
||||||
elif boot_option != "local":
|
elif boot_option != "local":
|
||||||
if task.driver.storage.should_write_image(task):
|
if task.driver.storage.should_write_image(task):
|
||||||
# Make sure that the instance kernel/ramdisk is cached.
|
# Make sure that the instance kernel/ramdisk is cached.
|
||||||
# This is for the takeover scenario for active nodes.
|
# This is for the takeover scenario for active nodes.
|
||||||
instance_image_info = _get_instance_image_info(
|
instance_image_info = _get_instance_image_info(task)
|
||||||
task.node, task.context)
|
_cache_ramdisk_kernel(task,
|
||||||
_cache_ramdisk_kernel(task.context, task.node,
|
|
||||||
instance_image_info)
|
instance_image_info)
|
||||||
|
|
||||||
# If it's going to PXE boot we need to update the DHCP server
|
# If it's going to PXE boot we need to update the DHCP server
|
||||||
@ -393,7 +373,8 @@ class PXEBoot(base.BootInterface):
|
|||||||
boot_device = boot_devices.DISK
|
boot_device = boot_devices.DISK
|
||||||
else:
|
else:
|
||||||
_build_service_pxe_config(task, instance_image_info,
|
_build_service_pxe_config(task, instance_image_info,
|
||||||
root_uuid_or_disk_id)
|
root_uuid_or_disk_id,
|
||||||
|
ipxe_enabled=ipxe_enabled)
|
||||||
boot_device = boot_devices.PXE
|
boot_device = boot_devices.PXE
|
||||||
else:
|
else:
|
||||||
# If it's going to boot from the local disk, we don't need
|
# If it's going to boot from the local disk, we don't need
|
||||||
@ -423,7 +404,7 @@ class PXEBoot(base.BootInterface):
|
|||||||
node = task.node
|
node = task.node
|
||||||
|
|
||||||
try:
|
try:
|
||||||
images_info = _get_instance_image_info(node, task.context)
|
images_info = _get_instance_image_info(task)
|
||||||
except exception.MissingParameterValue as e:
|
except exception.MissingParameterValue as e:
|
||||||
LOG.warning('Could not get instance image info '
|
LOG.warning('Could not get instance image info '
|
||||||
'to clean up images for node %(node)s: %(err)s',
|
'to clean up images for node %(node)s: %(err)s',
|
||||||
|
@ -76,7 +76,12 @@ class CinderStorage(base.StorageInterface):
|
|||||||
iscsi_uuids_found = []
|
iscsi_uuids_found = []
|
||||||
wwpn_found = 0
|
wwpn_found = 0
|
||||||
wwnn_found = 0
|
wwnn_found = 0
|
||||||
ipxe_enabled = CONF.pxe.ipxe_enabled
|
ipxe_enabled = False
|
||||||
|
if 'pxe_boot' in task.driver.boot.capabilities:
|
||||||
|
if CONF.pxe.ipxe_enabled:
|
||||||
|
ipxe_enabled = True
|
||||||
|
elif 'ipxe_boot' in task.driver.boot.capabilities:
|
||||||
|
ipxe_enabled = True
|
||||||
|
|
||||||
for connector in task.volume_connectors:
|
for connector in task.volume_connectors:
|
||||||
if (connector.type in VALID_ISCSI_TYPES
|
if (connector.type in VALID_ISCSI_TYPES
|
||||||
@ -84,7 +89,8 @@ class CinderStorage(base.StorageInterface):
|
|||||||
iscsi_uuids_found.append(connector.uuid)
|
iscsi_uuids_found.append(connector.uuid)
|
||||||
if not ipxe_enabled:
|
if not ipxe_enabled:
|
||||||
msg = _("The [pxe]/ipxe_enabled option must "
|
msg = _("The [pxe]/ipxe_enabled option must "
|
||||||
"be set to True to support network "
|
"be set to True or the boot interface "
|
||||||
|
"must be set to ``ipxe`` to support network "
|
||||||
"booting to an iSCSI volume.")
|
"booting to an iSCSI volume.")
|
||||||
self._fail_validation(task, msg)
|
self._fail_validation(task, msg)
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ from oslo_log import log
|
|||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
|
from ironic.common import pxe_utils as common_pxe_utils
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -35,10 +36,11 @@ class ExternalStorage(base.StorageInterface):
|
|||||||
raise exception(msg)
|
raise exception(msg)
|
||||||
|
|
||||||
if (not self.should_write_image(task)
|
if (not self.should_write_image(task)
|
||||||
and not CONF.pxe.ipxe_enabled):
|
and not common_pxe_utils.is_ipxe_enabled(task)):
|
||||||
msg = _("The [pxe]/ipxe_enabled option must "
|
msg = _("The [pxe]/ipxe_enabled option must "
|
||||||
"be set to True to support network "
|
"be set to True to support network "
|
||||||
"booting to an iSCSI volume.")
|
"booting to an iSCSI volume or the boot "
|
||||||
|
"interface must be set to ``ipxe``.")
|
||||||
_fail_validation(task, msg)
|
_fail_validation(task, msg)
|
||||||
|
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
|
@ -344,7 +344,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
]
|
]
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.ports = [port_1, port_2]
|
task.ports = [port_1, port_2]
|
||||||
pxe_utils._link_mac_pxe_configs(task)
|
pxe_utils._link_mac_pxe_configs(task, ipxe_enabled=True)
|
||||||
|
|
||||||
unlink_mock.assert_has_calls(unlink_calls)
|
unlink_mock.assert_has_calls(unlink_calls)
|
||||||
create_link_mock.assert_has_calls(create_link_calls)
|
create_link_mock.assert_has_calls(create_link_calls)
|
||||||
@ -487,7 +487,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
{'pxe_options': self.pxe_options,
|
{'pxe_options': self.pxe_options,
|
||||||
'ROOT': '{{ ROOT }}',
|
'ROOT': '{{ ROOT }}',
|
||||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
||||||
link_ip_configs_mock.assert_called_once_with(task, True)
|
link_ip_configs_mock.assert_called_once_with(task, True, False)
|
||||||
|
|
||||||
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||||
@ -519,7 +519,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
{'pxe_options': self.pxe_options,
|
{'pxe_options': self.pxe_options,
|
||||||
'ROOT': '(( ROOT ))',
|
'ROOT': '(( ROOT ))',
|
||||||
'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'})
|
'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'})
|
||||||
link_ip_configs_mock.assert_called_once_with(task, False)
|
link_ip_configs_mock.assert_called_once_with(task, False, False)
|
||||||
|
|
||||||
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||||
@ -558,8 +558,9 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
{'pxe_options': self.pxe_options,
|
{'pxe_options': self.pxe_options,
|
||||||
'ROOT': '(( ROOT ))',
|
'ROOT': '(( ROOT ))',
|
||||||
'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'})
|
'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'})
|
||||||
link_mac_pxe_configs_mock.assert_called_once_with(task)
|
link_mac_pxe_configs_mock.assert_called_once_with(
|
||||||
link_ip_configs_mock.assert_called_once_with(task, False)
|
task, ipxe_enabled=False)
|
||||||
|
link_ip_configs_mock.assert_called_once_with(task, False, False)
|
||||||
|
|
||||||
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||||
@ -578,7 +579,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.node.properties['capabilities'] = 'boot_mode:uefi'
|
task.node.properties['capabilities'] = 'boot_mode:uefi'
|
||||||
pxe_utils.create_pxe_config(task, self.ipxe_options,
|
pxe_utils.create_pxe_config(task, self.ipxe_options,
|
||||||
ipxe_template)
|
ipxe_template, ipxe_enabled=True)
|
||||||
|
|
||||||
ensure_calls = [
|
ensure_calls = [
|
||||||
mock.call(os.path.join(CONF.deploy.http_root, self.node.uuid)),
|
mock.call(os.path.join(CONF.deploy.http_root, self.node.uuid)),
|
||||||
@ -591,9 +592,10 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
{'pxe_options': self.ipxe_options,
|
{'pxe_options': self.ipxe_options,
|
||||||
'ROOT': '{{ ROOT }}',
|
'ROOT': '{{ ROOT }}',
|
||||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
||||||
link_mac_pxe_mock.assert_called_once_with(task)
|
link_mac_pxe_mock.assert_called_once_with(task, ipxe_enabled=True)
|
||||||
|
|
||||||
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(
|
||||||
|
self.node.uuid, ipxe_enabled=True)
|
||||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||||
render_mock.return_value)
|
render_mock.return_value)
|
||||||
|
|
||||||
@ -672,11 +674,10 @@ class TestPXEUtils(db_base.DbTestCase):
|
|||||||
pxe_utils._get_pxe_mac_path(mac))
|
pxe_utils._get_pxe_mac_path(mac))
|
||||||
|
|
||||||
def test__get_pxe_mac_path_ipxe(self):
|
def test__get_pxe_mac_path_ipxe(self):
|
||||||
self.config(ipxe_enabled=True, group='pxe')
|
|
||||||
self.config(http_root='/httpboot', group='deploy')
|
self.config(http_root='/httpboot', group='deploy')
|
||||||
mac = '00:11:22:33:AA:BB:CC'
|
mac = '00:11:22:33:AA:BB:CC'
|
||||||
self.assertEqual('/httpboot/pxelinux.cfg/00-11-22-33-aa-bb-cc',
|
self.assertEqual('/httpboot/pxelinux.cfg/00-11-22-33-aa-bb-cc',
|
||||||
pxe_utils._get_pxe_mac_path(mac))
|
pxe_utils._get_pxe_mac_path(mac, ipxe_enabled=True))
|
||||||
|
|
||||||
def test__get_pxe_ip_address_path(self):
|
def test__get_pxe_ip_address_path(self):
|
||||||
ipaddress = '10.10.0.1'
|
ipaddress = '10.10.0.1'
|
||||||
|
@ -874,24 +874,24 @@ class SwitchPxeConfigTestCase(tests_base.TestCase):
|
|||||||
|
|
||||||
def test_switch_ipxe_config_partition_image(self):
|
def test_switch_ipxe_config_partition_image(self):
|
||||||
boot_mode = 'bios'
|
boot_mode = 'bios'
|
||||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
|
||||||
fname = self._create_config(ipxe=True)
|
fname = self._create_config(ipxe=True)
|
||||||
utils.switch_pxe_config(fname,
|
utils.switch_pxe_config(fname,
|
||||||
'12345678-1234-1234-1234-1234567890abcdef',
|
'12345678-1234-1234-1234-1234567890abcdef',
|
||||||
boot_mode,
|
boot_mode,
|
||||||
False)
|
False,
|
||||||
|
ipxe_enabled=True)
|
||||||
with open(fname, 'r') as f:
|
with open(fname, 'r') as f:
|
||||||
pxeconf = f.read()
|
pxeconf = f.read()
|
||||||
self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
|
self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
|
||||||
|
|
||||||
def test_switch_ipxe_config_whole_disk_image(self):
|
def test_switch_ipxe_config_whole_disk_image(self):
|
||||||
boot_mode = 'bios'
|
boot_mode = 'bios'
|
||||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
|
||||||
fname = self._create_config(ipxe=True)
|
fname = self._create_config(ipxe=True)
|
||||||
utils.switch_pxe_config(fname,
|
utils.switch_pxe_config(fname,
|
||||||
'0x12345678',
|
'0x12345678',
|
||||||
boot_mode,
|
boot_mode,
|
||||||
True)
|
True,
|
||||||
|
ipxe_enabled=True)
|
||||||
with open(fname, 'r') as f:
|
with open(fname, 'r') as f:
|
||||||
pxeconf = f.read()
|
pxeconf = f.read()
|
||||||
self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
|
self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
|
||||||
@ -946,36 +946,36 @@ class SwitchPxeConfigTestCase(tests_base.TestCase):
|
|||||||
|
|
||||||
def test_switch_uefi_ipxe_config_partition_image(self):
|
def test_switch_uefi_ipxe_config_partition_image(self):
|
||||||
boot_mode = 'uefi'
|
boot_mode = 'uefi'
|
||||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
|
||||||
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
||||||
utils.switch_pxe_config(fname,
|
utils.switch_pxe_config(fname,
|
||||||
'12345678-1234-1234-1234-1234567890abcdef',
|
'12345678-1234-1234-1234-1234567890abcdef',
|
||||||
boot_mode,
|
boot_mode,
|
||||||
False)
|
False,
|
||||||
|
ipxe_enabled=True)
|
||||||
with open(fname, 'r') as f:
|
with open(fname, 'r') as f:
|
||||||
pxeconf = f.read()
|
pxeconf = f.read()
|
||||||
self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
|
self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
|
||||||
|
|
||||||
def test_switch_uefi_ipxe_config_whole_disk_image(self):
|
def test_switch_uefi_ipxe_config_whole_disk_image(self):
|
||||||
boot_mode = 'uefi'
|
boot_mode = 'uefi'
|
||||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
|
||||||
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
||||||
utils.switch_pxe_config(fname,
|
utils.switch_pxe_config(fname,
|
||||||
'0x12345678',
|
'0x12345678',
|
||||||
boot_mode,
|
boot_mode,
|
||||||
True)
|
True,
|
||||||
|
ipxe_enabled=True)
|
||||||
with open(fname, 'r') as f:
|
with open(fname, 'r') as f:
|
||||||
pxeconf = f.read()
|
pxeconf = f.read()
|
||||||
self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
|
self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
|
||||||
|
|
||||||
def test_switch_ipxe_iscsi_boot(self):
|
def test_switch_ipxe_iscsi_boot(self):
|
||||||
boot_mode = 'iscsi'
|
boot_mode = 'iscsi'
|
||||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
|
||||||
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
||||||
utils.switch_pxe_config(fname,
|
utils.switch_pxe_config(fname,
|
||||||
'0x12345678',
|
'0x12345678',
|
||||||
boot_mode,
|
boot_mode,
|
||||||
False, False, True)
|
False, False, True,
|
||||||
|
ipxe_enabled=True)
|
||||||
with open(fname, 'r') as f:
|
with open(fname, 'r') as f:
|
||||||
pxeconf = f.read()
|
pxeconf = f.read()
|
||||||
self.assertEqual(_IPXECONF_BOOT_ISCSI_NO_CONFIG, pxeconf)
|
self.assertEqual(_IPXECONF_BOOT_ISCSI_NO_CONFIG, pxeconf)
|
||||||
|
824
ironic/tests/unit/drivers/modules/test_ipxe.py
Normal file
824
ironic/tests/unit/drivers/modules/test_ipxe.py
Normal file
@ -0,0 +1,824 @@
|
|||||||
|
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""Test class for iPXE driver."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_serialization import jsonutils as json
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from ironic.common import boot_devices
|
||||||
|
from ironic.common import boot_modes
|
||||||
|
from ironic.common import dhcp_factory
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common.glance_service import base_image_service
|
||||||
|
from ironic.common import pxe_utils
|
||||||
|
from ironic.common import states
|
||||||
|
from ironic.common import utils as common_utils
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.conductor import utils as manager_utils
|
||||||
|
from ironic.drivers import base as drivers_base
|
||||||
|
from ironic.drivers.modules import agent_base_vendor
|
||||||
|
from ironic.drivers.modules import deploy_utils
|
||||||
|
from ironic.drivers.modules import ipxe
|
||||||
|
from ironic.drivers.modules import pxe
|
||||||
|
from ironic.drivers.modules.storage import noop as noop_storage
|
||||||
|
from ironic.tests.unit.db import base as db_base
|
||||||
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
|
||||||
|
DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
|
||||||
|
DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(TheJulia): This code is essentially a bulk copy of the
|
||||||
|
# test_pxe file with some contextual modifications to enforce
|
||||||
|
# use of ipxe while also explicitly having it globally disabled
|
||||||
|
# in the conductor.
|
||||||
|
@mock.patch.object(ipxe.iPXEBoot, '__init__', lambda self: None)
|
||||||
|
class iPXEBootTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
driver = 'fake-hardware'
|
||||||
|
boot_interface = 'ipxe'
|
||||||
|
driver_info = DRV_INFO_DICT
|
||||||
|
driver_internal_info = DRV_INTERNAL_INFO_DICT
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(iPXEBootTestCase, self).setUp()
|
||||||
|
self.context.auth_token = 'fake'
|
||||||
|
self.config_temp_dir('tftp_root', group='pxe')
|
||||||
|
self.config_temp_dir('images_path', group='pxe')
|
||||||
|
self.config_temp_dir('http_root', group='deploy')
|
||||||
|
self.config(group='deploy', http_url='http://myserver')
|
||||||
|
instance_info = INST_INFO_DICT
|
||||||
|
|
||||||
|
self.config(enabled_boot_interfaces=[self.boot_interface,
|
||||||
|
'ipxe', 'fake'])
|
||||||
|
self.node = obj_utils.create_test_node(
|
||||||
|
self.context,
|
||||||
|
driver=self.driver,
|
||||||
|
boot_interface=self.boot_interface,
|
||||||
|
# Avoid fake properties in get_properties() output
|
||||||
|
vendor_interface='no-vendor',
|
||||||
|
instance_info=instance_info,
|
||||||
|
driver_info=self.driver_info,
|
||||||
|
driver_internal_info=self.driver_internal_info)
|
||||||
|
self.port = obj_utils.create_test_port(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
self.config(group='conductor', api_url='http://127.0.0.1:1234/')
|
||||||
|
|
||||||
|
def test_get_properties(self):
|
||||||
|
expected = ipxe.COMMON_PROPERTIES
|
||||||
|
expected.update(agent_base_vendor.VENDOR_PROPERTIES)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(expected, task.driver.get_properties())
|
||||||
|
|
||||||
|
@mock.patch.object(base_image_service.BaseImageService, '_show',
|
||||||
|
autospec=True)
|
||||||
|
def test_validate_good(self, mock_glance):
|
||||||
|
mock_glance.return_value = {'properties': {'kernel_id': 'fake-kernel',
|
||||||
|
'ramdisk_id': 'fake-initr'}}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.boot.validate(task)
|
||||||
|
|
||||||
|
@mock.patch.object(base_image_service.BaseImageService, '_show',
|
||||||
|
autospec=True)
|
||||||
|
def test_validate_good_whole_disk_image(self, mock_glance):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||||
|
task.driver.boot.validate(task)
|
||||||
|
|
||||||
|
@mock.patch.object(base_image_service.BaseImageService, '_show',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(noop_storage.NoopStorage, 'should_write_image',
|
||||||
|
autospec=True)
|
||||||
|
def test_validate_skip_check_write_image_false(self, mock_write,
|
||||||
|
mock_glance):
|
||||||
|
mock_write.return_value = False
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.boot.validate(task)
|
||||||
|
self.assertFalse(mock_glance.called)
|
||||||
|
|
||||||
|
def test_validate_fail_missing_deploy_kernel(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
del task.node.driver_info['deploy_kernel']
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
def test_validate_fail_missing_deploy_ramdisk(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
del task.node.driver_info['deploy_ramdisk']
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
def test_validate_fail_missing_image_source(self):
|
||||||
|
info = dict(INST_INFO_DICT)
|
||||||
|
del info['image_source']
|
||||||
|
self.node.instance_info = json.dumps(info)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node['instance_info'] = json.dumps(info)
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
def test_validate_fail_no_port(self):
|
||||||
|
new_node = obj_utils.create_test_node(
|
||||||
|
self.context,
|
||||||
|
uuid='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||||
|
driver=self.driver, boot_interface=self.boot_interface,
|
||||||
|
instance_info=INST_INFO_DICT, driver_info=DRV_INFO_DICT)
|
||||||
|
with task_manager.acquire(self.context, new_node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
def test_validate_fail_trusted_boot_with_secure_boot(self):
|
||||||
|
instance_info = {"boot_option": "netboot",
|
||||||
|
"secure_boot": "true",
|
||||||
|
"trusted_boot": "true"}
|
||||||
|
properties = {'capabilities': 'trusted_boot:true'}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.instance_info['capabilities'] = instance_info
|
||||||
|
task.node.properties = properties
|
||||||
|
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
def test_validate_fail_invalid_trusted_boot_value(self):
|
||||||
|
properties = {'capabilities': 'trusted_boot:value'}
|
||||||
|
instance_info = {"trusted_boot": "value"}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.properties = properties
|
||||||
|
task.node.instance_info['capabilities'] = instance_info
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
@mock.patch.object(base_image_service.BaseImageService, '_show',
|
||||||
|
autospec=True)
|
||||||
|
def test_validate_fail_no_image_kernel_ramdisk_props(self, mock_glance):
|
||||||
|
mock_glance.return_value = {'properties': {}}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
task.driver.boot.validate,
|
||||||
|
task)
|
||||||
|
|
||||||
|
@mock.patch.object(base_image_service.BaseImageService, '_show',
|
||||||
|
autospec=True)
|
||||||
|
def test_validate_fail_glance_image_doesnt_exists(self, mock_glance):
|
||||||
|
mock_glance.side_effect = exception.ImageNotFound('not found')
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
@mock.patch.object(base_image_service.BaseImageService, '_show',
|
||||||
|
autospec=True)
|
||||||
|
def test_validate_fail_glance_conn_problem(self, mock_glance):
|
||||||
|
exceptions = (exception.GlanceConnectionFailed('connection fail'),
|
||||||
|
exception.ImageNotAuthorized('not authorized'),
|
||||||
|
exception.Invalid('invalid'))
|
||||||
|
mock_glance.side_effect = exceptions
|
||||||
|
for exc in exceptions:
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
# TODO(TheJulia): Many of the interfaces mocked below are private PXE
|
||||||
|
# interface methods. As time progresses, these will need to be migrated
|
||||||
|
# and refactored as we begin to separate PXE and iPXE interfaces.
|
||||||
|
@mock.patch.object(manager_utils, 'node_get_boot_mode', autospec=True)
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(dhcp_factory, 'DHCPFactory')
|
||||||
|
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_get_image_info', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_cache_ramdisk_kernel', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_build_pxe_config_options', autospec=True)
|
||||||
|
@mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True)
|
||||||
|
def _test_prepare_ramdisk(self, mock_pxe_config,
|
||||||
|
mock_build_pxe, mock_cache_r_k,
|
||||||
|
mock_deploy_img_info,
|
||||||
|
mock_instance_img_info,
|
||||||
|
dhcp_factory_mock,
|
||||||
|
set_boot_device_mock,
|
||||||
|
get_boot_mode_mock,
|
||||||
|
uefi=False,
|
||||||
|
cleaning=False,
|
||||||
|
ipxe_use_swift=False,
|
||||||
|
whole_disk_image=False,
|
||||||
|
mode='deploy',
|
||||||
|
node_boot_mode=None):
|
||||||
|
mock_build_pxe.return_value = {}
|
||||||
|
kernel_label = '%s_kernel' % mode
|
||||||
|
ramdisk_label = '%s_ramdisk' % mode
|
||||||
|
mock_deploy_img_info.return_value = {kernel_label: 'a',
|
||||||
|
ramdisk_label: 'r'}
|
||||||
|
if whole_disk_image:
|
||||||
|
mock_instance_img_info.return_value = {}
|
||||||
|
else:
|
||||||
|
mock_instance_img_info.return_value = {'kernel': 'b'}
|
||||||
|
mock_pxe_config.return_value = None
|
||||||
|
mock_cache_r_k.return_value = None
|
||||||
|
provider_mock = mock.MagicMock()
|
||||||
|
dhcp_factory_mock.return_value = provider_mock
|
||||||
|
get_boot_mode_mock.return_value = node_boot_mode
|
||||||
|
driver_internal_info = self.node.driver_internal_info
|
||||||
|
driver_internal_info['is_whole_disk_image'] = whole_disk_image
|
||||||
|
self.node.driver_internal_info = driver_internal_info
|
||||||
|
if mode == 'rescue':
|
||||||
|
mock_deploy_img_info.return_value = {
|
||||||
|
'rescue_kernel': 'a',
|
||||||
|
'rescue_ramdisk': 'r'}
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||||
|
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
|
||||||
|
mock_deploy_img_info.assert_called_once_with(task.node, mode=mode)
|
||||||
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
|
if self.node.provision_state == states.DEPLOYING:
|
||||||
|
get_boot_mode_mock.assert_called_once_with(task)
|
||||||
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
|
boot_devices.PXE,
|
||||||
|
persistent=False)
|
||||||
|
if ipxe_use_swift:
|
||||||
|
if whole_disk_image:
|
||||||
|
self.assertFalse(mock_cache_r_k.called)
|
||||||
|
else:
|
||||||
|
mock_cache_r_k.assert_called_once_with(
|
||||||
|
task, {'kernel': 'b'})
|
||||||
|
mock_instance_img_info.assert_called_once_with(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
elif not cleaning and mode == 'deploy':
|
||||||
|
mock_cache_r_k.assert_called_once_with(
|
||||||
|
task, {'deploy_kernel': 'a', 'deploy_ramdisk': 'r',
|
||||||
|
'kernel': 'b'})
|
||||||
|
mock_instance_img_info.assert_called_once_with(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
elif mode == 'deploy':
|
||||||
|
mock_cache_r_k.assert_called_once_with(
|
||||||
|
task, {'deploy_kernel': 'a', 'deploy_ramdisk': 'r'})
|
||||||
|
elif mode == 'rescue':
|
||||||
|
mock_cache_r_k.assert_called_once_with(
|
||||||
|
task, {'rescue_kernel': 'a', 'rescue_ramdisk': 'r'})
|
||||||
|
if uefi:
|
||||||
|
mock_pxe_config.assert_called_once_with(
|
||||||
|
task, {'foo': 'bar'}, CONF.pxe.uefi_pxe_config_template,
|
||||||
|
ipxe_enabled=True)
|
||||||
|
else:
|
||||||
|
mock_pxe_config.assert_called_once_with(
|
||||||
|
task, {'foo': 'bar'}, CONF.pxe.pxe_config_template,
|
||||||
|
ipxe_enabled=True)
|
||||||
|
|
||||||
|
def test_prepare_ramdisk(self):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk()
|
||||||
|
|
||||||
|
def test_prepare_ramdisk_rescue(self):
|
||||||
|
self.node.provision_state = states.RESCUING
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(mode='rescue')
|
||||||
|
|
||||||
|
def test_prepare_ramdisk_uefi(self):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(uefi=True)
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isfile', lambda path: True)
|
||||||
|
@mock.patch.object(common_utils, 'file_has_content', lambda *args: False)
|
||||||
|
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||||
|
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||||
|
def test_prepare_ramdisk_ipxe_with_copy_file_different(
|
||||||
|
self, render_mock, write_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self.config(group='pxe', ipxe_enabled=False)
|
||||||
|
render_mock.return_value = 'foo'
|
||||||
|
self._test_prepare_ramdisk()
|
||||||
|
write_mock.assert_called_once_with(
|
||||||
|
os.path.join(
|
||||||
|
CONF.deploy.http_root,
|
||||||
|
os.path.basename(CONF.pxe.ipxe_boot_script)),
|
||||||
|
'foo')
|
||||||
|
render_mock.assert_called_once_with(
|
||||||
|
CONF.pxe.ipxe_boot_script,
|
||||||
|
{'ipxe_for_mac_uri': 'pxelinux.cfg/'})
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isfile', lambda path: False)
|
||||||
|
@mock.patch('ironic.common.utils.file_has_content', autospec=True)
|
||||||
|
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||||
|
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||||
|
def test_prepare_ramdisk_ipxe_with_copy_no_file(
|
||||||
|
self, render_mock, write_mock, file_has_content_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self.config(group='pxe', ipxe_enabled=False)
|
||||||
|
render_mock.return_value = 'foo'
|
||||||
|
self._test_prepare_ramdisk()
|
||||||
|
self.assertFalse(file_has_content_mock.called)
|
||||||
|
write_mock.assert_called_once_with(
|
||||||
|
os.path.join(
|
||||||
|
CONF.deploy.http_root,
|
||||||
|
os.path.basename(CONF.pxe.ipxe_boot_script)),
|
||||||
|
'foo')
|
||||||
|
render_mock.assert_called_once_with(
|
||||||
|
CONF.pxe.ipxe_boot_script,
|
||||||
|
{'ipxe_for_mac_uri': 'pxelinux.cfg/'})
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isfile', lambda path: True)
|
||||||
|
@mock.patch.object(common_utils, 'file_has_content', lambda *args: True)
|
||||||
|
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||||
|
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||||
|
def test_prepare_ramdisk_ipxe_without_copy(
|
||||||
|
self, render_mock, write_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self.config(group='pxe', ipxe_enabled=False)
|
||||||
|
self._test_prepare_ramdisk()
|
||||||
|
self.assertFalse(write_mock.called)
|
||||||
|
|
||||||
|
@mock.patch.object(common_utils, 'render_template', lambda *args: 'foo')
|
||||||
|
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||||
|
def test_prepare_ramdisk_ipxe_swift(self, write_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self.config(group='pxe', ipxe_enabled=False)
|
||||||
|
self.config(group='pxe', ipxe_use_swift=True)
|
||||||
|
self._test_prepare_ramdisk(ipxe_use_swift=True)
|
||||||
|
write_mock.assert_called_once_with(
|
||||||
|
os.path.join(
|
||||||
|
CONF.deploy.http_root,
|
||||||
|
os.path.basename(CONF.pxe.ipxe_boot_script)),
|
||||||
|
'foo')
|
||||||
|
|
||||||
|
@mock.patch.object(common_utils, 'render_template', lambda *args: 'foo')
|
||||||
|
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||||
|
def test_prepare_ramdisk_ipxe_swift_whole_disk_image(
|
||||||
|
self, write_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self.config(group='pxe', ipxe_enabled=False)
|
||||||
|
self.config(group='pxe', ipxe_use_swift=True)
|
||||||
|
self._test_prepare_ramdisk(ipxe_use_swift=True, whole_disk_image=True)
|
||||||
|
write_mock.assert_called_once_with(
|
||||||
|
os.path.join(
|
||||||
|
CONF.deploy.http_root,
|
||||||
|
os.path.basename(CONF.pxe.ipxe_boot_script)),
|
||||||
|
'foo')
|
||||||
|
|
||||||
|
def test_prepare_ramdisk_cleaning(self):
|
||||||
|
self.node.provision_state = states.CLEANING
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(cleaning=True)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_boot_mode_on_bm(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(uefi=True)
|
||||||
|
set_boot_mode_mock.assert_called_once_with(mock.ANY, boot_modes.UEFI)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_boot_mode_on_ironic(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(node_boot_mode=boot_modes.LEGACY_BIOS)
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
driver_internal_info = task.node.driver_internal_info
|
||||||
|
self.assertIn('deploy_boot_mode', driver_internal_info)
|
||||||
|
self.assertEqual(boot_modes.LEGACY_BIOS,
|
||||||
|
driver_internal_info['deploy_boot_mode'])
|
||||||
|
self.assertEqual(set_boot_mode_mock.call_count, 0)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_default_boot_mode_on_ironic_bios(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
self.config(default_boot_mode=boot_modes.LEGACY_BIOS, group='deploy')
|
||||||
|
|
||||||
|
self._test_prepare_ramdisk()
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
driver_internal_info = task.node.driver_internal_info
|
||||||
|
self.assertIn('deploy_boot_mode', driver_internal_info)
|
||||||
|
self.assertEqual(boot_modes.LEGACY_BIOS,
|
||||||
|
driver_internal_info['deploy_boot_mode'])
|
||||||
|
self.assertEqual(set_boot_mode_mock.call_count, 1)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_default_boot_mode_on_ironic_uefi(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
self.config(default_boot_mode=boot_modes.UEFI, group='deploy')
|
||||||
|
|
||||||
|
self._test_prepare_ramdisk(uefi=True)
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
driver_internal_info = task.node.driver_internal_info
|
||||||
|
self.assertIn('deploy_boot_mode', driver_internal_info)
|
||||||
|
self.assertEqual(boot_modes.UEFI,
|
||||||
|
driver_internal_info['deploy_boot_mode'])
|
||||||
|
self.assertEqual(set_boot_mode_mock.call_count, 1)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_conflicting_boot_modes(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(uefi=True,
|
||||||
|
node_boot_mode=boot_modes.LEGACY_BIOS)
|
||||||
|
set_boot_mode_mock.assert_called_once_with(mock.ANY, boot_modes.UEFI)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_conflicting_boot_modes_set_unsupported(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
set_boot_mode_mock.side_effect = exception.UnsupportedDriverExtension(
|
||||||
|
extension='management', driver='test-driver'
|
||||||
|
)
|
||||||
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
|
self._test_prepare_ramdisk,
|
||||||
|
uefi=True, node_boot_mode=boot_modes.LEGACY_BIOS)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_boot_mode_not_called(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(uefi=True, node_boot_mode=boot_modes.UEFI)
|
||||||
|
self.assertEqual(set_boot_mode_mock.call_count, 0)
|
||||||
|
|
||||||
|
@mock.patch.object(pxe, '_clean_up_pxe_env', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_get_image_info', autospec=True)
|
||||||
|
def _test_clean_up_ramdisk(self, get_image_info_mock,
|
||||||
|
clean_up_pxe_env_mock, mode='deploy'):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
kernel_label = '%s_kernel' % mode
|
||||||
|
ramdisk_label = '%s_ramdisk' % mode
|
||||||
|
image_info = {kernel_label: ['', '/path/to/' + kernel_label],
|
||||||
|
ramdisk_label: ['', '/path/to/' + ramdisk_label]}
|
||||||
|
get_image_info_mock.return_value = image_info
|
||||||
|
task.driver.boot.clean_up_ramdisk(task)
|
||||||
|
clean_up_pxe_env_mock.assert_called_once_with(task, image_info)
|
||||||
|
get_image_info_mock.assert_called_once_with(task.node, mode=mode)
|
||||||
|
|
||||||
|
def test_clean_up_ramdisk(self):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self._test_clean_up_ramdisk()
|
||||||
|
|
||||||
|
def test_clean_up_ramdisk_rescue(self):
|
||||||
|
self.node.provision_state = states.RESCUING
|
||||||
|
self.node.save()
|
||||||
|
self._test_clean_up_ramdisk(mode='rescue')
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
|
||||||
|
@mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_cache_ramdisk_kernel', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||||
|
def test_prepare_instance_netboot(
|
||||||
|
self, get_image_info_mock, cache_mock,
|
||||||
|
dhcp_factory_mock, switch_pxe_config_mock,
|
||||||
|
set_boot_device_mock):
|
||||||
|
provider_mock = mock.MagicMock()
|
||||||
|
dhcp_factory_mock.return_value = provider_mock
|
||||||
|
image_info = {'kernel': ('', '/path/to/kernel'),
|
||||||
|
'ramdisk': ('', '/path/to/ramdisk')}
|
||||||
|
get_image_info_mock.return_value = image_info
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||||
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
|
task.node.uuid)
|
||||||
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
|
task.node.driver_internal_info['root_uuid_or_disk_id'] = (
|
||||||
|
"30212642-09d3-467f-8e09-21685826ab50")
|
||||||
|
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||||
|
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
|
get_image_info_mock.assert_called_once_with(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
cache_mock.assert_called_once_with(task, image_info)
|
||||||
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
|
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||||
|
'bios', False, False, False, False, ipxe_enabled=True)
|
||||||
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
|
boot_devices.PXE,
|
||||||
|
persistent=True)
|
||||||
|
|
||||||
|
@mock.patch('os.path.isfile', return_value=False)
|
||||||
|
@mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True)
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
|
||||||
|
@mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_cache_ramdisk_kernel', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||||
|
def test_prepare_instance_netboot_active(
|
||||||
|
self, get_image_info_mock, cache_mock,
|
||||||
|
dhcp_factory_mock, switch_pxe_config_mock,
|
||||||
|
set_boot_device_mock, create_pxe_config_mock, isfile_mock):
|
||||||
|
provider_mock = mock.MagicMock()
|
||||||
|
dhcp_factory_mock.return_value = provider_mock
|
||||||
|
image_info = {'kernel': ('', '/path/to/kernel'),
|
||||||
|
'ramdisk': ('', '/path/to/ramdisk')}
|
||||||
|
get_image_info_mock.return_value = image_info
|
||||||
|
self.node.provision_state = states.ACTIVE
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||||
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
|
task.node.uuid)
|
||||||
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
|
task.node.driver_internal_info['root_uuid_or_disk_id'] = (
|
||||||
|
"30212642-09d3-467f-8e09-21685826ab50")
|
||||||
|
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||||
|
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
|
get_image_info_mock.assert_called_once_with(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
cache_mock.assert_called_once_with(task, image_info)
|
||||||
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
|
create_pxe_config_mock.assert_called_once_with(
|
||||||
|
task, mock.ANY, CONF.pxe.pxe_config_template,
|
||||||
|
ipxe_enabled=True)
|
||||||
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
|
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||||
|
'bios', False, False, False, False, ipxe_enabled=True)
|
||||||
|
self.assertFalse(set_boot_device_mock.called)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
|
||||||
|
@mock.patch.object(dhcp_factory, 'DHCPFactory')
|
||||||
|
@mock.patch.object(pxe, '_cache_ramdisk_kernel', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||||
|
def test_prepare_instance_netboot_missing_root_uuid(
|
||||||
|
self, get_image_info_mock, cache_mock,
|
||||||
|
dhcp_factory_mock, switch_pxe_config_mock,
|
||||||
|
set_boot_device_mock):
|
||||||
|
provider_mock = mock.MagicMock()
|
||||||
|
dhcp_factory_mock.return_value = provider_mock
|
||||||
|
image_info = {'kernel': ('', '/path/to/kernel'),
|
||||||
|
'ramdisk': ('', '/path/to/ramdisk')}
|
||||||
|
get_image_info_mock.return_value = image_info
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||||
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
|
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||||
|
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
|
get_image_info_mock.assert_called_once_with(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
cache_mock.assert_called_once_with(task, image_info)
|
||||||
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
|
self.assertFalse(switch_pxe_config_mock.called)
|
||||||
|
self.assertFalse(set_boot_device_mock.called)
|
||||||
|
|
||||||
|
# NOTE(TheJulia): The log mock below is attached to the iPXE interface
|
||||||
|
# which directly logs the warning that is being checked for.
|
||||||
|
@mock.patch.object(ipxe.LOG, 'warning', autospec=True)
|
||||||
|
@mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True)
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(dhcp_factory, 'DHCPFactory')
|
||||||
|
@mock.patch.object(pxe, '_cache_ramdisk_kernel', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||||
|
def test_prepare_instance_whole_disk_image_missing_root_uuid(
|
||||||
|
self, get_image_info_mock, cache_mock,
|
||||||
|
dhcp_factory_mock, set_boot_device_mock,
|
||||||
|
clean_up_pxe_mock, log_mock):
|
||||||
|
provider_mock = mock.MagicMock()
|
||||||
|
dhcp_factory_mock.return_value = provider_mock
|
||||||
|
get_image_info_mock.return_value = {}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||||
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
|
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
get_image_info_mock.assert_called_once_with(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
cache_mock.assert_called_once_with(task, {})
|
||||||
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
|
self.assertTrue(log_mock.called)
|
||||||
|
clean_up_pxe_mock.assert_called_once_with(task)
|
||||||
|
set_boot_device_mock.assert_called_once_with(
|
||||||
|
task, boot_devices.DISK, persistent=True)
|
||||||
|
|
||||||
|
@mock.patch('os.path.isfile', lambda filename: False)
|
||||||
|
@mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True)
|
||||||
|
@mock.patch.object(deploy_utils, 'is_iscsi_boot', lambda task: True)
|
||||||
|
@mock.patch.object(noop_storage.NoopStorage, 'should_write_image',
|
||||||
|
lambda task: False)
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
|
||||||
|
@mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_cache_ramdisk_kernel', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||||
|
def test_prepare_instance_netboot_iscsi(
|
||||||
|
self, get_image_info_mock, cache_mock,
|
||||||
|
dhcp_factory_mock, switch_pxe_config_mock,
|
||||||
|
set_boot_device_mock, create_pxe_config_mock):
|
||||||
|
http_url = 'http://192.1.2.3:1234'
|
||||||
|
self.config(ipxe_enabled=False, group='pxe')
|
||||||
|
self.config(http_url=http_url, group='deploy')
|
||||||
|
provider_mock = mock.MagicMock()
|
||||||
|
dhcp_factory_mock.return_value = provider_mock
|
||||||
|
vol_id = uuidutils.generate_uuid()
|
||||||
|
obj_utils.create_test_volume_target(
|
||||||
|
self.context, node_id=self.node.id, volume_type='iscsi',
|
||||||
|
boot_index=0, volume_id='1234', uuid=vol_id,
|
||||||
|
properties={'target_lun': 0,
|
||||||
|
'target_portal': 'fake_host:3260',
|
||||||
|
'target_iqn': 'fake_iqn',
|
||||||
|
'auth_username': 'fake_username',
|
||||||
|
'auth_password': 'fake_password'})
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node.driver_internal_info = {
|
||||||
|
'boot_from_volume': vol_id}
|
||||||
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||||
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
|
task.node.uuid)
|
||||||
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
self.assertFalse(get_image_info_mock.called)
|
||||||
|
self.assertFalse(cache_mock.called)
|
||||||
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
|
create_pxe_config_mock.assert_called_once_with(
|
||||||
|
task, mock.ANY, CONF.pxe.pxe_config_template,
|
||||||
|
ipxe_enabled=True)
|
||||||
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
|
pxe_config_path, None, boot_modes.LEGACY_BIOS, False,
|
||||||
|
ipxe_enabled=True, iscsi_boot=True, ramdisk_boot=False)
|
||||||
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
|
boot_devices.PXE,
|
||||||
|
persistent=True)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True)
|
||||||
|
def test_prepare_instance_localboot(self, clean_up_pxe_config_mock,
|
||||||
|
set_boot_device_mock):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
instance_info = task.node.instance_info
|
||||||
|
instance_info['capabilities'] = {'boot_option': 'local'}
|
||||||
|
task.node.instance_info = instance_info
|
||||||
|
task.node.save()
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
clean_up_pxe_config_mock.assert_called_once_with(task)
|
||||||
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
|
boot_devices.DISK,
|
||||||
|
persistent=True)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True)
|
||||||
|
def test_is_force_persistent_boot_device_enabled(
|
||||||
|
self, clean_up_pxe_config_mock, set_boot_device_mock):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
instance_info = task.node.instance_info
|
||||||
|
instance_info['capabilities'] = {'boot_option': 'local'}
|
||||||
|
task.node.instance_info = instance_info
|
||||||
|
task.node.save()
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
clean_up_pxe_config_mock.assert_called_once_with(task)
|
||||||
|
driver_info = task.node.driver_info
|
||||||
|
driver_info['force_persistent _boot_device'] = True
|
||||||
|
task.node.driver_info = driver_info
|
||||||
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
|
boot_devices.DISK,
|
||||||
|
persistent=True)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True)
|
||||||
|
def test_prepare_instance_localboot_active(self, clean_up_pxe_config_mock,
|
||||||
|
set_boot_device_mock):
|
||||||
|
self.node.provision_state = states.ACTIVE
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
instance_info = task.node.instance_info
|
||||||
|
instance_info['capabilities'] = {'boot_option': 'local'}
|
||||||
|
task.node.instance_info = instance_info
|
||||||
|
task.node.save()
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
clean_up_pxe_config_mock.assert_called_once_with(task)
|
||||||
|
self.assertFalse(set_boot_device_mock.called)
|
||||||
|
|
||||||
|
@mock.patch.object(pxe, '_clean_up_pxe_env', autospec=True)
|
||||||
|
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||||
|
def test_clean_up_instance(self, get_image_info_mock,
|
||||||
|
clean_up_pxe_env_mock):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
image_info = {'kernel': ['', '/path/to/kernel'],
|
||||||
|
'ramdisk': ['', '/path/to/ramdisk']}
|
||||||
|
get_image_info_mock.return_value = image_info
|
||||||
|
task.driver.boot.clean_up_instance(task)
|
||||||
|
clean_up_pxe_env_mock.assert_called_once_with(task, image_info)
|
||||||
|
get_image_info_mock.assert_called_once_with(
|
||||||
|
task, ipxe_enabled=True)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(ipxe.iPXEBoot, '__init__', lambda self: None)
|
||||||
|
class iPXEValidateRescueTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(iPXEValidateRescueTestCase, self).setUp()
|
||||||
|
for iface in drivers_base.ALL_INTERFACES:
|
||||||
|
impl = 'fake'
|
||||||
|
if iface == 'network':
|
||||||
|
impl = 'flat'
|
||||||
|
if iface == 'rescue':
|
||||||
|
impl = 'agent'
|
||||||
|
if iface == 'boot':
|
||||||
|
impl = 'ipxe'
|
||||||
|
config_kwarg = {'enabled_%s_interfaces' % iface: [impl],
|
||||||
|
'default_%s_interface' % iface: impl}
|
||||||
|
self.config(**config_kwarg)
|
||||||
|
self.config(enabled_hardware_types=['fake-hardware'])
|
||||||
|
driver_info = DRV_INFO_DICT
|
||||||
|
driver_info.update({'rescue_ramdisk': 'my_ramdisk',
|
||||||
|
'rescue_kernel': 'my_kernel'})
|
||||||
|
instance_info = INST_INFO_DICT
|
||||||
|
instance_info.update({'rescue_password': 'password'})
|
||||||
|
n = {
|
||||||
|
'driver': 'fake-hardware',
|
||||||
|
'instance_info': instance_info,
|
||||||
|
'driver_info': driver_info,
|
||||||
|
'driver_internal_info': DRV_INTERNAL_INFO_DICT,
|
||||||
|
}
|
||||||
|
self.node = obj_utils.create_test_node(self.context, **n)
|
||||||
|
|
||||||
|
def test_validate_rescue(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.boot.validate_rescue(task)
|
||||||
|
|
||||||
|
def test_validate_rescue_no_rescue_ramdisk(self):
|
||||||
|
driver_info = self.node.driver_info
|
||||||
|
del driver_info['rescue_ramdisk']
|
||||||
|
self.node.driver_info = driver_info
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
self.assertRaisesRegex(exception.MissingParameterValue,
|
||||||
|
'Missing.*rescue_ramdisk',
|
||||||
|
task.driver.boot.validate_rescue, task)
|
||||||
|
|
||||||
|
def test_validate_rescue_fails_no_rescue_kernel(self):
|
||||||
|
driver_info = self.node.driver_info
|
||||||
|
del driver_info['rescue_kernel']
|
||||||
|
self.node.driver_info = driver_info
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
self.assertRaisesRegex(exception.MissingParameterValue,
|
||||||
|
'Missing.*rescue_kernel',
|
||||||
|
task.driver.boot.validate_rescue, task)
|
@ -1016,8 +1016,7 @@ class CleanUpFullFlowTestCase(db_base.DbTestCase):
|
|||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
task.driver.deploy.clean_up(task)
|
task.driver.deploy.clean_up(task)
|
||||||
mock_get_instance_image_info.assert_called_with(task.node,
|
mock_get_instance_image_info.assert_called_with(task)
|
||||||
task.context)
|
|
||||||
mock_get_deploy_image_info.assert_called_with(task.node,
|
mock_get_deploy_image_info.assert_called_with(task.node,
|
||||||
mode='deploy')
|
mode='deploy')
|
||||||
set_dhcp_provider_mock.assert_called_once_with()
|
set_dhcp_provider_mock.assert_called_once_with()
|
||||||
|
@ -38,6 +38,7 @@ from ironic.conductor import utils as manager_utils
|
|||||||
from ironic.drivers import base as drivers_base
|
from ironic.drivers import base as drivers_base
|
||||||
from ironic.drivers.modules import agent_base_vendor
|
from ironic.drivers.modules import agent_base_vendor
|
||||||
from ironic.drivers.modules import deploy_utils
|
from ironic.drivers.modules import deploy_utils
|
||||||
|
from ironic.drivers.modules import ipxe
|
||||||
from ironic.drivers.modules import pxe
|
from ironic.drivers.modules import pxe
|
||||||
from ironic.drivers.modules.storage import noop as noop_storage
|
from ironic.drivers.modules.storage import noop as noop_storage
|
||||||
from ironic.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
@ -51,6 +52,9 @@ DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
|
|||||||
DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
|
DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(TheJulia): This will need to be split until pxe interface code is
|
||||||
|
# refactored and cleaned up.
|
||||||
|
@mock.patch.object(ipxe.iPXEBoot, '__init__', lambda self: None)
|
||||||
@mock.patch.object(pxe.PXEBoot, '__init__', lambda self: None)
|
@mock.patch.object(pxe.PXEBoot, '__init__', lambda self: None)
|
||||||
class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
@ -146,20 +150,22 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
|||||||
'kernel'))}
|
'kernel'))}
|
||||||
show_mock.return_value = properties
|
show_mock.return_value = properties
|
||||||
self.context.auth_token = 'fake'
|
self.context.auth_token = 'fake'
|
||||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
show_mock.assert_called_once_with(mock.ANY, 'glance://image_uuid',
|
shared=True) as task:
|
||||||
method='get')
|
image_info = pxe._get_instance_image_info(task)
|
||||||
self.assertEqual(expected_info, image_info)
|
show_mock.assert_called_once_with(mock.ANY, 'glance://image_uuid',
|
||||||
|
method='get')
|
||||||
|
self.assertEqual(expected_info, image_info)
|
||||||
|
|
||||||
# test with saved info
|
# test with saved info
|
||||||
show_mock.reset_mock()
|
show_mock.reset_mock()
|
||||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
image_info = pxe._get_instance_image_info(task)
|
||||||
self.assertEqual(expected_info, image_info)
|
self.assertEqual(expected_info, image_info)
|
||||||
self.assertFalse(show_mock.called)
|
self.assertFalse(show_mock.called)
|
||||||
self.assertEqual('instance_kernel_uuid',
|
self.assertEqual('instance_kernel_uuid',
|
||||||
self.node.instance_info['kernel'])
|
task.node.instance_info['kernel'])
|
||||||
self.assertEqual('instance_ramdisk_uuid',
|
self.assertEqual('instance_ramdisk_uuid',
|
||||||
self.node.instance_info['ramdisk'])
|
task.node.instance_info['ramdisk'])
|
||||||
|
|
||||||
def test__get_instance_image_info(self):
|
def test__get_instance_image_info(self):
|
||||||
# Tests when 'is_whole_disk_image' exists in driver_internal_info
|
# Tests when 'is_whole_disk_image' exists in driver_internal_info
|
||||||
@ -177,17 +183,21 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
|||||||
def test__get_instance_image_info_localboot(self, boot_opt_mock):
|
def test__get_instance_image_info_localboot(self, boot_opt_mock):
|
||||||
self.node.driver_internal_info['is_whole_disk_image'] = False
|
self.node.driver_internal_info['is_whole_disk_image'] = False
|
||||||
self.node.save()
|
self.node.save()
|
||||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
self.assertEqual({}, image_info)
|
shared=True) as task:
|
||||||
boot_opt_mock.assert_called_once_with(self.node)
|
image_info = pxe._get_instance_image_info(task)
|
||||||
|
self.assertEqual({}, image_info)
|
||||||
|
boot_opt_mock.assert_called_once_with(task.node)
|
||||||
|
|
||||||
@mock.patch.object(base_image_service.BaseImageService, '_show',
|
@mock.patch.object(base_image_service.BaseImageService, '_show',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test__get_instance_image_info_whole_disk_image(self, show_mock):
|
def test__get_instance_image_info_whole_disk_image(self, show_mock):
|
||||||
properties = {'properties': None}
|
properties = {'properties': None}
|
||||||
show_mock.return_value = properties
|
show_mock.return_value = properties
|
||||||
self.node.driver_internal_info['is_whole_disk_image'] = True
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
shared=True) as task:
|
||||||
|
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||||
|
image_info = pxe._get_instance_image_info(task)
|
||||||
self.assertEqual({}, image_info)
|
self.assertEqual({}, image_info)
|
||||||
|
|
||||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||||
@ -442,7 +452,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
options = pxe._build_pxe_config_options(task, image_info)
|
options = pxe._build_pxe_config_options(
|
||||||
|
task, image_info, ipxe_enabled=True)
|
||||||
self.assertEqual(expected_options, options)
|
self.assertEqual(expected_options, options)
|
||||||
|
|
||||||
def test__build_pxe_config_options_ipxe(self):
|
def test__build_pxe_config_options_ipxe(self):
|
||||||
@ -637,10 +648,11 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
|||||||
'deploy_kernel')
|
'deploy_kernel')
|
||||||
image_info = {'deploy_kernel': ('deploy_kernel', image_path)}
|
image_info = {'deploy_kernel': ('deploy_kernel', image_path)}
|
||||||
fileutils.ensure_tree(CONF.pxe.tftp_master_path)
|
fileutils.ensure_tree(CONF.pxe.tftp_master_path)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
pxe._cache_ramdisk_kernel(task, image_info)
|
||||||
|
|
||||||
pxe._cache_ramdisk_kernel(None, self.node, image_info)
|
mock_fetch_image.assert_called_once_with(self.context,
|
||||||
|
|
||||||
mock_fetch_image.assert_called_once_with(None,
|
|
||||||
mock.ANY,
|
mock.ANY,
|
||||||
[('deploy_kernel',
|
[('deploy_kernel',
|
||||||
image_path)],
|
image_path)],
|
||||||
@ -653,8 +665,9 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
|||||||
self.config(ipxe_enabled=False, group='pxe')
|
self.config(ipxe_enabled=False, group='pxe')
|
||||||
fake_pxe_info = {'foo': 'bar'}
|
fake_pxe_info = {'foo': 'bar'}
|
||||||
expected_path = os.path.join(CONF.pxe.tftp_root, self.node.uuid)
|
expected_path = os.path.join(CONF.pxe.tftp_root, self.node.uuid)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
pxe._cache_ramdisk_kernel(self.context, self.node, fake_pxe_info)
|
shared=True) as task:
|
||||||
|
pxe._cache_ramdisk_kernel(task, fake_pxe_info)
|
||||||
mock_ensure_tree.assert_called_with(expected_path)
|
mock_ensure_tree.assert_called_with(expected_path)
|
||||||
mock_fetch_image.assert_called_once_with(
|
mock_fetch_image.assert_called_once_with(
|
||||||
self.context, mock.ANY, list(fake_pxe_info.values()), True)
|
self.context, mock.ANY, list(fake_pxe_info.values()), True)
|
||||||
@ -668,8 +681,9 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
|||||||
fake_pxe_info = {'foo': 'bar'}
|
fake_pxe_info = {'foo': 'bar'}
|
||||||
expected_path = os.path.join(CONF.deploy.http_root,
|
expected_path = os.path.join(CONF.deploy.http_root,
|
||||||
self.node.uuid)
|
self.node.uuid)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
pxe._cache_ramdisk_kernel(self.context, self.node, fake_pxe_info)
|
shared=True) as task:
|
||||||
|
pxe._cache_ramdisk_kernel(task, fake_pxe_info)
|
||||||
mock_ensure_tree.assert_called_with(expected_path)
|
mock_ensure_tree.assert_called_with(expected_path)
|
||||||
mock_fetch_image.assert_called_once_with(self.context, mock.ANY,
|
mock_fetch_image.assert_called_once_with(self.context, mock.ANY,
|
||||||
list(fake_pxe_info.values()),
|
list(fake_pxe_info.values()),
|
||||||
@ -748,6 +762,9 @@ class CleanUpPxeEnvTestCase(db_base.DbTestCase):
|
|||||||
mock_cache.return_value.clean_up.assert_called_once_with()
|
mock_cache.return_value.clean_up.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(TheJulia): cover the ipxe interface's init as well until
|
||||||
|
# the testing is separated apart.
|
||||||
|
@mock.patch.object(ipxe.iPXEBoot, '__init__', lambda self: None)
|
||||||
@mock.patch.object(pxe.PXEBoot, '__init__', lambda self: None)
|
@mock.patch.object(pxe.PXEBoot, '__init__', lambda self: None)
|
||||||
class PXEBootTestCase(db_base.DbTestCase):
|
class PXEBootTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
@ -765,7 +782,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
instance_info = INST_INFO_DICT
|
instance_info = INST_INFO_DICT
|
||||||
instance_info['deploy_key'] = 'fake-56789'
|
instance_info['deploy_key'] = 'fake-56789'
|
||||||
|
|
||||||
self.config(enabled_boot_interfaces=[self.boot_interface, 'fake'])
|
self.config(enabled_boot_interfaces=[self.boot_interface,
|
||||||
|
'ipxe', 'fake'])
|
||||||
self.node = obj_utils.create_test_node(
|
self.node = obj_utils.create_test_node(
|
||||||
self.context,
|
self.context,
|
||||||
driver=self.driver,
|
driver=self.driver,
|
||||||
@ -963,31 +981,31 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.assertFalse(mock_cache_r_k.called)
|
self.assertFalse(mock_cache_r_k.called)
|
||||||
else:
|
else:
|
||||||
mock_cache_r_k.assert_called_once_with(
|
mock_cache_r_k.assert_called_once_with(
|
||||||
self.context, task.node,
|
task,
|
||||||
{'kernel': 'b'})
|
{'kernel': 'b'})
|
||||||
mock_instance_img_info.assert_called_once_with(task.node,
|
mock_instance_img_info.assert_called_once_with(task)
|
||||||
self.context)
|
|
||||||
elif not cleaning and mode == 'deploy':
|
elif not cleaning and mode == 'deploy':
|
||||||
mock_cache_r_k.assert_called_once_with(
|
mock_cache_r_k.assert_called_once_with(
|
||||||
self.context, task.node,
|
task,
|
||||||
{'deploy_kernel': 'a', 'deploy_ramdisk': 'r',
|
{'deploy_kernel': 'a', 'deploy_ramdisk': 'r',
|
||||||
'kernel': 'b'})
|
'kernel': 'b'})
|
||||||
mock_instance_img_info.assert_called_once_with(task.node,
|
mock_instance_img_info.assert_called_once_with(task)
|
||||||
self.context)
|
|
||||||
elif mode == 'deploy':
|
elif mode == 'deploy':
|
||||||
mock_cache_r_k.assert_called_once_with(
|
mock_cache_r_k.assert_called_once_with(
|
||||||
self.context, task.node,
|
task,
|
||||||
{'deploy_kernel': 'a', 'deploy_ramdisk': 'r'})
|
{'deploy_kernel': 'a', 'deploy_ramdisk': 'r'})
|
||||||
elif mode == 'rescue':
|
elif mode == 'rescue':
|
||||||
mock_cache_r_k.assert_called_once_with(
|
mock_cache_r_k.assert_called_once_with(
|
||||||
self.context, task.node,
|
task,
|
||||||
{'rescue_kernel': 'a', 'rescue_ramdisk': 'r'})
|
{'rescue_kernel': 'a', 'rescue_ramdisk': 'r'})
|
||||||
if uefi:
|
if uefi:
|
||||||
mock_pxe_config.assert_called_once_with(
|
mock_pxe_config.assert_called_once_with(
|
||||||
task, {'foo': 'bar'}, CONF.pxe.uefi_pxe_config_template)
|
task, {'foo': 'bar'}, CONF.pxe.uefi_pxe_config_template,
|
||||||
|
ipxe_enabled=CONF.pxe.ipxe_enabled)
|
||||||
else:
|
else:
|
||||||
mock_pxe_config.assert_called_once_with(
|
mock_pxe_config.assert_called_once_with(
|
||||||
task, {'foo': 'bar'}, CONF.pxe.pxe_config_template)
|
task, {'foo': 'bar'}, CONF.pxe.pxe_config_template,
|
||||||
|
ipxe_enabled=CONF.pxe.ipxe_enabled)
|
||||||
|
|
||||||
def test_prepare_ramdisk(self):
|
def test_prepare_ramdisk(self):
|
||||||
self.node.provision_state = states.DEPLOYING
|
self.node.provision_state = states.DEPLOYING
|
||||||
@ -1248,13 +1266,12 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
get_image_info_mock.assert_called_once_with(
|
get_image_info_mock.assert_called_once_with(
|
||||||
task.node, task.context)
|
task)
|
||||||
cache_mock.assert_called_once_with(
|
cache_mock.assert_called_once_with(task, image_info)
|
||||||
task.context, task.node, image_info)
|
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
switch_pxe_config_mock.assert_called_once_with(
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||||
'bios', False, False, False, False)
|
'bios', False, False, False, False, ipxe_enabled=False)
|
||||||
set_boot_device_mock.assert_called_once_with(task,
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
boot_devices.PXE,
|
boot_devices.PXE,
|
||||||
persistent=True)
|
persistent=True)
|
||||||
@ -1289,15 +1306,16 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
get_image_info_mock.assert_called_once_with(
|
get_image_info_mock.assert_called_once_with(
|
||||||
task.node, task.context)
|
task)
|
||||||
cache_mock.assert_called_once_with(
|
cache_mock.assert_called_once_with(
|
||||||
task.context, task.node, image_info)
|
task, image_info)
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
create_pxe_config_mock.assert_called_once_with(
|
create_pxe_config_mock.assert_called_once_with(
|
||||||
task, mock.ANY, CONF.pxe.pxe_config_template)
|
task, mock.ANY, CONF.pxe.pxe_config_template,
|
||||||
|
ipxe_enabled=False)
|
||||||
switch_pxe_config_mock.assert_called_once_with(
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||||
'bios', False, False, False, False)
|
'bios', False, False, False, False, ipxe_enabled=False)
|
||||||
self.assertFalse(set_boot_device_mock.called)
|
self.assertFalse(set_boot_device_mock.called)
|
||||||
|
|
||||||
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
@ -1321,10 +1339,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
get_image_info_mock.assert_called_once_with(
|
get_image_info_mock.assert_called_once_with(task)
|
||||||
task.node, task.context)
|
cache_mock.assert_called_once_with(task, image_info)
|
||||||
cache_mock.assert_called_once_with(
|
|
||||||
task.context, task.node, image_info)
|
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
self.assertFalse(switch_pxe_config_mock.called)
|
self.assertFalse(switch_pxe_config_mock.called)
|
||||||
self.assertFalse(set_boot_device_mock.called)
|
self.assertFalse(set_boot_device_mock.called)
|
||||||
@ -1347,10 +1363,9 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
task.node.properties['capabilities'] = 'boot_mode:bios'
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
get_image_info_mock.assert_called_once_with(
|
get_image_info_mock.assert_called_once_with(task)
|
||||||
task.node, task.context)
|
|
||||||
cache_mock.assert_called_once_with(
|
cache_mock.assert_called_once_with(
|
||||||
task.context, task.node, {})
|
task, {})
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
self.assertTrue(log_mock.called)
|
self.assertTrue(log_mock.called)
|
||||||
clean_up_pxe_mock.assert_called_once_with(task)
|
clean_up_pxe_mock.assert_called_once_with(task)
|
||||||
@ -1397,10 +1412,11 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.assertFalse(cache_mock.called)
|
self.assertFalse(cache_mock.called)
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
create_pxe_config_mock.assert_called_once_with(
|
create_pxe_config_mock.assert_called_once_with(
|
||||||
task, mock.ANY, CONF.pxe.pxe_config_template)
|
task, mock.ANY, CONF.pxe.pxe_config_template,
|
||||||
|
ipxe_enabled=True)
|
||||||
switch_pxe_config_mock.assert_called_once_with(
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
pxe_config_path, None, boot_modes.LEGACY_BIOS, False,
|
pxe_config_path, None, boot_modes.LEGACY_BIOS, False,
|
||||||
iscsi_boot=True, ramdisk_boot=False)
|
ipxe_enabled=True, ramdisk_boot=False, iscsi_boot=True)
|
||||||
set_boot_device_mock.assert_called_once_with(task,
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
boot_devices.PXE,
|
boot_devices.PXE,
|
||||||
persistent=True)
|
persistent=True)
|
||||||
@ -1481,19 +1497,19 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
get_image_info_mock.assert_called_once_with(
|
get_image_info_mock.assert_called_once_with(task)
|
||||||
task.node, task.context)
|
cache_mock.assert_called_once_with(task, image_info)
|
||||||
cache_mock.assert_called_once_with(
|
|
||||||
task.context, task.node, image_info)
|
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
if config_file_exits:
|
if config_file_exits:
|
||||||
self.assertFalse(create_pxe_config_mock.called)
|
self.assertFalse(create_pxe_config_mock.called)
|
||||||
else:
|
else:
|
||||||
create_pxe_config_mock.assert_called_once_with(
|
create_pxe_config_mock.assert_called_once_with(
|
||||||
task, mock.ANY, CONF.pxe.pxe_config_template)
|
task, mock.ANY, CONF.pxe.pxe_config_template,
|
||||||
|
ipxe_enabled=False)
|
||||||
switch_pxe_config_mock.assert_called_once_with(
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
pxe_config_path, None,
|
pxe_config_path, None,
|
||||||
'bios', False, iscsi_boot=False, ramdisk_boot=True)
|
'bios', False, ipxe_enabled=False, iscsi_boot=False,
|
||||||
|
ramdisk_boot=True)
|
||||||
set_boot_device_mock.assert_called_once_with(task,
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
boot_devices.PXE,
|
boot_devices.PXE,
|
||||||
persistent=True)
|
persistent=True)
|
||||||
@ -1516,8 +1532,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
get_image_info_mock.return_value = image_info
|
get_image_info_mock.return_value = image_info
|
||||||
task.driver.boot.clean_up_instance(task)
|
task.driver.boot.clean_up_instance(task)
|
||||||
clean_up_pxe_env_mock.assert_called_once_with(task, image_info)
|
clean_up_pxe_env_mock.assert_called_once_with(task, image_info)
|
||||||
get_image_info_mock.assert_called_once_with(
|
get_image_info_mock.assert_called_once_with(task)
|
||||||
task.node, task.context)
|
|
||||||
|
|
||||||
|
|
||||||
class PXERamdiskDeployTestCase(db_base.DbTestCase):
|
class PXERamdiskDeployTestCase(db_base.DbTestCase):
|
||||||
@ -1576,14 +1591,14 @@ class PXERamdiskDeployTestCase(db_base.DbTestCase):
|
|||||||
task.driver.deploy.prepare(task)
|
task.driver.deploy.prepare(task)
|
||||||
task.driver.deploy.deploy(task)
|
task.driver.deploy.deploy(task)
|
||||||
|
|
||||||
get_image_info_mock.assert_called_once_with(
|
get_image_info_mock.assert_called_once_with(task)
|
||||||
task.node, task.context)
|
|
||||||
cache_mock.assert_called_once_with(
|
cache_mock.assert_called_once_with(
|
||||||
task.context, task.node, image_info)
|
task, image_info)
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
switch_pxe_config_mock.assert_called_once_with(
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
pxe_config_path, None,
|
pxe_config_path, None,
|
||||||
'bios', False, iscsi_boot=False, ramdisk_boot=True)
|
'bios', False, ipxe_enabled=False, iscsi_boot=False,
|
||||||
|
ramdisk_boot=True)
|
||||||
set_boot_device_mock.assert_called_once_with(task,
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
boot_devices.PXE,
|
boot_devices.PXE,
|
||||||
persistent=True)
|
persistent=True)
|
||||||
@ -1604,10 +1619,8 @@ class PXERamdiskDeployTestCase(db_base.DbTestCase):
|
|||||||
self.node.save()
|
self.node.save()
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
self.assertIsNone(task.driver.deploy.deploy(task))
|
self.assertIsNone(task.driver.deploy.deploy(task))
|
||||||
mock_image_info.assert_called_once_with(
|
mock_image_info.assert_called_once_with(task)
|
||||||
task.node, task.context)
|
mock_cache.assert_called_once_with(task, image_info)
|
||||||
mock_cache.assert_called_once_with(
|
|
||||||
task.context, task.node, image_info)
|
|
||||||
self.assertFalse(mock_warning.called)
|
self.assertFalse(mock_warning.called)
|
||||||
i_info['configdrive'] = 'meow'
|
i_info['configdrive'] = 'meow'
|
||||||
self.node.instance_info = i_info
|
self.node.instance_info = i_info
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds an ``ipxe`` boot interface which allows for instance level iPXE
|
||||||
|
enablement as opposed to conductor-wide enablement of iPXE.
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
Deployments utilizing iPXE should consider use of the ``ipxe``
|
||||||
|
boot interface as opposed to the ``pxe`` boot interface. iPXE
|
||||||
|
functionality in the ``pxe`` boot interface is deprecated and will
|
||||||
|
be removed during the U* development cycle.
|
||||||
|
deprecations:
|
||||||
|
- |
|
||||||
|
The ``[pxe]ipxe_enabled`` configuration option has been deprecated in
|
||||||
|
preference for the ``ipxe`` boot interface. The configuration option
|
||||||
|
will be removed during the U* development cycle.
|
||||||
|
- |
|
||||||
|
Support for iPXE in the ``pxe`` boot interface has been deprecated,
|
||||||
|
and will be removed during the U* development cycle. The ``ipxe``
|
||||||
|
boot interface should be used instead.
|
@ -62,6 +62,7 @@ ironic.hardware.interfaces.boot =
|
|||||||
fake = ironic.drivers.modules.fake:FakeBoot
|
fake = ironic.drivers.modules.fake:FakeBoot
|
||||||
ilo-pxe = ironic.drivers.modules.ilo.boot:IloPXEBoot
|
ilo-pxe = ironic.drivers.modules.ilo.boot:IloPXEBoot
|
||||||
ilo-virtual-media = ironic.drivers.modules.ilo.boot:IloVirtualMediaBoot
|
ilo-virtual-media = ironic.drivers.modules.ilo.boot:IloVirtualMediaBoot
|
||||||
|
ipxe = ironic.drivers.modules.ipxe:iPXEBoot
|
||||||
irmc-pxe = ironic.drivers.modules.irmc.boot:IRMCPXEBoot
|
irmc-pxe = ironic.drivers.modules.irmc.boot:IRMCPXEBoot
|
||||||
irmc-virtual-media = ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot
|
irmc-virtual-media = ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot
|
||||||
pxe = ironic.drivers.modules.pxe:PXEBoot
|
pxe = ironic.drivers.modules.pxe:PXEBoot
|
||||||
|
@ -227,6 +227,15 @@
|
|||||||
c-vol: True
|
c-vol: True
|
||||||
cinder: True
|
cinder: True
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: ironic-tempest-dsvm-ipxe-bfv
|
||||||
|
description: ironic-tempest-dsvm-ipxe-bfv
|
||||||
|
parent: ironic-tempest-dsvm-bfv
|
||||||
|
vars:
|
||||||
|
devstack_localrc:
|
||||||
|
IRONIC_ENABLED_BOOT_INTERFACES: ipxe,pxe,fake
|
||||||
|
IRONIC_DEFAULT_BOOT_INTERFACE: ipxe
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
name: ironic-tempest-dsvm-ironic-inspector
|
name: ironic-tempest-dsvm-ironic-inspector
|
||||||
description: ironic-tempest-dsvm-ironic-inspector
|
description: ironic-tempest-dsvm-ironic-inspector
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
- ironic-tempest-dsvm-functional-python3
|
- ironic-tempest-dsvm-functional-python3
|
||||||
- ironic-grenade-dsvm
|
- ironic-grenade-dsvm
|
||||||
- ironic-grenade-dsvm-multinode-multitenant
|
- ironic-grenade-dsvm-multinode-multitenant
|
||||||
- ironic-tempest-dsvm-bfv
|
|
||||||
- ironic-tempest-dsvm-ipa-partition-pxe_ipmitool-tinyipa-python3
|
- ironic-tempest-dsvm-ipa-partition-pxe_ipmitool-tinyipa-python3
|
||||||
- ironic-tempest-dsvm-ipa-partition-redfish-tinyipa
|
- ironic-tempest-dsvm-ipa-partition-redfish-tinyipa
|
||||||
- ironic-tempest-dsvm-ipa-partition-uefi-pxe_ipmitool-tinyipa
|
- ironic-tempest-dsvm-ipa-partition-uefi-pxe_ipmitool-tinyipa
|
||||||
@ -24,7 +23,14 @@
|
|||||||
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
||||||
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa-indirect
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa-indirect
|
||||||
- ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
- ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
||||||
|
- ironic-tempest-dsvm-ipxe-bfv
|
||||||
# Non-voting jobs
|
# Non-voting jobs
|
||||||
|
# NOTE(TheJulia): BFV default job moves to use the ipxe interface,
|
||||||
|
# this non-voting job is just a safety net as we continue to do
|
||||||
|
# refactoring/cleanup and can be removed later in the stein
|
||||||
|
# cycle or after.
|
||||||
|
- ironic-tempest-dsvm-bfv:
|
||||||
|
voting: false
|
||||||
- ironic-tempest-dsvm-ipa-wholedisk-bios-pxe_snmp-tinyipa:
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-pxe_snmp-tinyipa:
|
||||||
voting: false
|
voting: false
|
||||||
- ironic-tempest-dsvm-ironic-inspector:
|
- ironic-tempest-dsvm-ironic-inspector:
|
||||||
@ -39,7 +45,6 @@
|
|||||||
- ironic-tempest-dsvm-functional-python3
|
- ironic-tempest-dsvm-functional-python3
|
||||||
- ironic-grenade-dsvm
|
- ironic-grenade-dsvm
|
||||||
- ironic-grenade-dsvm-multinode-multitenant
|
- ironic-grenade-dsvm-multinode-multitenant
|
||||||
- ironic-tempest-dsvm-bfv
|
|
||||||
- ironic-tempest-dsvm-ipa-partition-pxe_ipmitool-tinyipa-python3
|
- ironic-tempest-dsvm-ipa-partition-pxe_ipmitool-tinyipa-python3
|
||||||
- ironic-tempest-dsvm-ipa-partition-redfish-tinyipa
|
- ironic-tempest-dsvm-ipa-partition-redfish-tinyipa
|
||||||
- ironic-tempest-dsvm-ipa-partition-uefi-pxe_ipmitool-tinyipa
|
- ironic-tempest-dsvm-ipa-partition-uefi-pxe_ipmitool-tinyipa
|
||||||
@ -47,3 +52,4 @@
|
|||||||
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
||||||
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa-indirect
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa-indirect
|
||||||
- ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
- ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
||||||
|
- ironic-tempest-dsvm-ipxe-bfv
|
||||||
|
Loading…
Reference in New Issue
Block a user