Merge "Refactoring: get rid of AgentDeployMixin"
This commit is contained in:
commit
9550eca761
ironic
releasenotes/notes
@ -16,17 +16,22 @@ from urllib import parse as urlparse
|
||||
|
||||
from ironic_lib import metrics_utils
|
||||
from oslo_log import log
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import units
|
||||
import tenacity
|
||||
|
||||
from ironic.common import async_steps
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.glance_service import service_utils
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import image_service
|
||||
from ironic.common import images
|
||||
from ironic.common import raid
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import deployments
|
||||
from ironic.conductor import steps as conductor_steps
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
@ -35,6 +40,7 @@ from ironic.drivers.modules import agent_base
|
||||
from ironic.drivers.modules import agent_client
|
||||
from ironic.drivers.modules import boot_mode_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers import utils as driver_utils
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -201,7 +207,9 @@ def validate_http_provisioning_configuration(node):
|
||||
deploy_utils.check_for_missing_params(params, error_msg)
|
||||
|
||||
|
||||
class CustomAgentDeploy(agent_base.AgentBaseMixin, agent_base.AgentDeployMixin,
|
||||
class CustomAgentDeploy(agent_base.AgentBaseMixin,
|
||||
agent_base.HeartbeatMixin,
|
||||
agent_base.AgentOobStepsMixin,
|
||||
base.DeployInterface):
|
||||
"""A deploy interface that relies on a custom agent to deploy.
|
||||
|
||||
@ -241,6 +249,55 @@ class CustomAgentDeploy(agent_base.AgentBaseMixin, agent_base.AgentDeployMixin,
|
||||
# Validate the root device hints
|
||||
deploy_utils.get_root_device_for_deploy(task.node)
|
||||
|
||||
@METRICS.timer('CustomAgentDeploy.get_deploy_steps')
|
||||
def get_deploy_steps(self, task):
|
||||
"""Get the list of deploy steps from the agent.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:raises InstanceDeployFailure: if the deploy steps are not yet
|
||||
available (cached), for example, when a node has just been
|
||||
enrolled and has not been deployed yet.
|
||||
:returns: A list of deploy step dictionaries
|
||||
"""
|
||||
steps = super().get_deploy_steps(task)[:]
|
||||
ib_steps = agent_base.get_steps(task, 'deploy', interface='deploy')
|
||||
# NOTE(dtantsur): we allow in-band steps to be shadowed by out-of-band
|
||||
# ones, see the docstring of execute_deploy_step for details.
|
||||
steps += [step for step in ib_steps
|
||||
# FIXME(dtantsur): nested loops are not too efficient
|
||||
if not conductor_steps.find_step(steps, step)]
|
||||
return steps
|
||||
|
||||
@METRICS.timer('CustomAgentDeploy.execute_deploy_step')
|
||||
def execute_deploy_step(self, task, step):
|
||||
"""Execute a deploy step.
|
||||
|
||||
We're trying to find a step among both out-of-band and in-band steps.
|
||||
In case of duplicates, out-of-band steps take priority. This property
|
||||
allows having an out-of-band deploy step that calls into
|
||||
a corresponding in-band step after some preparation (e.g. with
|
||||
additional input).
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:param step: a deploy step dictionary to execute
|
||||
:raises: InstanceDeployFailure if the agent does not return a command
|
||||
status
|
||||
:returns: states.DEPLOYWAIT to signify the step will be completed async
|
||||
"""
|
||||
agent_running = task.node.driver_internal_info.get(
|
||||
'agent_cached_deploy_steps')
|
||||
oob_steps = self.deploy_steps
|
||||
|
||||
if conductor_steps.find_step(oob_steps, step):
|
||||
return super().execute_deploy_step(task, step)
|
||||
elif not agent_running:
|
||||
raise exception.InstanceDeployFailure(
|
||||
_('Deploy step %(step)s has not been found. Available '
|
||||
'out-of-band steps: %(oob)s. Agent is not running.') %
|
||||
{'step': step, 'oob': oob_steps})
|
||||
else:
|
||||
return agent_base.execute_step(task, step, 'deploy')
|
||||
|
||||
@METRICS.timer('CustomAgentDeploy.deploy')
|
||||
@base.deploy_step(priority=100)
|
||||
@task_manager.require_exclusive_lock
|
||||
@ -274,7 +331,7 @@ class CustomAgentDeploy(agent_base.AgentBaseMixin, agent_base.AgentDeployMixin,
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
return states.DEPLOYWAIT
|
||||
|
||||
@METRICS.timer('CustomAgentDeployMixin.prepare_instance_boot')
|
||||
@METRICS.timer('CustomAgentDeploy.prepare_instance_boot')
|
||||
@base.deploy_step(priority=60)
|
||||
@task_manager.require_exclusive_lock
|
||||
def prepare_instance_boot(self, task):
|
||||
@ -292,6 +349,91 @@ class CustomAgentDeploy(agent_base.AgentBaseMixin, agent_base.AgentDeployMixin,
|
||||
msg = _('Failed to prepare instance for booting')
|
||||
agent_base.log_and_raise_deployment_error(task, msg, exc=e)
|
||||
|
||||
@METRICS.timer('CustomAgentDeploy.tear_down_agent')
|
||||
@base.deploy_step(priority=40)
|
||||
@task_manager.require_exclusive_lock
|
||||
def tear_down_agent(self, task):
|
||||
"""A deploy step to tear down the agent.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
"""
|
||||
wait = CONF.agent.post_deploy_get_power_state_retry_interval
|
||||
attempts = CONF.agent.post_deploy_get_power_state_retries + 1
|
||||
|
||||
@tenacity.retry(stop=tenacity.stop_after_attempt(attempts),
|
||||
retry=(tenacity.retry_if_result(
|
||||
lambda state: state != states.POWER_OFF)
|
||||
| tenacity.retry_if_exception_type(Exception)),
|
||||
wait=tenacity.wait_fixed(wait),
|
||||
reraise=True)
|
||||
def _wait_until_powered_off(task):
|
||||
return task.driver.power.get_power_state(task)
|
||||
|
||||
node = task.node
|
||||
|
||||
if CONF.agent.deploy_logs_collect == 'always':
|
||||
driver_utils.collect_ramdisk_logs(node)
|
||||
|
||||
# Whether ironic should power off the node via out-of-band or
|
||||
# in-band methods
|
||||
oob_power_off = strutils.bool_from_string(
|
||||
node.driver_info.get('deploy_forces_oob_reboot', False))
|
||||
can_power_on = (states.POWER_ON in
|
||||
task.driver.power.get_supported_power_states(task))
|
||||
|
||||
client = agent_client.get_client(task)
|
||||
try:
|
||||
if not can_power_on:
|
||||
LOG.info('Power interface of node %(node)s does not support '
|
||||
'power on, using reboot to switch to the instance',
|
||||
node.uuid)
|
||||
client.sync(node)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
elif not oob_power_off:
|
||||
try:
|
||||
client.power_off(node)
|
||||
except Exception as e:
|
||||
LOG.warning('Failed to soft power off node %(node_uuid)s. '
|
||||
'%(cls)s: %(error)s',
|
||||
{'node_uuid': node.uuid,
|
||||
'cls': e.__class__.__name__, 'error': e},
|
||||
exc_info=not isinstance(
|
||||
e, exception.IronicException))
|
||||
|
||||
# NOTE(dtantsur): in rare cases it may happen that the power
|
||||
# off request comes through but we never receive the response.
|
||||
# Check the power state before trying to force off.
|
||||
try:
|
||||
_wait_until_powered_off(task)
|
||||
except Exception:
|
||||
LOG.warning('Failed to soft power off node %(node_uuid)s '
|
||||
'in at least %(timeout)d seconds. Forcing '
|
||||
'hard power off and proceeding.',
|
||||
{'node_uuid': node.uuid,
|
||||
'timeout': (wait * (attempts - 1))})
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
else:
|
||||
# Flush the file system prior to hard rebooting the node
|
||||
result = client.sync(node)
|
||||
error = result.get('faultstring')
|
||||
if error:
|
||||
if 'Unknown command' in error:
|
||||
error = _('The version of the IPA ramdisk used in '
|
||||
'the deployment do not support the '
|
||||
'command "sync"')
|
||||
LOG.warning(
|
||||
'Failed to flush the file system prior to hard '
|
||||
'rebooting the node %(node)s: %(error)s',
|
||||
{'node': node.uuid, 'error': error})
|
||||
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
except Exception as e:
|
||||
msg = (_('Error rebooting node %(node)s after deploy. '
|
||||
'%(cls)s: %(error)s') %
|
||||
{'node': node.uuid, 'cls': e.__class__.__name__,
|
||||
'error': e})
|
||||
agent_base.log_and_raise_deployment_error(task, msg, exc=e)
|
||||
|
||||
def _update_instance_info(self, task):
|
||||
"""Update instance information with extra data for deploy.
|
||||
|
||||
@ -486,7 +628,7 @@ class AgentDeploy(CustomAgentDeploy):
|
||||
LOG.warning("The boot_option capability has been deprecated, "
|
||||
"please unset it for node %s", node.uuid)
|
||||
|
||||
@METRICS.timer('AgentDeployMixin.write_image')
|
||||
@METRICS.timer('AgentDeploy.write_image')
|
||||
@base.deploy_step(priority=80)
|
||||
@task_manager.require_exclusive_lock
|
||||
def write_image(self, task):
|
||||
@ -567,7 +709,7 @@ class AgentDeploy(CustomAgentDeploy):
|
||||
return agent_base.execute_step(task, new_step, 'deploy',
|
||||
client=client)
|
||||
|
||||
@METRICS.timer('AgentDeployMixin.prepare_instance_boot')
|
||||
@METRICS.timer('AgentDeploy.prepare_instance_boot')
|
||||
@base.deploy_step(priority=60)
|
||||
@task_manager.require_exclusive_lock
|
||||
def prepare_instance_boot(self, task):
|
||||
@ -628,6 +770,166 @@ class AgentDeploy(CustomAgentDeploy):
|
||||
# Remove symbolic link and image when deploy is done.
|
||||
deploy_utils.destroy_http_instance_images(task.node)
|
||||
|
||||
@METRICS.timer('AgentDeploy.prepare_instance_to_boot')
|
||||
def prepare_instance_to_boot(self, task, root_uuid, efi_sys_uuid,
|
||||
prep_boot_part_uuid=None):
|
||||
"""Prepares instance to boot.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:param root_uuid: the UUID for root partition
|
||||
:param efi_sys_uuid: the UUID for the efi partition
|
||||
:raises: InvalidState if fails to prepare instance
|
||||
"""
|
||||
|
||||
node = task.node
|
||||
# Install the boot loader
|
||||
self.configure_local_boot(
|
||||
task, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_sys_uuid,
|
||||
prep_boot_part_uuid=prep_boot_part_uuid)
|
||||
|
||||
try:
|
||||
task.driver.boot.prepare_instance(task)
|
||||
except Exception as e:
|
||||
LOG.error('Preparing instance for booting failed for instance '
|
||||
'%(instance)s. %(cls)s: %(error)s',
|
||||
{'instance': node.instance_uuid,
|
||||
'cls': e.__class__.__name__, 'error': e})
|
||||
msg = _('Failed to prepare instance for booting')
|
||||
agent_base.log_and_raise_deployment_error(task, msg, exc=e)
|
||||
|
||||
@METRICS.timer('AgentDeploy.configure_local_boot')
|
||||
def configure_local_boot(self, task, root_uuid=None,
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None):
|
||||
"""Helper method to configure local boot on the node.
|
||||
|
||||
This method triggers bootloader installation on the node.
|
||||
On successful installation of bootloader, this method sets the
|
||||
node to boot from disk.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:param root_uuid: The UUID of the root partition. This is used
|
||||
for identifying the partition which contains the image deployed
|
||||
or None in case of whole disk images which we expect to already
|
||||
have a bootloader installed.
|
||||
:param efi_system_part_uuid: The UUID of the efi system partition.
|
||||
This is used only in uefi boot mode.
|
||||
:param prep_boot_part_uuid: The UUID of the PReP Boot partition.
|
||||
This is used only for booting ppc64* hardware.
|
||||
:raises: InstanceDeployFailure if bootloader installation failed or
|
||||
on encountering error while setting the boot device on the node.
|
||||
"""
|
||||
node = task.node
|
||||
# Almost never taken into account on agent side, just used for softraid
|
||||
# Can be useful with whole_disk_images
|
||||
target_boot_mode = boot_mode_utils.get_boot_mode(task.node)
|
||||
LOG.debug('Configuring local boot for node %s', node.uuid)
|
||||
|
||||
# If the target RAID configuration is set to 'software' for the
|
||||
# 'controller', we need to trigger the installation of grub on
|
||||
# the holder disks of the desired Software RAID.
|
||||
internal_info = node.driver_internal_info
|
||||
raid_config = node.target_raid_config
|
||||
logical_disks = raid_config.get('logical_disks', [])
|
||||
software_raid = False
|
||||
for logical_disk in logical_disks:
|
||||
if logical_disk.get('controller') == 'software':
|
||||
LOG.debug('Node %s has a Software RAID configuration',
|
||||
node.uuid)
|
||||
software_raid = True
|
||||
break
|
||||
|
||||
# For software RAID try to get the UUID of the root fs from the
|
||||
# image's metadata (via Glance). Fall back to the driver internal
|
||||
# info in case it is not available (e.g. not set or there's no Glance).
|
||||
if software_raid:
|
||||
root_uuid = node.instance_info.get('image_rootfs_uuid')
|
||||
if not root_uuid:
|
||||
image_source = node.instance_info.get('image_source')
|
||||
try:
|
||||
context = task.context
|
||||
# TODO(TheJulia): Uhh, is_admin likely needs to be
|
||||
# addressed in Xena as undesirable behavior may
|
||||
# result, or just outright break in an entirely
|
||||
# system scoped configuration.
|
||||
context.is_admin = True
|
||||
glance = image_service.GlanceImageService(
|
||||
context=context)
|
||||
image_info = glance.show(image_source)
|
||||
image_properties = image_info.get('properties')
|
||||
root_uuid = image_properties['rootfs_uuid']
|
||||
LOG.debug('Got rootfs_uuid from Glance: %s '
|
||||
'(node %s)', root_uuid, node.uuid)
|
||||
except Exception as e:
|
||||
LOG.warning(
|
||||
'Could not get \'rootfs_uuid\' property for '
|
||||
'image %(image)s from Glance for node %(node)s. '
|
||||
'%(cls)s: %(error)s.',
|
||||
{'image': image_source, 'node': node.uuid,
|
||||
'cls': e.__class__.__name__, 'error': e})
|
||||
root_uuid = internal_info.get('root_uuid_or_disk_id')
|
||||
LOG.debug('Got rootfs_uuid from driver internal info: '
|
||||
'%s (node %s)', root_uuid, node.uuid)
|
||||
|
||||
# For whole disk images it is not necessary that the root_uuid
|
||||
# be provided since the bootloaders on the disk will be used
|
||||
whole_disk_image = internal_info.get('is_whole_disk_image')
|
||||
if (software_raid or (root_uuid and not whole_disk_image)
|
||||
or (whole_disk_image
|
||||
and boot_mode_utils.get_boot_mode(node) == 'uefi')):
|
||||
LOG.debug('Installing the bootloader for node %(node)s on '
|
||||
'partition %(part)s, EFI system partition %(efi)s',
|
||||
{'node': node.uuid, 'part': root_uuid,
|
||||
'efi': efi_system_part_uuid})
|
||||
client = agent_client.get_client(task)
|
||||
result = client.install_bootloader(
|
||||
node, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=prep_boot_part_uuid,
|
||||
target_boot_mode=target_boot_mode,
|
||||
software_raid=software_raid
|
||||
)
|
||||
if result['command_status'] == 'FAILED':
|
||||
msg = (_("Failed to install a bootloader when "
|
||||
"deploying node %(node)s: %(error)s") %
|
||||
{'node': node.uuid,
|
||||
'error': agent_client.get_command_error(result)})
|
||||
agent_base.log_and_raise_deployment_error(task, msg)
|
||||
|
||||
try:
|
||||
persistent = True
|
||||
# NOTE(TheJulia): We *really* only should be doing this in bios
|
||||
# boot mode. In UEFI this might just get disregarded, or cause
|
||||
# issues/failures.
|
||||
if node.driver_info.get('force_persistent_boot_device',
|
||||
'Default') == 'Never':
|
||||
persistent = False
|
||||
|
||||
vendor = task.node.properties.get('vendor', None)
|
||||
if not (vendor and vendor.lower() == 'lenovo'
|
||||
and target_boot_mode == 'uefi'):
|
||||
# Lenovo hardware is modeled on a "just update"
|
||||
# UEFI nvram model of use, and if multiple actions
|
||||
# get requested, you can end up in cases where NVRAM
|
||||
# changes are deleted as the host "restores" to the
|
||||
# backup. For more information see
|
||||
# https://bugs.launchpad.net/ironic/+bug/2053064
|
||||
# NOTE(TheJulia): We likely just need to do this with
|
||||
# all hosts in uefi mode, but libvirt VMs don't handle
|
||||
# nvram only changes *and* this pattern is known to generally
|
||||
# work for Ironic operators.
|
||||
deploy_utils.try_set_boot_device(task, boot_devices.DISK,
|
||||
persistent=persistent)
|
||||
except Exception as e:
|
||||
msg = (_("Failed to change the boot device to %(boot_dev)s "
|
||||
"when deploying node %(node)s: %(error)s") %
|
||||
{'boot_dev': boot_devices.DISK, 'node': node.uuid,
|
||||
'error': e})
|
||||
agent_base.log_and_raise_deployment_error(task, msg, exc=e)
|
||||
|
||||
LOG.info('Local boot successfully configured for node %s', node.uuid)
|
||||
|
||||
|
||||
class AgentRAID(base.RAIDInterface):
|
||||
"""Implementation of RAIDInterface which uses agent ramdisk."""
|
||||
|
@ -20,15 +20,11 @@ import collections
|
||||
|
||||
from ironic_lib import metrics_utils
|
||||
from oslo_log import log
|
||||
from oslo_utils import strutils
|
||||
import tenacity
|
||||
|
||||
from ironic.common import async_steps
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import dhcp_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import image_service
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import cleaning
|
||||
@ -40,7 +36,6 @@ from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent_client
|
||||
from ironic.drivers.modules import boot_mode_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers import utils as driver_utils
|
||||
from ironic import objects
|
||||
@ -1198,302 +1193,3 @@ class AgentOobStepsMixin(object):
|
||||
# powered off.
|
||||
log_and_raise_deployment_error(task, msg, collect_logs=False,
|
||||
exc=e)
|
||||
|
||||
|
||||
class AgentDeployMixin(HeartbeatMixin, AgentOobStepsMixin):
|
||||
"""Mixin with deploy methods."""
|
||||
|
||||
@METRICS.timer('AgentDeployMixin.get_deploy_steps')
|
||||
def get_deploy_steps(self, task):
|
||||
"""Get the list of deploy steps from the agent.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:raises InstanceDeployFailure: if the deploy steps are not yet
|
||||
available (cached), for example, when a node has just been
|
||||
enrolled and has not been deployed yet.
|
||||
:returns: A list of deploy step dictionaries
|
||||
"""
|
||||
steps = super(AgentDeployMixin, self).get_deploy_steps(task)[:]
|
||||
ib_steps = get_steps(task, 'deploy', interface='deploy')
|
||||
# NOTE(dtantsur): we allow in-band steps to be shadowed by out-of-band
|
||||
# ones, see the docstring of execute_deploy_step for details.
|
||||
steps += [step for step in ib_steps
|
||||
# FIXME(dtantsur): nested loops are not too efficient
|
||||
if not conductor_steps.find_step(steps, step)]
|
||||
return steps
|
||||
|
||||
@METRICS.timer('AgentDeployMixin.execute_deploy_step')
|
||||
def execute_deploy_step(self, task, step):
|
||||
"""Execute a deploy step.
|
||||
|
||||
We're trying to find a step among both out-of-band and in-band steps.
|
||||
In case of duplicates, out-of-band steps take priority. This property
|
||||
allows having an out-of-band deploy step that calls into
|
||||
a corresponding in-band step after some preparation (e.g. with
|
||||
additional input).
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:param step: a deploy step dictionary to execute
|
||||
:raises: InstanceDeployFailure if the agent does not return a command
|
||||
status
|
||||
:returns: states.DEPLOYWAIT to signify the step will be completed async
|
||||
"""
|
||||
agent_running = task.node.driver_internal_info.get(
|
||||
'agent_cached_deploy_steps')
|
||||
oob_steps = self.deploy_steps
|
||||
|
||||
if conductor_steps.find_step(oob_steps, step):
|
||||
return super(AgentDeployMixin, self).execute_deploy_step(
|
||||
task, step)
|
||||
elif not agent_running:
|
||||
raise exception.InstanceDeployFailure(
|
||||
_('Deploy step %(step)s has not been found. Available '
|
||||
'out-of-band steps: %(oob)s. Agent is not running.') %
|
||||
{'step': step, 'oob': oob_steps})
|
||||
else:
|
||||
return execute_step(task, step, 'deploy')
|
||||
|
||||
@METRICS.timer('AgentDeployMixin.tear_down_agent')
|
||||
@base.deploy_step(priority=40)
|
||||
@task_manager.require_exclusive_lock
|
||||
def tear_down_agent(self, task):
|
||||
"""A deploy step to tear down the agent.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
"""
|
||||
wait = CONF.agent.post_deploy_get_power_state_retry_interval
|
||||
attempts = CONF.agent.post_deploy_get_power_state_retries + 1
|
||||
|
||||
@tenacity.retry(stop=tenacity.stop_after_attempt(attempts),
|
||||
retry=(tenacity.retry_if_result(
|
||||
lambda state: state != states.POWER_OFF)
|
||||
| tenacity.retry_if_exception_type(Exception)),
|
||||
wait=tenacity.wait_fixed(wait),
|
||||
reraise=True)
|
||||
def _wait_until_powered_off(task):
|
||||
return task.driver.power.get_power_state(task)
|
||||
|
||||
node = task.node
|
||||
|
||||
if CONF.agent.deploy_logs_collect == 'always':
|
||||
driver_utils.collect_ramdisk_logs(node)
|
||||
|
||||
# Whether ironic should power off the node via out-of-band or
|
||||
# in-band methods
|
||||
oob_power_off = strutils.bool_from_string(
|
||||
node.driver_info.get('deploy_forces_oob_reboot', False))
|
||||
can_power_on = (states.POWER_ON in
|
||||
task.driver.power.get_supported_power_states(task))
|
||||
|
||||
client = agent_client.get_client(task)
|
||||
try:
|
||||
if not can_power_on:
|
||||
LOG.info('Power interface of node %(node)s does not support '
|
||||
'power on, using reboot to switch to the instance',
|
||||
node.uuid)
|
||||
client.sync(node)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
elif not oob_power_off:
|
||||
try:
|
||||
client.power_off(node)
|
||||
except Exception as e:
|
||||
LOG.warning('Failed to soft power off node %(node_uuid)s. '
|
||||
'%(cls)s: %(error)s',
|
||||
{'node_uuid': node.uuid,
|
||||
'cls': e.__class__.__name__, 'error': e},
|
||||
exc_info=not isinstance(
|
||||
e, exception.IronicException))
|
||||
|
||||
# NOTE(dtantsur): in rare cases it may happen that the power
|
||||
# off request comes through but we never receive the response.
|
||||
# Check the power state before trying to force off.
|
||||
try:
|
||||
_wait_until_powered_off(task)
|
||||
except Exception:
|
||||
LOG.warning('Failed to soft power off node %(node_uuid)s '
|
||||
'in at least %(timeout)d seconds. Forcing '
|
||||
'hard power off and proceeding.',
|
||||
{'node_uuid': node.uuid,
|
||||
'timeout': (wait * (attempts - 1))})
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
else:
|
||||
# Flush the file system prior to hard rebooting the node
|
||||
result = client.sync(node)
|
||||
error = result.get('faultstring')
|
||||
if error:
|
||||
if 'Unknown command' in error:
|
||||
error = _('The version of the IPA ramdisk used in '
|
||||
'the deployment do not support the '
|
||||
'command "sync"')
|
||||
LOG.warning(
|
||||
'Failed to flush the file system prior to hard '
|
||||
'rebooting the node %(node)s: %(error)s',
|
||||
{'node': node.uuid, 'error': error})
|
||||
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
except Exception as e:
|
||||
msg = (_('Error rebooting node %(node)s after deploy. '
|
||||
'%(cls)s: %(error)s') %
|
||||
{'node': node.uuid, 'cls': e.__class__.__name__,
|
||||
'error': e})
|
||||
log_and_raise_deployment_error(task, msg, exc=e)
|
||||
|
||||
@METRICS.timer('AgentDeployMixin.prepare_instance_to_boot')
|
||||
def prepare_instance_to_boot(self, task, root_uuid, efi_sys_uuid,
|
||||
prep_boot_part_uuid=None):
|
||||
"""Prepares instance to boot.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:param root_uuid: the UUID for root partition
|
||||
:param efi_sys_uuid: the UUID for the efi partition
|
||||
:raises: InvalidState if fails to prepare instance
|
||||
"""
|
||||
|
||||
node = task.node
|
||||
# Install the boot loader
|
||||
self.configure_local_boot(
|
||||
task, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_sys_uuid,
|
||||
prep_boot_part_uuid=prep_boot_part_uuid)
|
||||
|
||||
try:
|
||||
task.driver.boot.prepare_instance(task)
|
||||
except Exception as e:
|
||||
LOG.error('Preparing instance for booting failed for instance '
|
||||
'%(instance)s. %(cls)s: %(error)s',
|
||||
{'instance': node.instance_uuid,
|
||||
'cls': e.__class__.__name__, 'error': e})
|
||||
msg = _('Failed to prepare instance for booting')
|
||||
log_and_raise_deployment_error(task, msg, exc=e)
|
||||
|
||||
@METRICS.timer('AgentDeployMixin.configure_local_boot')
|
||||
def configure_local_boot(self, task, root_uuid=None,
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None):
|
||||
"""Helper method to configure local boot on the node.
|
||||
|
||||
This method triggers bootloader installation on the node.
|
||||
On successful installation of bootloader, this method sets the
|
||||
node to boot from disk.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:param root_uuid: The UUID of the root partition. This is used
|
||||
for identifying the partition which contains the image deployed
|
||||
or None in case of whole disk images which we expect to already
|
||||
have a bootloader installed.
|
||||
:param efi_system_part_uuid: The UUID of the efi system partition.
|
||||
This is used only in uefi boot mode.
|
||||
:param prep_boot_part_uuid: The UUID of the PReP Boot partition.
|
||||
This is used only for booting ppc64* hardware.
|
||||
:raises: InstanceDeployFailure if bootloader installation failed or
|
||||
on encountering error while setting the boot device on the node.
|
||||
"""
|
||||
node = task.node
|
||||
# Almost never taken into account on agent side, just used for softraid
|
||||
# Can be useful with whole_disk_images
|
||||
target_boot_mode = boot_mode_utils.get_boot_mode(task.node)
|
||||
LOG.debug('Configuring local boot for node %s', node.uuid)
|
||||
|
||||
# If the target RAID configuration is set to 'software' for the
|
||||
# 'controller', we need to trigger the installation of grub on
|
||||
# the holder disks of the desired Software RAID.
|
||||
internal_info = node.driver_internal_info
|
||||
raid_config = node.target_raid_config
|
||||
logical_disks = raid_config.get('logical_disks', [])
|
||||
software_raid = False
|
||||
for logical_disk in logical_disks:
|
||||
if logical_disk.get('controller') == 'software':
|
||||
LOG.debug('Node %s has a Software RAID configuration',
|
||||
node.uuid)
|
||||
software_raid = True
|
||||
break
|
||||
|
||||
# For software RAID try to get the UUID of the root fs from the
|
||||
# image's metadata (via Glance). Fall back to the driver internal
|
||||
# info in case it is not available (e.g. not set or there's no Glance).
|
||||
if software_raid:
|
||||
root_uuid = node.instance_info.get('image_rootfs_uuid')
|
||||
if not root_uuid:
|
||||
image_source = node.instance_info.get('image_source')
|
||||
try:
|
||||
context = task.context
|
||||
# TODO(TheJulia): Uhh, is_admin likely needs to be
|
||||
# addressed in Xena as undesirable behavior may
|
||||
# result, or just outright break in an entirely
|
||||
# system scoped configuration.
|
||||
context.is_admin = True
|
||||
glance = image_service.GlanceImageService(
|
||||
context=context)
|
||||
image_info = glance.show(image_source)
|
||||
image_properties = image_info.get('properties')
|
||||
root_uuid = image_properties['rootfs_uuid']
|
||||
LOG.debug('Got rootfs_uuid from Glance: %s '
|
||||
'(node %s)', root_uuid, node.uuid)
|
||||
except Exception as e:
|
||||
LOG.warning(
|
||||
'Could not get \'rootfs_uuid\' property for '
|
||||
'image %(image)s from Glance for node %(node)s. '
|
||||
'%(cls)s: %(error)s.',
|
||||
{'image': image_source, 'node': node.uuid,
|
||||
'cls': e.__class__.__name__, 'error': e})
|
||||
root_uuid = internal_info.get('root_uuid_or_disk_id')
|
||||
LOG.debug('Got rootfs_uuid from driver internal info: '
|
||||
'%s (node %s)', root_uuid, node.uuid)
|
||||
|
||||
# For whole disk images it is not necessary that the root_uuid
|
||||
# be provided since the bootloaders on the disk will be used
|
||||
whole_disk_image = internal_info.get('is_whole_disk_image')
|
||||
if (software_raid or (root_uuid and not whole_disk_image)
|
||||
or (whole_disk_image
|
||||
and boot_mode_utils.get_boot_mode(node) == 'uefi')):
|
||||
LOG.debug('Installing the bootloader for node %(node)s on '
|
||||
'partition %(part)s, EFI system partition %(efi)s',
|
||||
{'node': node.uuid, 'part': root_uuid,
|
||||
'efi': efi_system_part_uuid})
|
||||
client = agent_client.get_client(task)
|
||||
result = client.install_bootloader(
|
||||
node, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=prep_boot_part_uuid,
|
||||
target_boot_mode=target_boot_mode,
|
||||
software_raid=software_raid
|
||||
)
|
||||
if result['command_status'] == 'FAILED':
|
||||
msg = (_("Failed to install a bootloader when "
|
||||
"deploying node %(node)s: %(error)s") %
|
||||
{'node': node.uuid,
|
||||
'error': agent_client.get_command_error(result)})
|
||||
log_and_raise_deployment_error(task, msg)
|
||||
|
||||
try:
|
||||
persistent = True
|
||||
# NOTE(TheJulia): We *really* only should be doing this in bios
|
||||
# boot mode. In UEFI this might just get disregarded, or cause
|
||||
# issues/failures.
|
||||
if node.driver_info.get('force_persistent_boot_device',
|
||||
'Default') == 'Never':
|
||||
persistent = False
|
||||
|
||||
vendor = task.node.properties.get('vendor', None)
|
||||
if not (vendor and vendor.lower() == 'lenovo'
|
||||
and target_boot_mode == 'uefi'):
|
||||
# Lenovo hardware is modeled on a "just update"
|
||||
# UEFI nvram model of use, and if multiple actions
|
||||
# get requested, you can end up in cases where NVRAM
|
||||
# changes are deleted as the host "restores" to the
|
||||
# backup. For more information see
|
||||
# https://bugs.launchpad.net/ironic/+bug/2053064
|
||||
# NOTE(TheJulia): We likely just need to do this with
|
||||
# all hosts in uefi mode, but libvirt VMs don't handle
|
||||
# nvram only changes *and* this pattern is known to generally
|
||||
# work for Ironic operators.
|
||||
deploy_utils.try_set_boot_device(task, boot_devices.DISK,
|
||||
persistent=persistent)
|
||||
except Exception as e:
|
||||
msg = (_("Failed to change the boot device to %(boot_dev)s "
|
||||
"when deploying node %(node)s: %(error)s") %
|
||||
{'boot_dev': boot_devices.DISK, 'node': node.uuid,
|
||||
'error': e})
|
||||
log_and_raise_deployment_error(task, msg, exc=e)
|
||||
|
||||
LOG.info('Local boot successfully configured for node %s', node.uuid)
|
||||
|
@ -12,12 +12,16 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import time
|
||||
import types
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import dhcp_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common import image_service
|
||||
from ironic.common import images
|
||||
from ironic.common import raid
|
||||
from ironic.common import states
|
||||
@ -34,8 +38,10 @@ from ironic.drivers.modules.network import flat as flat_network
|
||||
from ironic.drivers.modules.network import neutron as neutron_network
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules.storage import noop as noop_storage
|
||||
from ironic.drivers import utils as driver_utils
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.db import utils as db_utils
|
||||
from ironic.tests.unit.drivers.modules import test_agent_base
|
||||
from ironic.tests.unit.objects import utils as object_utils
|
||||
|
||||
|
||||
@ -2330,3 +2336,928 @@ class AgentRescueTestCase(db_base.DbTestCase):
|
||||
mock_remove_rescue_net.assert_called_once_with(mock.ANY, task)
|
||||
restore_power_state_mock.assert_called_once_with(
|
||||
task, states.POWER_OFF)
|
||||
|
||||
|
||||
class TearDownAgentTest(test_agent_base.AgentDeployMixinBaseTest):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.deploy = agent.CustomAgentDeploy()
|
||||
|
||||
@mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent(
|
||||
self, power_off_mock, get_power_state_mock,
|
||||
node_power_action_mock, collect_mock,
|
||||
power_on_node_if_needed_mock):
|
||||
cfg.CONF.set_override('deploy_logs_collect', 'always', 'agent')
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.side_effect = [states.POWER_ON,
|
||||
states.POWER_OFF]
|
||||
|
||||
power_on_node_if_needed_mock.return_value = None
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertEqual(2, get_power_state_mock.call_count)
|
||||
self.assertFalse(node_power_action_mock.called)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
collect_mock.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_soft_poweroff_doesnt_complete(
|
||||
self, power_off_mock, get_power_state_mock,
|
||||
node_power_action_mock, mock_collect):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertEqual(7, get_power_state_mock.call_count)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_soft_poweroff_fails(
|
||||
self, power_off_mock, get_power_state_mock, node_power_action_mock,
|
||||
mock_collect):
|
||||
power_off_mock.side_effect = RuntimeError("boom")
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_soft_poweroff_race(
|
||||
self, power_off_mock, get_power_state_mock, node_power_action_mock,
|
||||
mock_collect):
|
||||
# Test the situation when soft power off works, but ironic doesn't
|
||||
# learn about it.
|
||||
power_off_mock.side_effect = RuntimeError("boom")
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.side_effect = [states.POWER_ON,
|
||||
states.POWER_OFF]
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertFalse(node_power_action_mock.called)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_get_power_state_fails(
|
||||
self, power_off_mock, get_power_state_mock, node_power_action_mock,
|
||||
mock_collect, power_on_node_if_needed_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.return_value = RuntimeError("boom")
|
||||
power_on_node_if_needed_mock.return_value = None
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertEqual(7, get_power_state_mock.call_count)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_power_off_fails(
|
||||
self, power_off_mock, get_power_state_mock,
|
||||
node_power_action_mock, mock_collect):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
node_power_action_mock.side_effect = RuntimeError("boom")
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.tear_down_agent,
|
||||
task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertEqual(7, get_power_state_mock.call_count)
|
||||
node_power_action_mock.assert_called_with(task, states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
mock_collect.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'sync',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_power_action_oob_power_off(
|
||||
self, sync_mock, node_power_action_mock, mock_collect):
|
||||
# Enable force power off
|
||||
driver_info = self.node.driver_info
|
||||
driver_info['deploy_forces_oob_reboot'] = True
|
||||
self.node.driver_info = driver_info
|
||||
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.deploy.tear_down_agent(task)
|
||||
|
||||
sync_mock.assert_called_once_with(task.node)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(agent.LOG, 'warning', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'sync',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_power_action_oob_power_off_failed(
|
||||
self, sync_mock, node_power_action_mock, log_mock, mock_collect):
|
||||
# Enable force power off
|
||||
driver_info = self.node.driver_info
|
||||
driver_info['deploy_forces_oob_reboot'] = True
|
||||
self.node.driver_info = driver_info
|
||||
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
log_mock.reset_mock()
|
||||
|
||||
sync_mock.return_value = {'faultstring': 'Unknown command: blah'}
|
||||
self.deploy.tear_down_agent(task)
|
||||
|
||||
sync_mock.assert_called_once_with(task.node)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
log_error = ('The version of the IPA ramdisk used in the '
|
||||
'deployment do not support the command "sync"')
|
||||
log_mock.assert_called_once_with(
|
||||
'Failed to flush the file system prior to hard rebooting the '
|
||||
'node %(node)s: %(error)s',
|
||||
{'node': task.node.uuid, 'error': log_error})
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True)
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_supported_power_states',
|
||||
lambda self, task: [states.REBOOT])
|
||||
@mock.patch.object(agent_client.AgentClient, 'sync', autospec=True)
|
||||
def test_tear_down_agent_no_power_on_support(
|
||||
self, sync_mock, node_power_action_mock, collect_mock,
|
||||
power_on_node_if_needed_mock):
|
||||
cfg.CONF.set_override('deploy_logs_collect', 'always', 'agent')
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.deploy.tear_down_agent(task)
|
||||
node_power_action_mock.assert_called_once_with(task, states.REBOOT)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
collect_mock.assert_called_once_with(task.node)
|
||||
self.assertFalse(power_on_node_if_needed_mock.called)
|
||||
sync_mock.assert_called_once_with(agent_client.get_client(task),
|
||||
task.node)
|
||||
|
||||
|
||||
class SwitchToTenantNetworkTest(test_agent_base.AgentDeployMixinBaseTest):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.deploy = agent.CustomAgentDeploy()
|
||||
|
||||
@mock.patch.object(manager_utils, 'restore_power_state_if_needed',
|
||||
autospec=True)
|
||||
@mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
|
||||
'remove_provisioning_network', spec_set=True, autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
|
||||
'configure_tenant_networks', spec_set=True, autospec=True)
|
||||
def test_switch_to_tenant_network(self, configure_tenant_net_mock,
|
||||
remove_provisioning_net_mock,
|
||||
power_on_node_if_needed_mock,
|
||||
restore_power_state_mock):
|
||||
power_on_node_if_needed_mock.return_value = states.POWER_OFF
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.deploy.switch_to_tenant_network(task)
|
||||
remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
|
||||
task)
|
||||
configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
|
||||
power_on_node_if_needed_mock.assert_called_once_with(task)
|
||||
restore_power_state_mock.assert_called_once_with(
|
||||
task, states.POWER_OFF)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
|
||||
'remove_provisioning_network', spec_set=True, autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
|
||||
'configure_tenant_networks', spec_set=True, autospec=True)
|
||||
def test_switch_to_tenant_network_fails(self, configure_tenant_net_mock,
|
||||
remove_provisioning_net_mock,
|
||||
mock_collect):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
configure_tenant_net_mock.side_effect = exception.NetworkError(
|
||||
"boom")
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.switch_to_tenant_network, task)
|
||||
remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
|
||||
task)
|
||||
configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
|
||||
class ConfigureLocalBootTest(test_agent_base.AgentDeployMixinBaseTest):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.deploy = agent.AgentDeploy()
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='whatever')
|
||||
def test_configure_local_boot(self, boot_mode_mock,
|
||||
try_set_boot_device_mock,
|
||||
install_bootloader_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task, root_uuid='some-root-uuid')
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='whatever', software_raid=False
|
||||
)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='uefi')
|
||||
def test_configure_local_boot_lenovo(self, boot_mode_mock,
|
||||
try_set_boot_device_mock,
|
||||
install_bootloader_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
props = self.node.properties
|
||||
props['vendor'] = 'Lenovo'
|
||||
props['capabilities'] = 'boot_mode:uefi'
|
||||
self.node.properties = props
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task, root_uuid='some-root-uuid')
|
||||
try_set_boot_device_mock.assert_not_called()
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi', software_raid=False
|
||||
)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='whatever')
|
||||
def test_configure_local_boot_with_prep(self, boot_mode_mock,
|
||||
try_set_boot_device_mock,
|
||||
install_bootloader_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task, root_uuid='some-root-uuid',
|
||||
prep_boot_part_uuid='fake-prep')
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid='fake-prep',
|
||||
target_boot_mode='whatever', software_raid=False
|
||||
)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='uefi')
|
||||
def test_configure_local_boot_uefi(self, boot_mode_mock,
|
||||
try_set_boot_device_mock,
|
||||
install_bootloader_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(
|
||||
task, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid='efi-system-part-uuid')
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid='efi-system-part-uuid',
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi', software_raid=False
|
||||
)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_whole_disk_image(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.deploy.configure_local_boot(task)
|
||||
# NOTE(TheJulia): We explicitly call install_bootloader when
|
||||
# we have a whole disk image *and* are in UEFI mode as setting
|
||||
# the internal NVRAM helps negate need to know a root device
|
||||
# hint if the boot order is weird.
|
||||
self.assertTrue(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_whole_disk_image_bios(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
self.config(default_boot_mode='bios', group='deploy')
|
||||
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_no_root_uuid(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_no_root_uuid_whole_disk(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
boot_mode_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
boot_mode_mock.return_value = 'uefi'
|
||||
self.deploy.configure_local_boot(
|
||||
task, root_uuid=None,
|
||||
efi_system_part_uuid='efi-system-part-uuid')
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid=None,
|
||||
efi_system_part_uuid='efi-system-part-uuid',
|
||||
prep_boot_part_uuid=None, target_boot_mode='uefi',
|
||||
software_raid=False)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
image = GlanceImageService_mock.return_value.show.return_value
|
||||
image.get.return_value = {'rootfs_uuid': 'rootfs'}
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertTrue(GlanceImageService_mock.called)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node,
|
||||
root_uuid='rootfs',
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi',
|
||||
software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_bios(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
self.config(default_boot_mode='bios', group='deploy')
|
||||
image = GlanceImageService_mock.return_value.show.return_value
|
||||
image.get.return_value = {'rootfs_uuid': 'rootfs'}
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertTrue(GlanceImageService_mock.called)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node,
|
||||
root_uuid='rootfs',
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='bios',
|
||||
software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_explicit_uuid(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
task.node.instance_info['image_rootfs_uuid'] = 'rootfs'
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(GlanceImageService_mock.called)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node,
|
||||
root_uuid='rootfs',
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi',
|
||||
software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_explicit_uuid_bios(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
self.config(default_boot_mode='bios', group='deploy')
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
task.node.instance_info['image_rootfs_uuid'] = 'rootfs'
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(GlanceImageService_mock.called)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node,
|
||||
root_uuid='rootfs',
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='bios',
|
||||
software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_exception_uefi(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
GlanceImageService_mock.side_effect = Exception('Glance not found')
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
root_uuid = "1efecf88-2b58-4d4e-8fbd-7bef1a40a1b0"
|
||||
task.node.driver_internal_info['root_uuid_or_disk_id'] = root_uuid
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertTrue(GlanceImageService_mock.called)
|
||||
# check if the root_uuid comes from the driver_internal_info
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi', software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_exception_bios(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
self.config(default_boot_mode='bios', group='deploy')
|
||||
GlanceImageService_mock.side_effect = Exception('Glance not found')
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
root_uuid = "1efecf88-2b58-4d4e-8fbd-7bef1a40a1b0"
|
||||
task.node.driver_internal_info['root_uuid_or_disk_id'] = root_uuid
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertTrue(GlanceImageService_mock.called)
|
||||
# check if the root_uuid comes from the driver_internal_info
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='bios', software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_non_software_raid(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_enforce_persistent_boot_device_default(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
driver_info = task.node.driver_info
|
||||
driver_info['force_persistent_boot_device'] = 'Default'
|
||||
task.node.driver_info = driver_info
|
||||
driver_info['force_persistent_boot_device'] = 'Always'
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_enforce_persistent_boot_device_always(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
driver_info = task.node.driver_info
|
||||
driver_info['force_persistent_boot_device'] = 'Always'
|
||||
task.node.driver_info = driver_info
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_enforce_persistent_boot_device_never(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
driver_info = task.node.driver_info
|
||||
driver_info['force_persistent_boot_device'] = 'Never'
|
||||
task.node.driver_info = driver_info
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=False)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'collect_system_logs',
|
||||
autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='whatever')
|
||||
def test_configure_local_boot_boot_loader_install_fail(
|
||||
self, boot_mode_mock, install_bootloader_mock,
|
||||
collect_logs_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'FAILED', 'command_error': 'boom'}
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.configure_local_boot,
|
||||
task, root_uuid='some-root-uuid')
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='whatever', software_raid=False
|
||||
)
|
||||
collect_logs_mock.assert_called_once_with(mock.ANY, task.node)
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'collect_system_logs',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='whatever')
|
||||
def test_configure_local_boot_set_boot_device_fail(
|
||||
self, boot_mode_mock, install_bootloader_mock,
|
||||
try_set_boot_device_mock, collect_logs_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
try_set_boot_device_mock.side_effect = RuntimeError('error')
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.configure_local_boot,
|
||||
task, root_uuid='some-root-uuid',
|
||||
prep_boot_part_uuid=None)
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='whatever', software_raid=False)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
collect_logs_mock.assert_called_once_with(mock.ANY, task.node)
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
|
||||
|
||||
class PrepareInstanceToBootTest(test_agent_base.AgentDeployMixinBaseTest):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.deploy = agent.AgentDeploy()
|
||||
|
||||
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
|
||||
@mock.patch.object(agent.AgentDeploy,
|
||||
'configure_local_boot', autospec=True)
|
||||
def test_prepare_instance_to_boot(self, configure_mock,
|
||||
prepare_instance_mock,
|
||||
failed_state_mock):
|
||||
prepare_instance_mock.return_value = None
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
root_uuid = 'root_uuid'
|
||||
efi_system_part_uuid = 'efi_sys_uuid'
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.deploy.prepare_instance_to_boot(task, root_uuid,
|
||||
efi_system_part_uuid)
|
||||
configure_mock.assert_called_once_with(
|
||||
self.deploy, task,
|
||||
root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=None)
|
||||
prepare_instance_mock.assert_called_once_with(task.driver.boot,
|
||||
task)
|
||||
self.assertFalse(failed_state_mock.called)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
|
||||
@mock.patch.object(agent.AgentDeploy,
|
||||
'configure_local_boot', autospec=True)
|
||||
def test_prepare_instance_to_boot_localboot_prep_partition(
|
||||
self, configure_mock, prepare_instance_mock, failed_state_mock):
|
||||
prepare_instance_mock.return_value = None
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
root_uuid = 'root_uuid'
|
||||
efi_system_part_uuid = 'efi_sys_uuid'
|
||||
prep_boot_part_uuid = 'prep_boot_part_uuid'
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.deploy.prepare_instance_to_boot(task, root_uuid,
|
||||
efi_system_part_uuid,
|
||||
prep_boot_part_uuid)
|
||||
configure_mock.assert_called_once_with(
|
||||
self.deploy, task,
|
||||
root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=prep_boot_part_uuid)
|
||||
prepare_instance_mock.assert_called_once_with(task.driver.boot,
|
||||
task)
|
||||
self.assertFalse(failed_state_mock.called)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
|
||||
@mock.patch.object(agent.AgentDeploy,
|
||||
'configure_local_boot', autospec=True)
|
||||
def test_prepare_instance_to_boot_configure_fails(self, configure_mock,
|
||||
prepare_mock,
|
||||
failed_state_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
root_uuid = 'root_uuid'
|
||||
efi_system_part_uuid = 'efi_sys_uuid'
|
||||
reason = 'reason'
|
||||
configure_mock.side_effect = (
|
||||
exception.InstanceDeployFailure(reason=reason))
|
||||
prepare_mock.side_effect = (
|
||||
exception.InstanceDeployFailure(reason=reason))
|
||||
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.prepare_instance_to_boot, task,
|
||||
root_uuid, efi_system_part_uuid)
|
||||
configure_mock.assert_called_once_with(
|
||||
self.deploy, task,
|
||||
root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=None)
|
||||
self.assertFalse(prepare_mock.called)
|
||||
self.assertFalse(failed_state_mock.called)
|
||||
|
@ -13,16 +13,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
import types
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
from testtools import matchers
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common import image_service
|
||||
from ironic.common import states
|
||||
from ironic.conductor import cleaning
|
||||
from ironic.conductor import servicing
|
||||
@ -33,7 +30,6 @@ from ironic.drivers import base as drivers_base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules import agent_base
|
||||
from ironic.drivers.modules import agent_client
|
||||
from ironic.drivers.modules import boot_mode_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules import fake
|
||||
from ironic.drivers.modules import pxe
|
||||
@ -50,7 +46,9 @@ DRIVER_INFO = db_utils.get_test_agent_driver_info()
|
||||
DRIVER_INTERNAL_INFO = db_utils.get_test_agent_driver_internal_info()
|
||||
|
||||
|
||||
class FakeAgentDeploy(agent_base.AgentBaseMixin, agent_base.AgentDeployMixin,
|
||||
class FakeAgentDeploy(agent_base.AgentBaseMixin,
|
||||
agent_base.HeartbeatMixin,
|
||||
agent_base.AgentOobStepsMixin,
|
||||
fake.FakeDeploy):
|
||||
pass
|
||||
|
||||
@ -687,288 +685,7 @@ class AgentRescueTests(AgentDeployMixinBaseTest):
|
||||
task, states.POWER_OFF)
|
||||
|
||||
|
||||
class AgentDeployMixinTest(AgentDeployMixinBaseTest):
|
||||
@mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent(
|
||||
self, power_off_mock, get_power_state_mock,
|
||||
node_power_action_mock, collect_mock,
|
||||
power_on_node_if_needed_mock):
|
||||
cfg.CONF.set_override('deploy_logs_collect', 'always', 'agent')
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.side_effect = [states.POWER_ON,
|
||||
states.POWER_OFF]
|
||||
|
||||
power_on_node_if_needed_mock.return_value = None
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertEqual(2, get_power_state_mock.call_count)
|
||||
self.assertFalse(node_power_action_mock.called)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
collect_mock.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_soft_poweroff_doesnt_complete(
|
||||
self, power_off_mock, get_power_state_mock,
|
||||
node_power_action_mock, mock_collect):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertEqual(7, get_power_state_mock.call_count)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_soft_poweroff_fails(
|
||||
self, power_off_mock, get_power_state_mock, node_power_action_mock,
|
||||
mock_collect):
|
||||
power_off_mock.side_effect = RuntimeError("boom")
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_soft_poweroff_race(
|
||||
self, power_off_mock, get_power_state_mock, node_power_action_mock,
|
||||
mock_collect):
|
||||
# Test the situation when soft power off works, but ironic doesn't
|
||||
# learn about it.
|
||||
power_off_mock.side_effect = RuntimeError("boom")
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.side_effect = [states.POWER_ON,
|
||||
states.POWER_OFF]
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertFalse(node_power_action_mock.called)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_get_power_state_fails(
|
||||
self, power_off_mock, get_power_state_mock, node_power_action_mock,
|
||||
mock_collect, power_on_node_if_needed_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.return_value = RuntimeError("boom")
|
||||
power_on_node_if_needed_mock.return_value = None
|
||||
self.deploy.tear_down_agent(task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertEqual(7, get_power_state_mock.call_count)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
spec=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'power_off',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_power_off_fails(
|
||||
self, power_off_mock, get_power_state_mock,
|
||||
node_power_action_mock, mock_collect):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
node_power_action_mock.side_effect = RuntimeError("boom")
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.tear_down_agent,
|
||||
task)
|
||||
power_off_mock.assert_called_once_with(task.node)
|
||||
self.assertEqual(7, get_power_state_mock.call_count)
|
||||
node_power_action_mock.assert_called_with(task, states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
mock_collect.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'sync',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_power_action_oob_power_off(
|
||||
self, sync_mock, node_power_action_mock, mock_collect):
|
||||
# Enable force power off
|
||||
driver_info = self.node.driver_info
|
||||
driver_info['deploy_forces_oob_reboot'] = True
|
||||
self.node.driver_info = driver_info
|
||||
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.deploy.tear_down_agent(task)
|
||||
|
||||
sync_mock.assert_called_once_with(task.node)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(agent_base.LOG, 'warning', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'sync',
|
||||
spec=types.FunctionType)
|
||||
def test_tear_down_agent_power_action_oob_power_off_failed(
|
||||
self, sync_mock, node_power_action_mock, log_mock, mock_collect):
|
||||
# Enable force power off
|
||||
driver_info = self.node.driver_info
|
||||
driver_info['deploy_forces_oob_reboot'] = True
|
||||
self.node.driver_info = driver_info
|
||||
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
log_mock.reset_mock()
|
||||
|
||||
sync_mock.return_value = {'faultstring': 'Unknown command: blah'}
|
||||
self.deploy.tear_down_agent(task)
|
||||
|
||||
sync_mock.assert_called_once_with(task.node)
|
||||
node_power_action_mock.assert_called_once_with(task,
|
||||
states.POWER_OFF)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
log_error = ('The version of the IPA ramdisk used in the '
|
||||
'deployment do not support the command "sync"')
|
||||
log_mock.assert_called_once_with(
|
||||
'Failed to flush the file system prior to hard rebooting the '
|
||||
'node %(node)s: %(error)s',
|
||||
{'node': task.node.uuid, 'error': log_error})
|
||||
self.assertFalse(mock_collect.called)
|
||||
|
||||
@mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True)
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch.object(time, 'sleep', lambda seconds: None)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_supported_power_states',
|
||||
lambda self, task: [states.REBOOT])
|
||||
@mock.patch.object(agent_client.AgentClient, 'sync', autospec=True)
|
||||
def test_tear_down_agent_no_power_on_support(
|
||||
self, sync_mock, node_power_action_mock, collect_mock,
|
||||
power_on_node_if_needed_mock):
|
||||
cfg.CONF.set_override('deploy_logs_collect', 'always', 'agent')
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.deploy.tear_down_agent(task)
|
||||
node_power_action_mock.assert_called_once_with(task, states.REBOOT)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
collect_mock.assert_called_once_with(task.node)
|
||||
self.assertFalse(power_on_node_if_needed_mock.called)
|
||||
sync_mock.assert_called_once_with(agent_client.get_client(task),
|
||||
task.node)
|
||||
|
||||
@mock.patch.object(manager_utils, 'restore_power_state_if_needed',
|
||||
autospec=True)
|
||||
@mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
|
||||
'remove_provisioning_network', spec_set=True, autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
|
||||
'configure_tenant_networks', spec_set=True, autospec=True)
|
||||
def test_switch_to_tenant_network(self, configure_tenant_net_mock,
|
||||
remove_provisioning_net_mock,
|
||||
power_on_node_if_needed_mock,
|
||||
restore_power_state_mock):
|
||||
power_on_node_if_needed_mock.return_value = states.POWER_OFF
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.deploy.switch_to_tenant_network(task)
|
||||
remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
|
||||
task)
|
||||
configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
|
||||
power_on_node_if_needed_mock.assert_called_once_with(task)
|
||||
restore_power_state_mock.assert_called_once_with(
|
||||
task, states.POWER_OFF)
|
||||
|
||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
|
||||
'remove_provisioning_network', spec_set=True, autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
|
||||
'configure_tenant_networks', spec_set=True, autospec=True)
|
||||
def test_switch_to_tenant_network_fails(self, configure_tenant_net_mock,
|
||||
remove_provisioning_net_mock,
|
||||
mock_collect):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
configure_tenant_net_mock.side_effect = exception.NetworkError(
|
||||
"boom")
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.switch_to_tenant_network, task)
|
||||
remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
|
||||
task)
|
||||
configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
|
||||
self.assertFalse(mock_collect.called)
|
||||
class BootInstanceTest(AgentDeployMixinBaseTest):
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
def test_boot_instance(self, node_power_action_mock):
|
||||
@ -991,620 +708,8 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
|
||||
self.deploy.boot_instance(task)
|
||||
self.assertFalse(node_power_action_mock.called)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='whatever')
|
||||
def test_configure_local_boot(self, boot_mode_mock,
|
||||
try_set_boot_device_mock,
|
||||
install_bootloader_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task, root_uuid='some-root-uuid')
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='whatever', software_raid=False
|
||||
)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='uefi')
|
||||
def test_configure_local_boot_lenovo(self, boot_mode_mock,
|
||||
try_set_boot_device_mock,
|
||||
install_bootloader_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
props = self.node.properties
|
||||
props['vendor'] = 'Lenovo'
|
||||
props['capabilities'] = 'boot_mode:uefi'
|
||||
self.node.properties = props
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task, root_uuid='some-root-uuid')
|
||||
try_set_boot_device_mock.assert_not_called()
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi', software_raid=False
|
||||
)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='whatever')
|
||||
def test_configure_local_boot_with_prep(self, boot_mode_mock,
|
||||
try_set_boot_device_mock,
|
||||
install_bootloader_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task, root_uuid='some-root-uuid',
|
||||
prep_boot_part_uuid='fake-prep')
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid='fake-prep',
|
||||
target_boot_mode='whatever', software_raid=False
|
||||
)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='uefi')
|
||||
def test_configure_local_boot_uefi(self, boot_mode_mock,
|
||||
try_set_boot_device_mock,
|
||||
install_bootloader_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(
|
||||
task, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid='efi-system-part-uuid')
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid='efi-system-part-uuid',
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi', software_raid=False
|
||||
)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_whole_disk_image(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.deploy.configure_local_boot(task)
|
||||
# NOTE(TheJulia): We explicitly call install_bootloader when
|
||||
# we have a whole disk image *and* are in UEFI mode as setting
|
||||
# the internal NVRAM helps negate need to know a root device
|
||||
# hint if the boot order is weird.
|
||||
self.assertTrue(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_whole_disk_image_bios(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
self.config(default_boot_mode='bios', group='deploy')
|
||||
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_no_root_uuid(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_no_root_uuid_whole_disk(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
boot_mode_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
boot_mode_mock.return_value = 'uefi'
|
||||
self.deploy.configure_local_boot(
|
||||
task, root_uuid=None,
|
||||
efi_system_part_uuid='efi-system-part-uuid')
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid=None,
|
||||
efi_system_part_uuid='efi-system-part-uuid',
|
||||
prep_boot_part_uuid=None, target_boot_mode='uefi',
|
||||
software_raid=False)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
image = GlanceImageService_mock.return_value.show.return_value
|
||||
image.get.return_value = {'rootfs_uuid': 'rootfs'}
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertTrue(GlanceImageService_mock.called)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node,
|
||||
root_uuid='rootfs',
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi',
|
||||
software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_bios(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
self.config(default_boot_mode='bios', group='deploy')
|
||||
image = GlanceImageService_mock.return_value.show.return_value
|
||||
image.get.return_value = {'rootfs_uuid': 'rootfs'}
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertTrue(GlanceImageService_mock.called)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node,
|
||||
root_uuid='rootfs',
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='bios',
|
||||
software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_explicit_uuid(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
task.node.instance_info['image_rootfs_uuid'] = 'rootfs'
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(GlanceImageService_mock.called)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node,
|
||||
root_uuid='rootfs',
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi',
|
||||
software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_explicit_uuid_bios(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
self.config(default_boot_mode='bios', group='deploy')
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
task.node.instance_info['image_rootfs_uuid'] = 'rootfs'
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(GlanceImageService_mock.called)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node,
|
||||
root_uuid='rootfs',
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='bios',
|
||||
software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_exception_uefi(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
GlanceImageService_mock.side_effect = Exception('Glance not found')
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
root_uuid = "1efecf88-2b58-4d4e-8fbd-7bef1a40a1b0"
|
||||
task.node.driver_internal_info['root_uuid_or_disk_id'] = root_uuid
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertTrue(GlanceImageService_mock.called)
|
||||
# check if the root_uuid comes from the driver_internal_info
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi', software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_software_raid_exception_bios(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock,
|
||||
GlanceImageService_mock):
|
||||
self.config(default_boot_mode='bios', group='deploy')
|
||||
GlanceImageService_mock.side_effect = Exception('Glance not found')
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = True
|
||||
root_uuid = "1efecf88-2b58-4d4e-8fbd-7bef1a40a1b0"
|
||||
task.node.driver_internal_info['root_uuid_or_disk_id'] = root_uuid
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertTrue(GlanceImageService_mock.called)
|
||||
# check if the root_uuid comes from the driver_internal_info
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='bios', software_raid=True)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_on_non_software_raid(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
task.node.target_raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "1",
|
||||
},
|
||||
{
|
||||
"size_gb": 'MAX',
|
||||
"raid_level": "0",
|
||||
}
|
||||
]
|
||||
}
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_enforce_persistent_boot_device_default(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
driver_info = task.node.driver_info
|
||||
driver_info['force_persistent_boot_device'] = 'Default'
|
||||
task.node.driver_info = driver_info
|
||||
driver_info['force_persistent_boot_device'] = 'Always'
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_enforce_persistent_boot_device_always(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
driver_info = task.node.driver_info
|
||||
driver_info['force_persistent_boot_device'] = 'Always'
|
||||
task.node.driver_info = driver_info
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
def test_configure_local_boot_enforce_persistent_boot_device_never(
|
||||
self, install_bootloader_mock, try_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
driver_info = task.node.driver_info
|
||||
driver_info['force_persistent_boot_device'] = 'Never'
|
||||
task.node.driver_info = driver_info
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.deploy.configure_local_boot(task)
|
||||
self.assertFalse(install_bootloader_mock.called)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=False)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'collect_system_logs',
|
||||
autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='whatever')
|
||||
def test_configure_local_boot_boot_loader_install_fail(
|
||||
self, boot_mode_mock, install_bootloader_mock,
|
||||
collect_logs_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'FAILED', 'command_error': 'boom'}
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.configure_local_boot,
|
||||
task, root_uuid='some-root-uuid')
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='whatever', software_raid=False
|
||||
)
|
||||
collect_logs_mock.assert_called_once_with(mock.ANY, task.node)
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'collect_system_logs',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
|
||||
autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True,
|
||||
return_value='whatever')
|
||||
def test_configure_local_boot_set_boot_device_fail(
|
||||
self, boot_mode_mock, install_bootloader_mock,
|
||||
try_set_boot_device_mock, collect_logs_mock):
|
||||
install_bootloader_mock.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
try_set_boot_device_mock.side_effect = RuntimeError('error')
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.configure_local_boot,
|
||||
task, root_uuid='some-root-uuid',
|
||||
prep_boot_part_uuid=None)
|
||||
boot_mode_mock.assert_called_once_with(task.node)
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
mock.ANY, task.node, root_uuid='some-root-uuid',
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='whatever', software_raid=False)
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
collect_logs_mock.assert_called_once_with(mock.ANY, task.node)
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
|
||||
@mock.patch.object(agent_base.AgentDeployMixin,
|
||||
'configure_local_boot', autospec=True)
|
||||
def test_prepare_instance_to_boot(self, configure_mock,
|
||||
prepare_instance_mock,
|
||||
failed_state_mock):
|
||||
prepare_instance_mock.return_value = None
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
root_uuid = 'root_uuid'
|
||||
efi_system_part_uuid = 'efi_sys_uuid'
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.deploy.prepare_instance_to_boot(task, root_uuid,
|
||||
efi_system_part_uuid)
|
||||
configure_mock.assert_called_once_with(
|
||||
self.deploy, task,
|
||||
root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=None)
|
||||
prepare_instance_mock.assert_called_once_with(task.driver.boot,
|
||||
task)
|
||||
self.assertFalse(failed_state_mock.called)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
|
||||
@mock.patch.object(agent_base.AgentDeployMixin,
|
||||
'configure_local_boot', autospec=True)
|
||||
def test_prepare_instance_to_boot_localboot_prep_partition(
|
||||
self, configure_mock, prepare_instance_mock, failed_state_mock):
|
||||
prepare_instance_mock.return_value = None
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
root_uuid = 'root_uuid'
|
||||
efi_system_part_uuid = 'efi_sys_uuid'
|
||||
prep_boot_part_uuid = 'prep_boot_part_uuid'
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.deploy.prepare_instance_to_boot(task, root_uuid,
|
||||
efi_system_part_uuid,
|
||||
prep_boot_part_uuid)
|
||||
configure_mock.assert_called_once_with(
|
||||
self.deploy, task,
|
||||
root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=prep_boot_part_uuid)
|
||||
prepare_instance_mock.assert_called_once_with(task.driver.boot,
|
||||
task)
|
||||
self.assertFalse(failed_state_mock.called)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
|
||||
@mock.patch.object(agent_base.AgentDeployMixin,
|
||||
'configure_local_boot', autospec=True)
|
||||
def test_prepare_instance_to_boot_configure_fails(self, configure_mock,
|
||||
prepare_mock,
|
||||
failed_state_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
root_uuid = 'root_uuid'
|
||||
efi_system_part_uuid = 'efi_sys_uuid'
|
||||
reason = 'reason'
|
||||
configure_mock.side_effect = (
|
||||
exception.InstanceDeployFailure(reason=reason))
|
||||
prepare_mock.side_effect = (
|
||||
exception.InstanceDeployFailure(reason=reason))
|
||||
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.deploy.prepare_instance_to_boot, task,
|
||||
root_uuid, efi_system_part_uuid)
|
||||
configure_mock.assert_called_once_with(
|
||||
self.deploy, task,
|
||||
root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=None)
|
||||
self.assertFalse(prepare_mock.called)
|
||||
self.assertFalse(failed_state_mock.called)
|
||||
class PostStepHooksTest(AgentDeployMixinBaseTest):
|
||||
|
||||
@mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True,
|
||||
@ -2387,7 +1492,7 @@ class StepMethodsTestCase(db_base.DbTestCase):
|
||||
self.node = object_utils.create_test_node(self.context, **n)
|
||||
self.ports = [object_utils.create_test_port(self.context,
|
||||
node_id=self.node.id)]
|
||||
self.deploy = FakeAgentDeploy()
|
||||
self.deploy = agent.CustomAgentDeploy()
|
||||
|
||||
def test_agent_get_steps(self):
|
||||
with task_manager.acquire(
|
||||
@ -2479,6 +1584,8 @@ class StepMethodsTestCase(db_base.DbTestCase):
|
||||
expected = [
|
||||
{'step': 'deploy', 'priority': 100, 'argsinfo': None,
|
||||
'interface': 'deploy'},
|
||||
{'step': 'prepare_instance_boot', 'priority': 60,
|
||||
'argsinfo': None, 'interface': 'deploy'},
|
||||
{'step': 'tear_down_agent', 'priority': 40, 'argsinfo': None,
|
||||
'interface': 'deploy'},
|
||||
{'step': 'switch_to_tenant_network', 'priority': 30,
|
||||
@ -2496,6 +1603,8 @@ class StepMethodsTestCase(db_base.DbTestCase):
|
||||
expected = [
|
||||
{'step': 'deploy', 'priority': 100, 'argsinfo': None,
|
||||
'interface': 'deploy'},
|
||||
{'step': 'prepare_instance_boot', 'priority': 60,
|
||||
'argsinfo': None, 'interface': 'deploy'},
|
||||
{'step': 'tear_down_agent', 'priority': 40, 'argsinfo': None,
|
||||
'interface': 'deploy'},
|
||||
{'step': 'switch_to_tenant_network', 'priority': 30,
|
||||
|
14
releasenotes/notes/agent-deploy-cacaf7f2585992e8.yaml
Normal file
14
releasenotes/notes/agent-deploy-cacaf7f2585992e8.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Because of the code reorganization, some metrics have been removed: all
|
||||
metrics prefixed with ``AgentDeployMixin`` are now prefixed with
|
||||
``CustomAgentDeploy`` or ``AgentDeploy`` instead.
|
||||
other:
|
||||
- |
|
||||
The ``AgentDeployMixin`` class has been removed from ``agent_base.py``.
|
||||
Third-party deploy interfaces that inherit it most probably want to
|
||||
inherit ``ironic.drivers.modules.agent.CustomAgentDeploy`` instead.
|
||||
|
||||
If you rely on the ``prepare_instance_to_boot`` or ``configure_local_boot``
|
||||
helper methods, inherit from ``AgentDeploy`` instead.
|
Loading…
x
Reference in New Issue
Block a user