Merge "Refactor agent iscsi deploy out of pxe driver"
This commit is contained in:
commit
918013bd00
@ -376,6 +376,4 @@ class AgentVendorInterface(agent_base_vendor.BaseAgentVendor):
|
||||
LOG.debug('Rebooting node %s to disk', node.uuid)
|
||||
|
||||
manager_utils.node_set_boot_device(task, 'disk', persistent=True)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
|
||||
task.process_event('done')
|
||||
self.reboot_and_finish_deploy(task)
|
||||
|
@ -22,12 +22,15 @@ import time
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import excutils
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common.i18n import _LE
|
||||
from ironic.common.i18n import _LI
|
||||
from ironic.common.i18n import _LW
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent_client
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
@ -316,3 +319,60 @@ class BaseAgentVendor(base.VendorInterface):
|
||||
|
||||
# Only have one node_id left, return it.
|
||||
return node_ids.pop()
|
||||
|
||||
def _log_and_raise_deployment_error(self, task, msg):
|
||||
"""Helper method to log the error and raise exception."""
|
||||
LOG.error(msg)
|
||||
deploy_utils.set_failed_state(task, msg)
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
def reboot_and_finish_deploy(self, task):
|
||||
"""Helper method to trigger reboot on the node and finish deploy.
|
||||
|
||||
This method initiates a reboot on the node. On success, it
|
||||
marks the deploy as complete. On failure, it logs the error
|
||||
and marks deploy as failure.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:raises: InstanceDeployFailure, if node reboot failed.
|
||||
"""
|
||||
try:
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
except Exception as e:
|
||||
msg = (_('Error rebooting node %(node)s. Error: %(error)s') %
|
||||
{'node': task.node.uuid, 'error': e})
|
||||
self._log_and_raise_deployment_error(task, msg)
|
||||
|
||||
task.process_event('done')
|
||||
LOG.info(_LI('Deployment to node %s done'), task.node.uuid)
|
||||
|
||||
def configure_local_boot(self, task, root_uuid):
|
||||
"""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.
|
||||
:raises: InstanceDeployFailure if bootloader installation failed or
|
||||
on encountering error while setting the boot device on the node.
|
||||
"""
|
||||
node = task.node
|
||||
result = self._client.install_bootloader(node, root_uuid)
|
||||
if result['command_status'] == 'FAILED':
|
||||
msg = (_("Failed to install a bootloader when "
|
||||
"deploying node %(node)s. Error: %(error)s") %
|
||||
{'node': node.uuid,
|
||||
'error': result['command_error']})
|
||||
self._log_and_raise_deployment_error(task, msg)
|
||||
|
||||
try:
|
||||
deploy_utils.try_set_boot_device(task, boot_devices.DISK)
|
||||
except Exception as e:
|
||||
msg = (_("Failed to change the boot device to %(boot_dev)s "
|
||||
"when deploying node %(node)s. Error: %(error)s") %
|
||||
{'boot_dev': boot_devices.DISK, 'node': node.uuid,
|
||||
'error': e})
|
||||
self._log_and_raise_deployment_error(task, msg)
|
||||
|
@ -37,11 +37,13 @@ from ironic.common import disk_partitioner
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common.i18n import _LE
|
||||
from ironic.common.i18n import _LW
|
||||
from ironic.common import images
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import image_cache
|
||||
from ironic.drivers import utils as driver_utils
|
||||
from ironic.openstack.common import log as logging
|
||||
|
||||
|
||||
@ -734,3 +736,33 @@ def parse_instance_info_capabilities(node):
|
||||
parse_error()
|
||||
|
||||
return capabilities
|
||||
|
||||
|
||||
def try_set_boot_device(task, device, persistent=True):
|
||||
"""Tries to set the boot device on the node.
|
||||
|
||||
This method tries to set the boot device on the node to the given
|
||||
boot device. Under uefi boot mode, setting of boot device may differ
|
||||
between different machines. IPMI does not work for setting boot
|
||||
devices in uefi mode for certain machines. This method ignores the
|
||||
expected IPMI failure for uefi boot mode and just logs a message.
|
||||
In error cases, it is expected the operator has to manually set the
|
||||
node to boot from the correct device.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:param device: the boot device
|
||||
:param persistent: Whether to set the boot device persistently
|
||||
:raises: Any exception from set_boot_device except IPMIFailure
|
||||
(setting of boot device using ipmi is expected to fail).
|
||||
"""
|
||||
try:
|
||||
manager_utils.node_set_boot_device(task, device,
|
||||
persistent=persistent)
|
||||
except exception.IPMIFailure:
|
||||
if driver_utils.get_node_capability(task.node,
|
||||
'boot_mode') == 'uefi':
|
||||
LOG.warning(_LW("ipmitool is unable to set boot device while "
|
||||
"the node %s is in UEFI boot mode. Please set "
|
||||
"the boot device manually.") % task.node.uuid)
|
||||
else:
|
||||
raise
|
||||
|
@ -290,6 +290,63 @@ def continue_deploy(task, **kwargs):
|
||||
return root_uuid
|
||||
|
||||
|
||||
def do_agent_iscsi_deploy(task, agent_client):
|
||||
"""Method invoked when deployed with the agent ramdisk.
|
||||
|
||||
This method is invoked by drivers for doing iSCSI deploy
|
||||
using agent ramdisk. This method assumes that the agent
|
||||
is booted up on the node and is heartbeating.
|
||||
|
||||
:param task: a TaskManager object containing the node.
|
||||
:param agent_client: an instance of agent_client.AgentClient
|
||||
which will be used during iscsi deploy (for exposing node's
|
||||
target disk via iSCSI, for install boot loader, etc).
|
||||
:returns: UUID of the root partition which was deployed.
|
||||
:raises: InstanceDeployFailure, if it encounters some error
|
||||
during the deploy.
|
||||
"""
|
||||
node = task.node
|
||||
iscsi_options = build_deploy_ramdisk_options(node)
|
||||
|
||||
iqn = iscsi_options['iscsi_target_iqn']
|
||||
result = agent_client.start_iscsi_target(node, iqn)
|
||||
if result['command_status'] == 'FAILED':
|
||||
msg = (_("Failed to start the iSCSI target to deploy the "
|
||||
"node %(node)s. Error: %(error)s") %
|
||||
{'node': node.uuid, 'error': result['command_error']})
|
||||
deploy_utils.set_failed_state(task, msg)
|
||||
raise exception.InstanceDeployFailure(reason=msg)
|
||||
|
||||
address = parse.urlparse(node.driver_internal_info['agent_url'])
|
||||
address = address.hostname
|
||||
|
||||
# TODO(lucasagomes): The 'error' and 'key' parameters in the
|
||||
# dictionary below are just being passed because it's needed for
|
||||
# the iscsi_deploy.continue_deploy() method, we are fooling it
|
||||
# for now. The agent driver doesn't use/need those. So we need to
|
||||
# refactor this bits here later.
|
||||
iscsi_params = {'error': result['command_error'],
|
||||
'iqn': iqn,
|
||||
'key': iscsi_options['deployment_key'],
|
||||
'address': address}
|
||||
|
||||
root_uuid = continue_deploy(task, **iscsi_params)
|
||||
if not root_uuid:
|
||||
msg = (_("Couldn't determine the UUID of the root partition "
|
||||
"when deploying node %s") % node.uuid)
|
||||
deploy_utils.set_failed_state(task, msg)
|
||||
raise exception.InstanceDeployFailure(reason=msg)
|
||||
|
||||
# TODO(lucasagomes): Move this bit saving the root_uuid to
|
||||
# iscsi_deploy.continue_deploy()
|
||||
driver_internal_info = node.driver_internal_info
|
||||
driver_internal_info['root_uuid'] = root_uuid
|
||||
node.driver_internal_info = driver_internal_info
|
||||
node.save()
|
||||
|
||||
return root_uuid
|
||||
|
||||
|
||||
def parse_root_device_hints(node):
|
||||
"""Parse the root_device property of a node.
|
||||
|
||||
|
@ -21,7 +21,6 @@ import os
|
||||
import shutil
|
||||
|
||||
from oslo_config import cfg
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import dhcp_factory
|
||||
@ -279,24 +278,6 @@ def _destroy_token_file(node):
|
||||
utils.unlink_without_raise(token_file_path)
|
||||
|
||||
|
||||
def try_set_boot_device(task, device, persistent=True):
|
||||
# NOTE(faizan): Under UEFI boot mode, setting of boot device may differ
|
||||
# between different machines. IPMI does not work for setting boot
|
||||
# devices in UEFI mode for certain machines.
|
||||
# Expected IPMI failure for uefi boot mode. Logging a message to
|
||||
# set the boot device manually and continue with deploy.
|
||||
try:
|
||||
manager_utils.node_set_boot_device(task, device, persistent=persistent)
|
||||
except exception.IPMIFailure:
|
||||
if driver_utils.get_node_capability(task.node,
|
||||
'boot_mode') == 'uefi':
|
||||
LOG.warning(_LW("ipmitool is unable to set boot device while "
|
||||
"the node %s is in UEFI boot mode. Please set "
|
||||
"the boot device manually.") % task.node.uuid)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class PXEDeploy(base.DeployInterface):
|
||||
"""PXE Deploy Interface for deploy-related actions."""
|
||||
|
||||
@ -377,7 +358,7 @@ class PXEDeploy(base.DeployInterface):
|
||||
provider = dhcp_factory.DHCPFactory()
|
||||
provider.update_dhcp(task, dhcp_opts)
|
||||
|
||||
try_set_boot_device(task, boot_devices.PXE)
|
||||
deploy_utils.try_set_boot_device(task, boot_devices.PXE)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
|
||||
return states.DEPLOYWAIT
|
||||
@ -544,7 +525,7 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
|
||||
|
||||
try:
|
||||
if iscsi_deploy.get_boot_option(node) == "local":
|
||||
try_set_boot_device(task, boot_devices.DISK)
|
||||
deploy_utils.try_set_boot_device(task, boot_devices.DISK)
|
||||
# If it's going to boot from the local disk, get rid of
|
||||
# the PXE configuration files used for the deployment
|
||||
pxe_utils.clean_up_pxe_config(task)
|
||||
@ -563,95 +544,42 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
|
||||
msg = _('Failed to continue iSCSI deployment.')
|
||||
deploy_utils.set_failed_state(task, msg)
|
||||
|
||||
def _log_and_raise_deployment_error(self, task, msg):
|
||||
LOG.error(msg)
|
||||
deploy_utils.set_failed_state(task, msg)
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def continue_deploy(self, task, **kwargs):
|
||||
"""Method invoked when deployed with the IPA ramdisk."""
|
||||
"""Method invoked when deployed with the IPA ramdisk.
|
||||
|
||||
This method is invoked during a heartbeat from an agent when
|
||||
the node is in wait-call-back state. This deploys the image on
|
||||
the node and then configures the node to boot according to the
|
||||
desired boot option (netboot or localboot).
|
||||
|
||||
:param task: a TaskManager object containing the node.
|
||||
:param kwargs: the kwargs passed from the heartbeat method.
|
||||
:raises: InstanceDeployFailure, if it encounters some error during
|
||||
the deploy.
|
||||
"""
|
||||
task.process_event('resume')
|
||||
node = task.node
|
||||
LOG.debug('Continuing the deployment on node %s', node.uuid)
|
||||
|
||||
task.process_event('resume')
|
||||
|
||||
pxe_info = _get_image_info(node, task.context)
|
||||
pxe_options = _build_pxe_config_options(task.node, pxe_info,
|
||||
task.context)
|
||||
|
||||
iqn = pxe_options['iscsi_target_iqn']
|
||||
result = self._client.start_iscsi_target(node, iqn)
|
||||
if result['command_status'] == 'FAILED':
|
||||
msg = (_("Failed to start the iSCSI target to deploy the "
|
||||
"node %(node)s. Error: %(error)s") %
|
||||
{'node': node.uuid, 'error': result['command_error']})
|
||||
self._log_and_raise_deployment_error(task, msg)
|
||||
|
||||
address = urlparse.urlparse(node.driver_internal_info['agent_url'])
|
||||
address = address.hostname
|
||||
|
||||
# TODO(lucasagomes): The 'error' and 'key' parameters in the
|
||||
# dictionary below are just being passed because it's needed for
|
||||
# the iscsi_deploy.continue_deploy() method, we are fooling it
|
||||
# for now. The agent driver doesn't use/need those. So we need to
|
||||
# refactor this bits here later.
|
||||
iscsi_params = {'error': result['command_error'],
|
||||
'iqn': iqn,
|
||||
'key': pxe_options['deployment_key'],
|
||||
'address': address}
|
||||
|
||||
# NOTE(lucasagomes): We don't use the token file with the agent,
|
||||
# but as it's created as part of deploy() we are going to remove
|
||||
# it here.
|
||||
_destroy_token_file(node)
|
||||
|
||||
root_uuid = iscsi_deploy.continue_deploy(task, **iscsi_params)
|
||||
if not root_uuid:
|
||||
msg = (_("Couldn't determine the UUID of the root partition "
|
||||
"when deploying node %s") % node.uuid)
|
||||
self._log_and_raise_deployment_error(task, msg)
|
||||
|
||||
# TODO(lucasagomes): Move this bit saving the root_uuid to
|
||||
# iscsi_deploy.continue_deploy()
|
||||
driver_internal_info = node.driver_internal_info
|
||||
driver_internal_info['root_uuid'] = root_uuid
|
||||
node.driver_internal_info = driver_internal_info
|
||||
node.save()
|
||||
root_uuid = iscsi_deploy.do_agent_iscsi_deploy(task, self._client)
|
||||
|
||||
if iscsi_deploy.get_boot_option(node) == "local":
|
||||
# Install the boot loader
|
||||
result = self._client.install_bootloader(node, root_uuid)
|
||||
if result['command_status'] == 'FAILED':
|
||||
msg = (_("Failed to install a bootloader when "
|
||||
"deploying node %(node)s. Error: %(error)s") %
|
||||
{'node': node.uuid,
|
||||
'error': result['command_error']})
|
||||
self._log_and_raise_deployment_error(task, msg)
|
||||
|
||||
try:
|
||||
try_set_boot_device(task, boot_devices.DISK)
|
||||
except Exception as e:
|
||||
msg = (_("Failed to change the boot device to %(boot_dev)s "
|
||||
"when deploying node %(node)s. Error: %(error)s") %
|
||||
{'boot_dev': boot_devices.DISK, 'node': node.uuid,
|
||||
'error': e})
|
||||
self._log_and_raise_deployment_error(task, msg)
|
||||
self.configure_local_boot(task, root_uuid)
|
||||
|
||||
# If it's going to boot from the local disk, get rid of
|
||||
# the PXE configuration files used for the deployment
|
||||
pxe_utils.clean_up_pxe_config(task)
|
||||
else:
|
||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
|
||||
boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
|
||||
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
|
||||
driver_utils.get_node_capability(node, 'boot_mode'))
|
||||
boot_mode)
|
||||
|
||||
try:
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
except Exception as e:
|
||||
msg = (_('Error rebooting node %(node)s. Error: %(error)s') %
|
||||
{'node': node.uuid, 'error': e})
|
||||
self._log_and_raise_deployment_error(task, msg)
|
||||
|
||||
task.process_event('done')
|
||||
LOG.info(_LI('Deployment to node %s done'), node.uuid)
|
||||
self.reboot_and_finish_deploy(task)
|
||||
|
@ -17,10 +17,13 @@
|
||||
|
||||
import mock
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import agent_base_vendor
|
||||
from ironic.drivers.modules import agent_client
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic import objects
|
||||
from ironic.tests.conductor import utils as mgr_utils
|
||||
@ -296,3 +299,85 @@ class TestBaseAgentVendor(db_base.DbTestCase):
|
||||
driver_routes = task.driver.vendor.driver_routes
|
||||
self.assertIsInstance(driver_routes, dict)
|
||||
self.assertEqual(expected, list(driver_routes))
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_power_action')
|
||||
def test_reboot_and_finish_deploy_success(self, node_power_action_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'],
|
||||
shared=False) as task:
|
||||
self.passthru.reboot_and_finish_deploy(task)
|
||||
node_power_action_mock.assert_called_once_with(task, states.REBOOT)
|
||||
self.assertEqual(states.ACTIVE, task.node.provision_state)
|
||||
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_power_action')
|
||||
def test_reboot_and_finish_deploy_reboot_failure(self,
|
||||
node_power_action_mock):
|
||||
exc = exception.PowerStateFailure(pstate=states.REBOOT)
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
node_power_action_mock.side_effect = exc
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.passthru.reboot_and_finish_deploy, task)
|
||||
node_power_action_mock.assert_any_call(task, states.REBOOT)
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device')
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
|
||||
def test_configure_local_boot(self, install_bootloader_mock,
|
||||
try_set_boot_device_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:
|
||||
self.passthru.configure_local_boot(task, 'some-root-uuid')
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
task.node, 'some-root-uuid')
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
|
||||
def test_configure_local_boot_boot_loader_install_fail(
|
||||
self, install_bootloader_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:
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.passthru.configure_local_boot,
|
||||
task, 'some-root-uuid')
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
task.node, 'some-root-uuid')
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'try_set_boot_device')
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
|
||||
def test_configure_local_boot_set_boot_device_fail(
|
||||
self, install_bootloader_mock, try_set_boot_device_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:
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.passthru.configure_local_boot,
|
||||
task, 'some-root-uuid')
|
||||
install_bootloader_mock.assert_called_once_with(
|
||||
task.node, 'some-root-uuid')
|
||||
try_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK)
|
||||
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
|
@ -28,11 +28,13 @@ from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
import requests
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import disk_partitioner
|
||||
from ironic.common import exception
|
||||
from ironic.common import images
|
||||
from ironic.common import utils as common_utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import deploy_utils as utils
|
||||
from ironic.drivers.modules import image_cache
|
||||
from ironic.tests import base as tests_base
|
||||
@ -1158,3 +1160,60 @@ class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase):
|
||||
self.node.instance_info = {'capabilities': 'not-a-dict'}
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
utils.parse_instance_info_capabilities, self.node)
|
||||
|
||||
|
||||
class TrySetBootDeviceTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TrySetBootDeviceTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver="fake")
|
||||
self.node = obj_utils.create_test_node(self.context, driver="fake")
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device')
|
||||
def test_try_set_boot_device_okay(self, node_set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
utils.try_set_boot_device(task, boot_devices.DISK,
|
||||
persistent=True)
|
||||
node_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(utils, 'LOG')
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device')
|
||||
def test_try_set_boot_device_ipmifailure_uefi(self,
|
||||
node_set_boot_device_mock, log_mock):
|
||||
self.node.properties = {'capabilities': 'boot_mode:uefi'}
|
||||
self.node.save()
|
||||
node_set_boot_device_mock.side_effect = exception.IPMIFailure(cmd='a')
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
utils.try_set_boot_device(task, boot_devices.DISK,
|
||||
persistent=True)
|
||||
node_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
log_mock.warning.assert_called_once_with(mock.ANY)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device')
|
||||
def test_try_set_boot_device_ipmifailure_bios(
|
||||
self, node_set_boot_device_mock):
|
||||
node_set_boot_device_mock.side_effect = exception.IPMIFailure(cmd='a')
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.IPMIFailure,
|
||||
utils.try_set_boot_device,
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
node_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device')
|
||||
def test_try_set_boot_device_some_other_exception(
|
||||
self, node_set_boot_device_mock):
|
||||
exc = exception.IloOperationError(operation="qwe", error="error")
|
||||
node_set_boot_device_mock.side_effect = exc
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.IloOperationError,
|
||||
utils.try_set_boot_device,
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
node_set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
|
@ -515,3 +515,88 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
||||
self.assertEqual('1.1.1.1', ret_val['address'])
|
||||
self.assertEqual('target-iqn', ret_val['iqn'])
|
||||
self.assertEqual('local', ret_val['boot_option'])
|
||||
|
||||
@mock.patch.object(iscsi_deploy, 'continue_deploy')
|
||||
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
|
||||
def test_do_agent_iscsi_deploy_okay(self, build_options_mock,
|
||||
continue_deploy_mock):
|
||||
build_options_mock.return_value = {'deployment_key': 'abcdef',
|
||||
'iscsi_target_iqn': 'iqn-qweqwe'}
|
||||
agent_client_mock = mock.MagicMock()
|
||||
agent_client_mock.start_iscsi_target.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
driver_internal_info = {'agent_url': 'http://1.2.3.4:1234'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
continue_deploy_mock.return_value = 'some-root-uuid'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
ret_val = iscsi_deploy.do_agent_iscsi_deploy(
|
||||
task, agent_client_mock)
|
||||
build_options_mock.assert_called_once_with(task.node)
|
||||
agent_client_mock.start_iscsi_target.assert_called_once_with(
|
||||
task.node, 'iqn-qweqwe')
|
||||
continue_deploy_mock.assert_called_once_with(
|
||||
task, error=None, iqn='iqn-qweqwe', key='abcdef',
|
||||
address='1.2.3.4')
|
||||
self.assertEqual('some-root-uuid', ret_val)
|
||||
self.assertEqual('some-root-uuid',
|
||||
task.node.driver_internal_info['root_uuid'])
|
||||
|
||||
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
|
||||
def test_do_agent_iscsi_deploy_start_iscsi_failure(self,
|
||||
build_options_mock):
|
||||
build_options_mock.return_value = {'deployment_key': 'abcdef',
|
||||
'iscsi_target_iqn': 'iqn-qweqwe'}
|
||||
agent_client_mock = mock.MagicMock()
|
||||
agent_client_mock.start_iscsi_target.return_value = {
|
||||
'command_status': 'FAILED', 'command_error': 'booom'}
|
||||
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:
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
iscsi_deploy.do_agent_iscsi_deploy,
|
||||
task, agent_client_mock)
|
||||
build_options_mock.assert_called_once_with(task.node)
|
||||
agent_client_mock.start_iscsi_target.assert_called_once_with(
|
||||
task.node, 'iqn-qweqwe')
|
||||
self.node.refresh()
|
||||
self.assertEqual(states.DEPLOYFAIL, self.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, self.node.target_provision_state)
|
||||
self.assertIsNotNone(self.node.last_error)
|
||||
|
||||
@mock.patch.object(iscsi_deploy, 'continue_deploy')
|
||||
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
|
||||
def test_do_agent_iscsi_deploy_no_root_uuid(self, build_options_mock,
|
||||
continue_deploy_mock):
|
||||
build_options_mock.return_value = {'deployment_key': 'abcdef',
|
||||
'iscsi_target_iqn': 'iqn-qweqwe'}
|
||||
agent_client_mock = mock.MagicMock()
|
||||
agent_client_mock.start_iscsi_target.return_value = {
|
||||
'command_status': 'SUCCESS', 'command_error': None}
|
||||
driver_internal_info = {'agent_url': 'http://1.2.3.4:1234'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
continue_deploy_mock.return_value = None
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
iscsi_deploy.do_agent_iscsi_deploy,
|
||||
task, agent_client_mock)
|
||||
self.node.refresh()
|
||||
build_options_mock.assert_called_once_with(task.node)
|
||||
agent_client_mock.start_iscsi_target.assert_called_once_with(
|
||||
task.node, 'iqn-qweqwe')
|
||||
continue_deploy_mock.assert_called_once_with(
|
||||
task, error=None, iqn='iqn-qweqwe', key='abcdef',
|
||||
address='1.2.3.4')
|
||||
self.assertEqual(states.DEPLOYFAIL, self.node.provision_state)
|
||||
self.assertEqual(states.ACTIVE, self.node.target_provision_state)
|
||||
self.assertIsNotNone(self.node.last_error)
|
||||
|
@ -21,7 +21,6 @@ import os
|
||||
import tempfile
|
||||
|
||||
import mock
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils as json
|
||||
|
||||
@ -36,7 +35,7 @@ from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import agent_client
|
||||
from ironic.drivers.modules import agent_base_vendor
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.drivers.modules import pxe
|
||||
@ -928,10 +927,6 @@ class CleanUpFullFlowTestCase(db_base.DbTestCase):
|
||||
'%s is not expected to exist' % path)
|
||||
|
||||
|
||||
@mock.patch.object(iscsi_deploy, 'continue_deploy')
|
||||
@mock.patch.object(agent_client.AgentClient, 'start_iscsi_target')
|
||||
@mock.patch.object(pxe, '_build_pxe_config_options')
|
||||
@mock.patch.object(pxe, '_get_image_info')
|
||||
class TestAgentVendorPassthru(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -949,179 +944,51 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
|
||||
self.task.driver = self.driver
|
||||
self.task.context = self.context
|
||||
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'reboot_and_finish_deploy')
|
||||
@mock.patch.object(deploy_utils, 'switch_pxe_config')
|
||||
@mock.patch.object(manager_utils, 'node_power_action')
|
||||
def test_continue_deploy(self, mock_node_power, mock_pxe_config,
|
||||
mock_image_info, mock_pxe_opts, mock_start_iscsi,
|
||||
mock_cont_deploy):
|
||||
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
|
||||
'deployment_key': 'fake-deploy-key'}
|
||||
mock_start_iscsi.return_value = {'command_error': None,
|
||||
'command_status': 'SUCCEEDED'}
|
||||
mock_cont_deploy.return_value = 'fake-root-uuid'
|
||||
@mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy')
|
||||
@mock.patch.object(pxe, '_destroy_token_file')
|
||||
def test_continue_deploy_netboot(self, destroy_token_file_mock,
|
||||
do_agent_iscsi_deploy_mock,
|
||||
switch_pxe_config_mock,
|
||||
reboot_and_finish_deploy_mock):
|
||||
|
||||
do_agent_iscsi_deploy_mock.return_value = 'some-root-uuid'
|
||||
|
||||
self.driver.vendor.continue_deploy(self.task)
|
||||
mock_image_info.assert_called_once_with(self.node, self.context)
|
||||
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
|
||||
self.task.context)
|
||||
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
|
||||
mock_pxe_config.assert_called_once_with(mock.ANY, 'fake-root-uuid',
|
||||
None)
|
||||
mock_node_power.assert_called_once_with(self.task, states.REBOOT)
|
||||
self.assertIn('root_uuid', self.node.driver_internal_info)
|
||||
self.assertIsNone(self.node.last_error)
|
||||
|
||||
def test_continue_deploy_command_failed(self, mock_image_info,
|
||||
mock_pxe_opts, mock_start_iscsi,
|
||||
mock_cont_deploy):
|
||||
command_error = 'Gotham city is in danger!'
|
||||
mock_start_iscsi.return_value = {'command_error': command_error,
|
||||
'command_status': 'FAILED'}
|
||||
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
|
||||
'deployment_key': 'fake-deploy-key'}
|
||||
result = self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver.vendor.continue_deploy, self.task)
|
||||
self.assertIn(command_error, result.format_message())
|
||||
mock_image_info.assert_called_once_with(self.node, self.context)
|
||||
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
|
||||
self.task.context)
|
||||
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
|
||||
# assert iscsi_deploy.continue_deploy wasn't called because the
|
||||
# iSCSI target couldn't be initiated
|
||||
self.assertFalse(mock_cont_deploy.called)
|
||||
self.assertIsNotNone(self.node.last_error)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'switch_pxe_config')
|
||||
@mock.patch.object(manager_utils, 'node_power_action')
|
||||
def test_continue_deploy_cannot_reboot(self, mock_node_power,
|
||||
mock_pxe_config, mock_image_info,
|
||||
mock_pxe_opts, mock_start_iscsi,
|
||||
mock_cont_deploy):
|
||||
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
|
||||
'deployment_key': 'fake-deploy-key'}
|
||||
mock_start_iscsi.return_value = {'command_error': None,
|
||||
'command_status': 'SUCCEEDED'}
|
||||
mock_cont_deploy.return_value = 'fake-root-uuid'
|
||||
mock_node_power.side_effect = processutils.ProcessExecutionError()
|
||||
result = self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver.vendor.continue_deploy, self.task)
|
||||
self.assertIn("Error rebooting node %s" % self.node.uuid,
|
||||
result.format_message())
|
||||
mock_image_info.assert_called_once_with(self.node, self.context)
|
||||
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
|
||||
self.task.context)
|
||||
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
|
||||
mock_pxe_config.assert_called_once_with(mock.ANY, 'fake-root-uuid',
|
||||
None)
|
||||
self.assertIsNotNone(self.node.last_error)
|
||||
|
||||
def test_continue_deploy_no_root_uuid(self, mock_image_info, mock_pxe_opts,
|
||||
mock_start_iscsi, mock_cont_deploy):
|
||||
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
|
||||
'deployment_key': 'fake-deploy-key'}
|
||||
mock_start_iscsi.return_value = {'command_error': None,
|
||||
'command_status': 'SUCCEEDED'}
|
||||
mock_cont_deploy.return_value = None
|
||||
result = self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver.vendor.continue_deploy, self.task)
|
||||
self.assertIn("Couldn't determine the UUID of the root partition "
|
||||
"when deploying node %s" % self.node.uuid,
|
||||
result.format_message())
|
||||
mock_image_info.assert_called_once_with(self.node, self.context)
|
||||
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
|
||||
self.task.context)
|
||||
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
|
||||
self.assertNotIn('root_uuid', self.node.driver_internal_info)
|
||||
self.assertIsNotNone(self.node.last_error)
|
||||
destroy_token_file_mock.assert_called_once_with(self.node)
|
||||
do_agent_iscsi_deploy_mock.assert_called_once_with(
|
||||
self.task, self.driver.vendor._client)
|
||||
tftp_config = '/tftpboot/%s/config' % self.node.uuid
|
||||
switch_pxe_config_mock.assert_called_once_with(tftp_config,
|
||||
'some-root-uuid',
|
||||
None)
|
||||
reboot_and_finish_deploy_mock.assert_called_once_with(self.task)
|
||||
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'reboot_and_finish_deploy')
|
||||
@mock.patch.object(pxe_utils, 'clean_up_pxe_config')
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device')
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
|
||||
@mock.patch.object(deploy_utils, 'switch_pxe_config')
|
||||
@mock.patch.object(manager_utils, 'node_power_action')
|
||||
def test_continue_deploy_localboot(self, mock_node_power, mock_pxe_config,
|
||||
mock_install_bootloader, mock_boot_dev,
|
||||
mock_clean_pxe, mock_image_info, mock_pxe_opts,
|
||||
mock_start_iscsi, mock_cont_deploy):
|
||||
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
|
||||
'deployment_key': 'fake-deploy-key'}
|
||||
mock_start_iscsi.return_value = {'command_error': None,
|
||||
'command_status': 'SUCCEEDED'}
|
||||
mock_cont_deploy.return_value = 'fake-root-uuid'
|
||||
i_info = self.node.instance_info
|
||||
i_info['capabilities'] = {'boot_option': 'local'}
|
||||
mock_install_bootloader.return_value = {'command_status': 'SUCCEEDED'}
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'configure_local_boot')
|
||||
@mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy')
|
||||
@mock.patch.object(pxe, '_destroy_token_file')
|
||||
def test_continue_deploy_localboot(self, destroy_token_file_mock,
|
||||
do_agent_iscsi_deploy_mock,
|
||||
configure_local_boot_mock,
|
||||
clean_up_pxe_config_mock,
|
||||
reboot_and_finish_deploy_mock):
|
||||
|
||||
self.node.instance_info = {
|
||||
'capabilities': {'boot_option': 'local'}}
|
||||
self.node.save()
|
||||
do_agent_iscsi_deploy_mock.return_value = 'some-root-uuid'
|
||||
|
||||
self.driver.vendor.continue_deploy(self.task)
|
||||
|
||||
mock_image_info.assert_called_once_with(self.node, self.context)
|
||||
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
|
||||
self.task.context)
|
||||
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
|
||||
self.assertIn('root_uuid', self.node.driver_internal_info)
|
||||
mock_node_power.assert_called_once_with(self.task, states.REBOOT)
|
||||
mock_install_bootloader.assert_called_once_with(self.node,
|
||||
'fake-root-uuid')
|
||||
self.assertIsNone(self.node.last_error)
|
||||
# Assert we clean up the PXE configuration and make set the boot
|
||||
# device to boot from disk
|
||||
mock_clean_pxe.assert_called_once_with(self.task)
|
||||
mock_boot_dev.assert_called_once_with(self.task, boot_devices.DISK,
|
||||
persistent=True)
|
||||
|
||||
# Assert we dont try to switch the PXE configuration
|
||||
self.assertFalse(mock_pxe_config.called)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
|
||||
def test_continue_deploy_localboot_command_failed(self,
|
||||
mock_install_bootloader, mock_image_info,
|
||||
mock_pxe_opts, mock_start_iscsi,
|
||||
mock_cont_deploy):
|
||||
command_error = 'Gotham city is in danger!'
|
||||
mock_install_bootloader.return_value = {'command_error': command_error,
|
||||
'command_status': 'FAILED'}
|
||||
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
|
||||
'deployment_key': 'fake-deploy-key'}
|
||||
mock_start_iscsi.return_value = {'command_error': None,
|
||||
'command_status': 'SUCCEEDED'}
|
||||
mock_cont_deploy.return_value = 'fake-root-uuid'
|
||||
i_info = self.node.instance_info
|
||||
i_info['capabilities'] = {'boot_option': 'local'}
|
||||
|
||||
result = self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver.vendor.continue_deploy, self.task)
|
||||
self.assertIn(command_error, result.format_message())
|
||||
mock_image_info.assert_called_once_with(self.node, self.context)
|
||||
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
|
||||
self.task.context)
|
||||
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
|
||||
mock_install_bootloader.assert_called_once_with(self.node,
|
||||
'fake-root-uuid')
|
||||
self.assertIsNotNone(self.node.last_error)
|
||||
|
||||
@mock.patch.object(pxe, 'try_set_boot_device')
|
||||
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
|
||||
def test_continue_deploy_set_boot_device_failed(self,
|
||||
mock_install_bootloader, mock_set_boot_dev,
|
||||
mock_image_info, mock_pxe_opts, mock_start_iscsi,
|
||||
mock_cont_deploy):
|
||||
error_msg = "User error. Please replace the user."
|
||||
mock_set_boot_dev.side_effect = exception.IPMIFailure(error_msg)
|
||||
mock_install_bootloader.return_value = {'command_error': None,
|
||||
'command_status': 'SUCCEEDED'}
|
||||
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
|
||||
'deployment_key': 'fake-deploy-key'}
|
||||
mock_start_iscsi.return_value = {'command_error': None,
|
||||
'command_status': 'SUCCEEDED'}
|
||||
mock_cont_deploy.return_value = 'fake-root-uuid'
|
||||
i_info = self.node.instance_info
|
||||
i_info['capabilities'] = {'boot_option': 'local'}
|
||||
|
||||
result = self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver.vendor.continue_deploy, self.task)
|
||||
self.assertIn(error_msg, result.format_message())
|
||||
mock_image_info.assert_called_once_with(self.node, self.context)
|
||||
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
|
||||
self.task.context)
|
||||
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
|
||||
mock_install_bootloader.assert_called_once_with(self.node,
|
||||
'fake-root-uuid')
|
||||
self.assertIsNotNone(self.node.last_error)
|
||||
destroy_token_file_mock.assert_called_once_with(self.node)
|
||||
do_agent_iscsi_deploy_mock.assert_called_once_with(
|
||||
self.task, self.driver.vendor._client)
|
||||
configure_local_boot_mock.assert_called_once_with(self.task,
|
||||
'some-root-uuid')
|
||||
clean_up_pxe_config_mock.assert_called_once_with(self.task)
|
||||
reboot_and_finish_deploy_mock.assert_called_once_with(self.task)
|
||||
|
Loading…
Reference in New Issue
Block a user