Merge "Refactor agent iscsi deploy out of pxe driver"

This commit is contained in:
Jenkins 2015-03-09 17:32:24 +00:00 committed by Gerrit Code Review
commit 918013bd00
9 changed files with 443 additions and 272 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)