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_utils import excutils
|
||||
from oslo_utils import fileutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import dhcp_factory
|
||||
from ironic.common import exception
|
||||
@ -58,14 +59,22 @@ def get_root_dir():
|
||||
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.
|
||||
|
||||
: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()
|
||||
node_dir = os.path.join(root_dir, node_uuid)
|
||||
if ipxe_enabled:
|
||||
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)
|
||||
# NOTE: We should only change the permissions if the folder
|
||||
# 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)
|
||||
|
||||
|
||||
def _link_mac_pxe_configs(task):
|
||||
def _link_mac_pxe_configs(task, ipxe_enabled=False):
|
||||
"""Link each MAC address with the PXE configuration file.
|
||||
|
||||
: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):
|
||||
@ -91,26 +101,32 @@ def _link_mac_pxe_configs(task):
|
||||
pxe_config_file_path, os.path.dirname(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:
|
||||
client_id = port.extra.get('client-id')
|
||||
# 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
|
||||
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.
|
||||
|
||||
:param task: A TaskManager instance.
|
||||
:param hex_form: Boolean value indicating if the conf file name should be
|
||||
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: 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
|
||||
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')
|
||||
|
||||
|
||||
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.
|
||||
|
||||
: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 client_id: client_id indicate InfiniBand port.
|
||||
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.
|
||||
|
||||
"""
|
||||
mac_file_name = mac.replace(':', delimiter).lower()
|
||||
if not CONF.pxe.ipxe_enabled:
|
||||
if not ipxe_enabled:
|
||||
hw_type = '01-'
|
||||
if client_id:
|
||||
hw_type = '20-'
|
||||
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):
|
||||
@ -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.
|
||||
|
||||
: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'
|
||||
'rescue'. Defaults to 'deploy', indicating deploy paths will
|
||||
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
|
||||
rescue_kernel and rescue_ramdisk and whose values are the
|
||||
absolute paths to them.
|
||||
|
||||
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 = {}
|
||||
labels = KERNEL_RAMDISK_LABELS[mode]
|
||||
for label in labels:
|
||||
@ -199,17 +227,22 @@ def get_kernel_ramdisk_info(node_uuid, driver_info, mode='deploy'):
|
||||
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.
|
||||
|
||||
: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.
|
||||
|
||||
"""
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
if template is None:
|
||||
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)
|
||||
== 'uefi')
|
||||
|
||||
@ -269,10 +303,10 @@ def create_pxe_config(task, pxe_options, template=None):
|
||||
utils.write_to_file(pxe_config_file_path, pxe_config)
|
||||
|
||||
# Always write the mac addresses
|
||||
_link_mac_pxe_configs(task)
|
||||
if is_uefi_boot_mode and not CONF.pxe.ipxe_enabled:
|
||||
_link_mac_pxe_configs(task, ipxe_enabled=ipxe_enabled)
|
||||
if is_uefi_boot_mode and not ipxe_enabled:
|
||||
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
|
||||
# dhcp_provider interface is set to none. This will result
|
||||
# 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)
|
||||
== '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
|
||||
ip_addresses = api.get_ip_addresses(task)
|
||||
if not ip_addresses:
|
||||
@ -341,12 +377,17 @@ def clean_up_pxe_config(task):
|
||||
client_id = port.extra.get('client-id')
|
||||
# syslinux, ipxe, etc.
|
||||
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
|
||||
ironic_utils.unlink_without_raise(
|
||||
_get_pxe_grub_mac_path(port.address))
|
||||
utils.rmtree_without_raise(os.path.join(get_root_dir(),
|
||||
task.node.uuid))
|
||||
if ipxe_enabled:
|
||||
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):
|
||||
@ -358,7 +399,7 @@ def dhcp_options_for_instance(task):
|
||||
|
||||
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)
|
||||
ipxe_script_url = '/'.join([CONF.deploy.http_url, script_name])
|
||||
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
|
||||
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
|
||||
# import weirdness otherwise, specifically when the classes that use
|
||||
# the pxe interface as their parent.
|
||||
# iPXEBoot = importutils.import_class(
|
||||
# 'ironic.drivers.modules.ipxe.iPXEBoot')
|
||||
# return CONF.pxe.ipxe_enabled or isinstance(task.driver.boot,
|
||||
# iPXEBoot)
|
||||
return CONF.pxe.ipxe_enabled
|
||||
# TODO(TheJulia): We should remove this as soon as it is no longer
|
||||
# required to help us bridge the split of the interfaces and helper
|
||||
# methods.
|
||||
iPXEBoot = importutils.import_class(
|
||||
'ironic.drivers.modules.ipxe.iPXEBoot')
|
||||
return CONF.pxe.ipxe_enabled or isinstance(task.driver.boot,
|
||||
iPXEBoot)
|
||||
|
||||
|
||||
def parse_driver_info(node, mode='deploy'):
|
||||
@ -479,28 +520,33 @@ def parse_driver_info(node, mode='deploy'):
|
||||
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.
|
||||
|
||||
This method generates the paths for instance kernel and
|
||||
instance ramdisk. This method also updates the node, so caller should
|
||||
already have a non-shared lock on the node.
|
||||
|
||||
:param node: a node object
|
||||
:param ctx: context
|
||||
:param task: A TaskManager instance containing node and 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,
|
||||
ramdisk) and values are the absolute paths of them. If it's a whole
|
||||
disk image or node is configured for localboot,
|
||||
it returns an empty dictionary.
|
||||
"""
|
||||
ctx = task.context
|
||||
node = task.node
|
||||
image_info = {}
|
||||
# NOTE(pas-ha) do not report image kernel and ramdisk for
|
||||
# local boot or whole disk images so that they are not cached
|
||||
if (node.driver_internal_info.get('is_whole_disk_image')
|
||||
or deploy_utils.get_boot_option(node) == 'local'):
|
||||
return image_info
|
||||
|
||||
root_dir = get_root_dir()
|
||||
if ipxe_enabled:
|
||||
root_dir = get_ipxe_root_dir()
|
||||
else:
|
||||
root_dir = get_root_dir()
|
||||
i_info = node.instance_info
|
||||
labels = ('kernel', 'ramdisk')
|
||||
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}
|
||||
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
user image and skip adding deployment image kernel and ramdisk info
|
||||
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
|
||||
template.
|
||||
"""
|
||||
@ -639,7 +688,7 @@ def build_pxe_config_options(task, pxe_info, service=False):
|
||||
if service:
|
||||
pxe_options = {}
|
||||
elif (node.driver_internal_info.get('boot_from_volume')
|
||||
and is_ipxe_enabled(task)):
|
||||
and ipxe_enabled):
|
||||
pxe_options = get_volume_pxe_options(task)
|
||||
else:
|
||||
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,
|
||||
root_uuid_or_disk_id,
|
||||
ramdisk_boot=False):
|
||||
ramdisk_boot=False,
|
||||
ipxe_enabled=False):
|
||||
node = task.node
|
||||
pxe_config_path = get_pxe_config_file_path(node.uuid)
|
||||
# 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]
|
||||
and not os.path.isfile(pxe_config_path)):
|
||||
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)
|
||||
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')
|
||||
deploy_utils.switch_pxe_config(
|
||||
pxe_config_path, root_uuid_or_disk_id,
|
||||
boot_mode_utils.get_boot_mode_for_deploy(node),
|
||||
iwdi, deploy_utils.is_trusted_boot_requested(node),
|
||||
deploy_utils.is_iscsi_boot(task), ramdisk_boot)
|
||||
# TODO(TheJulia): Add with ipxe interface
|
||||
# ipxe_enabled=is_ipxe_enabled(task))
|
||||
deploy_utils.is_iscsi_boot(task), ramdisk_boot,
|
||||
ipxe_enabled=ipxe_enabled)
|
||||
|
||||
|
||||
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,
|
||||
iscsi_boot=False,
|
||||
ramdisk_boot=False):
|
||||
ramdisk_boot=False,
|
||||
ipxe_enabled=False):
|
||||
"""Prepares the config file for PXE boot
|
||||
|
||||
: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.
|
||||
:param iscsi_boot: if boot is from an iSCSI volume or not.
|
||||
: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
|
||||
"""
|
||||
|
||||
@ -791,13 +845,15 @@ def prepare_instance_pxe_config(task, image_info,
|
||||
node.uuid)
|
||||
if not os.path.isfile(pxe_config_path):
|
||||
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 = (
|
||||
deploy_utils.get_pxe_config_template(node))
|
||||
create_pxe_config(
|
||||
task, pxe_options, pxe_config_template)
|
||||
task, pxe_options, pxe_config_template,
|
||||
ipxe_enabled=ipxe_enabled)
|
||||
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,
|
||||
ipxe_enabled=is_ipxe_enabled(task))
|
||||
ipxe_enabled=ipxe_enabled)
|
||||
|
@ -108,7 +108,15 @@ opts = [
|
||||
'For example: aarch64:grubaa64.efi')),
|
||||
cfg.BoolOpt('ipxe_enabled',
|
||||
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',
|
||||
default=os.path.join(
|
||||
'$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 import fake
|
||||
from ironic.drivers.modules import inspector
|
||||
from ironic.drivers.modules import ipxe
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.drivers.modules.network import flat as flat_net
|
||||
from ironic.drivers.modules.network import neutron
|
||||
@ -41,7 +42,7 @@ class GenericHardware(hardware_type.AbstractHardwareType):
|
||||
@property
|
||||
def supported_boot_interfaces(self):
|
||||
"""List of supported boot interfaces."""
|
||||
return [pxe.PXEBoot]
|
||||
return [ipxe.iPXEBoot, pxe.PXEBoot]
|
||||
|
||||
@property
|
||||
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,
|
||||
trusted_boot=False, iscsi_boot=False,
|
||||
ramdisk_boot=False):
|
||||
ramdisk_boot=False, ipxe_enabled=False):
|
||||
if is_whole_disk_image:
|
||||
boot_disk_type = 'boot_whole_disk'
|
||||
elif trusted_boot:
|
||||
@ -299,11 +299,11 @@ def _replace_boot_line(path, boot_mode, is_whole_disk_image,
|
||||
else:
|
||||
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)=.*$'
|
||||
boot_line = '\\1=%s' % boot_disk_type
|
||||
else:
|
||||
pxe_cmd = 'goto' if CONF.pxe.ipxe_enabled else 'default'
|
||||
pxe_cmd = 'goto' if ipxe_enabled else 'default'
|
||||
pattern = '^%s .*$' % pxe_cmd
|
||||
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)
|
||||
|
||||
|
||||
# NOTE(TheJulia): This should likely be migrated to pxe_utils.
|
||||
def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
|
||||
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.
|
||||
|
||||
: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.
|
||||
: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 ipxe_enabled: A default False boolean value to tell the method
|
||||
if the caller is using iPXE.
|
||||
"""
|
||||
if not ramdisk_boot:
|
||||
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_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):
|
||||
|
@ -68,6 +68,10 @@ class FakePower(base.PowerInterface):
|
||||
class FakeBoot(base.BootInterface):
|
||||
"""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):
|
||||
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)
|
||||
|
||||
|
||||
def _cache_ramdisk_kernel(ctx, node, pxe_info):
|
||||
def _cache_ramdisk_kernel(task, pxe_info):
|
||||
"""Fetch the necessary kernels and ramdisks for the instance."""
|
||||
fileutils.ensure_tree(
|
||||
os.path.join(pxe_utils.get_root_dir(), node.uuid))
|
||||
ctx = task.context
|
||||
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",
|
||||
node.uuid)
|
||||
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):
|
||||
|
||||
capabilities = ['iscsi_volume_boot', 'ramdisk_boot']
|
||||
capabilities = ['iscsi_volume_boot', 'ramdisk_boot', 'ipxe_boot',
|
||||
'pxe_boot']
|
||||
|
||||
def __init__(self):
|
||||
# TODO(TheJulia): Once the pxe/ipxe interfaces split is complete,
|
||||
# this can be removed.
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
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.uuid)
|
||||
|
||||
# TODO(TheJulia): Once ipxe support is remove from the pxe
|
||||
# interface, this can be removed.
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
if (not CONF.deploy.http_url
|
||||
or not CONF.deploy.http_root):
|
||||
@ -242,7 +252,7 @@ class PXEBoot(base.BootInterface):
|
||||
# NODE: Try to validate and fetch instance images only
|
||||
# if we are in DEPLOYING state.
|
||||
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)
|
||||
|
||||
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_utils.create_pxe_config(task, pxe_options,
|
||||
pxe_config_template)
|
||||
pxe_config_template,
|
||||
ipxe_enabled=CONF.pxe.ipxe_enabled)
|
||||
persistent = strutils.bool_from_string(
|
||||
node.driver_info.get('force_persistent_boot_device',
|
||||
False))
|
||||
@ -265,7 +276,7 @@ class PXEBoot(base.BootInterface):
|
||||
pxe_info.pop(ramdisk_label, None)
|
||||
|
||||
if pxe_info:
|
||||
_cache_ramdisk_kernel(task.context, node, pxe_info)
|
||||
_cache_ramdisk_kernel(task, pxe_info)
|
||||
|
||||
@METRICS.timer('PXEBoot.clean_up_ramdisk')
|
||||
def clean_up_ramdisk(self, task):
|
||||
@ -294,37 +305,6 @@ class PXEBoot(base.BootInterface):
|
||||
else:
|
||||
_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')
|
||||
def prepare_instance(self, task):
|
||||
"""Prepares the boot of instance.
|
||||
@ -337,6 +317,7 @@ class PXEBoot(base.BootInterface):
|
||||
:param task: a task from TaskManager.
|
||||
:returns: None
|
||||
"""
|
||||
ipxe_enabled = CONF.pxe.ipxe_enabled
|
||||
boot_mode_utils.sync_boot_mode(task)
|
||||
|
||||
node = task.node
|
||||
@ -344,25 +325,24 @@ class PXEBoot(base.BootInterface):
|
||||
boot_device = None
|
||||
instance_image_info = {}
|
||||
if boot_option == "ramdisk":
|
||||
instance_image_info = _get_instance_image_info(
|
||||
task.node, task.context)
|
||||
_cache_ramdisk_kernel(task.context, task.node,
|
||||
instance_image_info = _get_instance_image_info(task)
|
||||
_cache_ramdisk_kernel(task,
|
||||
instance_image_info)
|
||||
|
||||
if deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk":
|
||||
self._prepare_instance_pxe_config(
|
||||
_prepare_instance_pxe_config(
|
||||
task, instance_image_info,
|
||||
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
|
||||
|
||||
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 = _get_instance_image_info(
|
||||
task.node, task.context)
|
||||
_cache_ramdisk_kernel(task.context, task.node,
|
||||
instance_image_info = _get_instance_image_info(task)
|
||||
_cache_ramdisk_kernel(task,
|
||||
instance_image_info)
|
||||
|
||||
# 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
|
||||
else:
|
||||
_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
|
||||
else:
|
||||
# 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
|
||||
|
||||
try:
|
||||
images_info = _get_instance_image_info(node, task.context)
|
||||
images_info = _get_instance_image_info(task)
|
||||
except exception.MissingParameterValue as e:
|
||||
LOG.warning('Could not get instance image info '
|
||||
'to clean up images for node %(node)s: %(err)s',
|
||||
|
@ -76,7 +76,12 @@ class CinderStorage(base.StorageInterface):
|
||||
iscsi_uuids_found = []
|
||||
wwpn_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:
|
||||
if (connector.type in VALID_ISCSI_TYPES
|
||||
@ -84,7 +89,8 @@ class CinderStorage(base.StorageInterface):
|
||||
iscsi_uuids_found.append(connector.uuid)
|
||||
if not ipxe_enabled:
|
||||
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.")
|
||||
self._fail_validation(task, msg)
|
||||
|
||||
|
@ -15,6 +15,7 @@ from oslo_log import log
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import pxe_utils as common_pxe_utils
|
||||
from ironic.drivers import base
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -35,10 +36,11 @@ class ExternalStorage(base.StorageInterface):
|
||||
raise exception(msg)
|
||||
|
||||
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 "
|
||||
"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)
|
||||
|
||||
def get_properties(self):
|
||||
|
@ -344,7 +344,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
]
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
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)
|
||||
create_link_mock.assert_has_calls(create_link_calls)
|
||||
@ -487,7 +487,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
{'pxe_options': self.pxe_options,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'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)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||
@ -519,7 +519,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
{'pxe_options': self.pxe_options,
|
||||
'ROOT': '(( ROOT ))',
|
||||
'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)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||
@ -558,8 +558,9 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
{'pxe_options': self.pxe_options,
|
||||
'ROOT': '(( ROOT ))',
|
||||
'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'})
|
||||
link_mac_pxe_configs_mock.assert_called_once_with(task)
|
||||
link_ip_configs_mock.assert_called_once_with(task, False)
|
||||
link_mac_pxe_configs_mock.assert_called_once_with(
|
||||
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)
|
||||
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:
|
||||
task.node.properties['capabilities'] = 'boot_mode:uefi'
|
||||
pxe_utils.create_pxe_config(task, self.ipxe_options,
|
||||
ipxe_template)
|
||||
ipxe_template, ipxe_enabled=True)
|
||||
|
||||
ensure_calls = [
|
||||
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,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'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,
|
||||
render_mock.return_value)
|
||||
|
||||
@ -672,11 +674,10 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
pxe_utils._get_pxe_mac_path(mac))
|
||||
|
||||
def test__get_pxe_mac_path_ipxe(self):
|
||||
self.config(ipxe_enabled=True, group='pxe')
|
||||
self.config(http_root='/httpboot', group='deploy')
|
||||
mac = '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):
|
||||
ipaddress = '10.10.0.1'
|
||||
|
@ -874,24 +874,24 @@ class SwitchPxeConfigTestCase(tests_base.TestCase):
|
||||
|
||||
def test_switch_ipxe_config_partition_image(self):
|
||||
boot_mode = 'bios'
|
||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
||||
fname = self._create_config(ipxe=True)
|
||||
utils.switch_pxe_config(fname,
|
||||
'12345678-1234-1234-1234-1234567890abcdef',
|
||||
boot_mode,
|
||||
False)
|
||||
False,
|
||||
ipxe_enabled=True)
|
||||
with open(fname, 'r') as f:
|
||||
pxeconf = f.read()
|
||||
self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
|
||||
|
||||
def test_switch_ipxe_config_whole_disk_image(self):
|
||||
boot_mode = 'bios'
|
||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
||||
fname = self._create_config(ipxe=True)
|
||||
utils.switch_pxe_config(fname,
|
||||
'0x12345678',
|
||||
boot_mode,
|
||||
True)
|
||||
True,
|
||||
ipxe_enabled=True)
|
||||
with open(fname, 'r') as f:
|
||||
pxeconf = f.read()
|
||||
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):
|
||||
boot_mode = 'uefi'
|
||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
||||
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
||||
utils.switch_pxe_config(fname,
|
||||
'12345678-1234-1234-1234-1234567890abcdef',
|
||||
boot_mode,
|
||||
False)
|
||||
False,
|
||||
ipxe_enabled=True)
|
||||
with open(fname, 'r') as f:
|
||||
pxeconf = f.read()
|
||||
self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
|
||||
|
||||
def test_switch_uefi_ipxe_config_whole_disk_image(self):
|
||||
boot_mode = 'uefi'
|
||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
||||
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
||||
utils.switch_pxe_config(fname,
|
||||
'0x12345678',
|
||||
boot_mode,
|
||||
True)
|
||||
True,
|
||||
ipxe_enabled=True)
|
||||
with open(fname, 'r') as f:
|
||||
pxeconf = f.read()
|
||||
self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
|
||||
|
||||
def test_switch_ipxe_iscsi_boot(self):
|
||||
boot_mode = 'iscsi'
|
||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
||||
fname = self._create_config(boot_mode=boot_mode, ipxe=True)
|
||||
utils.switch_pxe_config(fname,
|
||||
'0x12345678',
|
||||
boot_mode,
|
||||
False, False, True)
|
||||
False, False, True,
|
||||
ipxe_enabled=True)
|
||||
with open(fname, 'r') as f:
|
||||
pxeconf = f.read()
|
||||
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,
|
||||
shared=True) as task:
|
||||
task.driver.deploy.clean_up(task)
|
||||
mock_get_instance_image_info.assert_called_with(task.node,
|
||||
task.context)
|
||||
mock_get_instance_image_info.assert_called_with(task)
|
||||
mock_get_deploy_image_info.assert_called_with(task.node,
|
||||
mode='deploy')
|
||||
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.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
|
||||
@ -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()
|
||||
|
||||
|
||||
# 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)
|
||||
class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
@ -146,20 +150,22 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
'kernel'))}
|
||||
show_mock.return_value = properties
|
||||
self.context.auth_token = 'fake'
|
||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
||||
show_mock.assert_called_once_with(mock.ANY, 'glance://image_uuid',
|
||||
method='get')
|
||||
self.assertEqual(expected_info, image_info)
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
image_info = pxe._get_instance_image_info(task)
|
||||
show_mock.assert_called_once_with(mock.ANY, 'glance://image_uuid',
|
||||
method='get')
|
||||
self.assertEqual(expected_info, image_info)
|
||||
|
||||
# test with saved info
|
||||
show_mock.reset_mock()
|
||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
||||
self.assertEqual(expected_info, image_info)
|
||||
self.assertFalse(show_mock.called)
|
||||
self.assertEqual('instance_kernel_uuid',
|
||||
self.node.instance_info['kernel'])
|
||||
self.assertEqual('instance_ramdisk_uuid',
|
||||
self.node.instance_info['ramdisk'])
|
||||
# test with saved info
|
||||
show_mock.reset_mock()
|
||||
image_info = pxe._get_instance_image_info(task)
|
||||
self.assertEqual(expected_info, image_info)
|
||||
self.assertFalse(show_mock.called)
|
||||
self.assertEqual('instance_kernel_uuid',
|
||||
task.node.instance_info['kernel'])
|
||||
self.assertEqual('instance_ramdisk_uuid',
|
||||
task.node.instance_info['ramdisk'])
|
||||
|
||||
def test__get_instance_image_info(self):
|
||||
# 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):
|
||||
self.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.node.save()
|
||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
||||
self.assertEqual({}, image_info)
|
||||
boot_opt_mock.assert_called_once_with(self.node)
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
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',
|
||||
autospec=True)
|
||||
def test__get_instance_image_info_whole_disk_image(self, show_mock):
|
||||
properties = {'properties': None}
|
||||
show_mock.return_value = properties
|
||||
self.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
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)
|
||||
|
||||
@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,
|
||||
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)
|
||||
|
||||
def test__build_pxe_config_options_ipxe(self):
|
||||
@ -637,10 +648,11 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
'deploy_kernel')
|
||||
image_info = {'deploy_kernel': ('deploy_kernel', image_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(None,
|
||||
mock_fetch_image.assert_called_once_with(self.context,
|
||||
mock.ANY,
|
||||
[('deploy_kernel',
|
||||
image_path)],
|
||||
@ -653,8 +665,9 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
self.config(ipxe_enabled=False, group='pxe')
|
||||
fake_pxe_info = {'foo': 'bar'}
|
||||
expected_path = os.path.join(CONF.pxe.tftp_root, self.node.uuid)
|
||||
|
||||
pxe._cache_ramdisk_kernel(self.context, self.node, fake_pxe_info)
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
pxe._cache_ramdisk_kernel(task, fake_pxe_info)
|
||||
mock_ensure_tree.assert_called_with(expected_path)
|
||||
mock_fetch_image.assert_called_once_with(
|
||||
self.context, mock.ANY, list(fake_pxe_info.values()), True)
|
||||
@ -668,8 +681,9 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
fake_pxe_info = {'foo': 'bar'}
|
||||
expected_path = os.path.join(CONF.deploy.http_root,
|
||||
self.node.uuid)
|
||||
|
||||
pxe._cache_ramdisk_kernel(self.context, self.node, fake_pxe_info)
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
pxe._cache_ramdisk_kernel(task, fake_pxe_info)
|
||||
mock_ensure_tree.assert_called_with(expected_path)
|
||||
mock_fetch_image.assert_called_once_with(self.context, mock.ANY,
|
||||
list(fake_pxe_info.values()),
|
||||
@ -748,6 +762,9 @@ class CleanUpPxeEnvTestCase(db_base.DbTestCase):
|
||||
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)
|
||||
class PXEBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@ -765,7 +782,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
instance_info = INST_INFO_DICT
|
||||
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.context,
|
||||
driver=self.driver,
|
||||
@ -963,31 +981,31 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
self.assertFalse(mock_cache_r_k.called)
|
||||
else:
|
||||
mock_cache_r_k.assert_called_once_with(
|
||||
self.context, task.node,
|
||||
task,
|
||||
{'kernel': 'b'})
|
||||
mock_instance_img_info.assert_called_once_with(task.node,
|
||||
self.context)
|
||||
mock_instance_img_info.assert_called_once_with(task)
|
||||
elif not cleaning and mode == 'deploy':
|
||||
mock_cache_r_k.assert_called_once_with(
|
||||
self.context, task.node,
|
||||
task,
|
||||
{'deploy_kernel': 'a', 'deploy_ramdisk': 'r',
|
||||
'kernel': 'b'})
|
||||
mock_instance_img_info.assert_called_once_with(task.node,
|
||||
self.context)
|
||||
mock_instance_img_info.assert_called_once_with(task)
|
||||
elif mode == 'deploy':
|
||||
mock_cache_r_k.assert_called_once_with(
|
||||
self.context, task.node,
|
||||
task,
|
||||
{'deploy_kernel': 'a', 'deploy_ramdisk': 'r'})
|
||||
elif mode == 'rescue':
|
||||
mock_cache_r_k.assert_called_once_with(
|
||||
self.context, task.node,
|
||||
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)
|
||||
task, {'foo': 'bar'}, CONF.pxe.uefi_pxe_config_template,
|
||||
ipxe_enabled=CONF.pxe.ipxe_enabled)
|
||||
else:
|
||||
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):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
@ -1248,13 +1266,12 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
get_image_info_mock.assert_called_once_with(
|
||||
task.node, task.context)
|
||||
cache_mock.assert_called_once_with(
|
||||
task.context, task.node, image_info)
|
||||
task)
|
||||
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)
|
||||
'bios', False, False, False, False, ipxe_enabled=False)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -1289,15 +1306,16 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
get_image_info_mock.assert_called_once_with(
|
||||
task.node, task.context)
|
||||
task)
|
||||
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)
|
||||
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(
|
||||
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)
|
||||
|
||||
@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)
|
||||
|
||||
get_image_info_mock.assert_called_once_with(
|
||||
task.node, task.context)
|
||||
cache_mock.assert_called_once_with(
|
||||
task.context, task.node, image_info)
|
||||
get_image_info_mock.assert_called_once_with(task)
|
||||
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)
|
||||
@ -1347,10 +1363,9 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
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.node, task.context)
|
||||
get_image_info_mock.assert_called_once_with(task)
|
||||
cache_mock.assert_called_once_with(
|
||||
task.context, task.node, {})
|
||||
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)
|
||||
@ -1397,10 +1412,11 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
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)
|
||||
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,
|
||||
iscsi_boot=True, ramdisk_boot=False)
|
||||
ipxe_enabled=True, ramdisk_boot=False, iscsi_boot=True)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -1481,19 +1497,19 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
task.node.uuid)
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
get_image_info_mock.assert_called_once_with(
|
||||
task.node, task.context)
|
||||
cache_mock.assert_called_once_with(
|
||||
task.context, task.node, image_info)
|
||||
get_image_info_mock.assert_called_once_with(task)
|
||||
cache_mock.assert_called_once_with(task, image_info)
|
||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||
if config_file_exits:
|
||||
self.assertFalse(create_pxe_config_mock.called)
|
||||
else:
|
||||
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(
|
||||
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,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -1516,8 +1532,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
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.node, task.context)
|
||||
get_image_info_mock.assert_called_once_with(task)
|
||||
|
||||
|
||||
class PXERamdiskDeployTestCase(db_base.DbTestCase):
|
||||
@ -1576,14 +1591,14 @@ class PXERamdiskDeployTestCase(db_base.DbTestCase):
|
||||
task.driver.deploy.prepare(task)
|
||||
task.driver.deploy.deploy(task)
|
||||
|
||||
get_image_info_mock.assert_called_once_with(
|
||||
task.node, task.context)
|
||||
get_image_info_mock.assert_called_once_with(task)
|
||||
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)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
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,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -1604,10 +1619,8 @@ class PXERamdiskDeployTestCase(db_base.DbTestCase):
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertIsNone(task.driver.deploy.deploy(task))
|
||||
mock_image_info.assert_called_once_with(
|
||||
task.node, task.context)
|
||||
mock_cache.assert_called_once_with(
|
||||
task.context, task.node, image_info)
|
||||
mock_image_info.assert_called_once_with(task)
|
||||
mock_cache.assert_called_once_with(task, image_info)
|
||||
self.assertFalse(mock_warning.called)
|
||||
i_info['configdrive'] = 'meow'
|
||||
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
|
||||
ilo-pxe = ironic.drivers.modules.ilo.boot:IloPXEBoot
|
||||
ilo-virtual-media = ironic.drivers.modules.ilo.boot:IloVirtualMediaBoot
|
||||
ipxe = ironic.drivers.modules.ipxe:iPXEBoot
|
||||
irmc-pxe = ironic.drivers.modules.irmc.boot:IRMCPXEBoot
|
||||
irmc-virtual-media = ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot
|
||||
pxe = ironic.drivers.modules.pxe:PXEBoot
|
||||
|
@ -227,6 +227,15 @@
|
||||
c-vol: 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:
|
||||
name: ironic-tempest-dsvm-ironic-inspector
|
||||
description: ironic-tempest-dsvm-ironic-inspector
|
||||
|
@ -16,7 +16,6 @@
|
||||
- ironic-tempest-dsvm-functional-python3
|
||||
- ironic-grenade-dsvm
|
||||
- ironic-grenade-dsvm-multinode-multitenant
|
||||
- ironic-tempest-dsvm-bfv
|
||||
- ironic-tempest-dsvm-ipa-partition-pxe_ipmitool-tinyipa-python3
|
||||
- ironic-tempest-dsvm-ipa-partition-redfish-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-indirect
|
||||
- ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
||||
- ironic-tempest-dsvm-ipxe-bfv
|
||||
# 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:
|
||||
voting: false
|
||||
- ironic-tempest-dsvm-ironic-inspector:
|
||||
@ -39,7 +45,6 @@
|
||||
- ironic-tempest-dsvm-functional-python3
|
||||
- ironic-grenade-dsvm
|
||||
- ironic-grenade-dsvm-multinode-multitenant
|
||||
- ironic-tempest-dsvm-bfv
|
||||
- ironic-tempest-dsvm-ipa-partition-pxe_ipmitool-tinyipa-python3
|
||||
- ironic-tempest-dsvm-ipa-partition-redfish-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-indirect
|
||||
- ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
||||
- ironic-tempest-dsvm-ipxe-bfv
|
||||
|
Loading…
Reference in New Issue
Block a user