Refactoring: get rid of AgentDeployMixin
Its existence is probably a legacy of the iSCSI deploy times. Currently, we have 4 different base classes/mixins in agent_base, which is confusing even for a long-term contributor like me. AgentDeployMixin is only used in CustomAgentDeploy, so it makes sense to get rid of it to simplify the code navigation. All deploy steps are moved to CustomAgentDeploy. Two two helper methods, prepare_instance_to_boot and configure_local_boot are only used in AgentDeploy, so moving them there. Change-Id: Ib670571eb511d2f2e724ecfab1d2abb1ab471346
This commit is contained in:
parent
fd2572f463
commit
4724eb9d6f
@ -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