[ansible] driver refactoring
make driver much closer to AgentDeploy by reusing HeartbeatMixin and implementing other required methods This effectively requires ironic version > 7.0 Change-Id: I9ba5186ecdae49a17785a109341050c828c849cc
This commit is contained in:
parent
77326e7a5c
commit
6e8cd3163f
@ -38,11 +38,11 @@ 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 rpcapi
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent_base_vendor as agent_base
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
|
||||
|
||||
@ -182,34 +182,6 @@ def _get_node_ip(task):
|
||||
return _get_node_ip_dhcp(task)
|
||||
|
||||
|
||||
# some good code from agent
|
||||
def _reboot_and_finish_deploy(task):
|
||||
wait = CONF.ansible.post_deploy_get_power_state_retry_interval * 1000
|
||||
attempts = CONF.ansible.post_deploy_get_power_state_retries + 1
|
||||
|
||||
@retrying.retry(
|
||||
stop_max_attempt_number=attempts,
|
||||
retry_on_result=lambda state: state != states.POWER_OFF,
|
||||
wait_fixed=wait
|
||||
)
|
||||
def _wait_until_powered_off(task):
|
||||
return task.driver.power.get_power_state(task)
|
||||
|
||||
try:
|
||||
_wait_until_powered_off(task)
|
||||
except Exception as e:
|
||||
LOG.warning(_LW('Failed to soft power off node %(node_uuid)s '
|
||||
'in at least %(timeout)d seconds. Error: %(error)s'),
|
||||
{'node_uuid': task.node.uuid,
|
||||
'timeout': (wait * (attempts - 1)) / 1000,
|
||||
'error': e})
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
|
||||
task.driver.network.remove_provisioning_network(task)
|
||||
task.driver.network.configure_tenant_networks(task)
|
||||
manager_utils.node_power_action(task, states.POWER_ON)
|
||||
|
||||
|
||||
def _prepare_extra_vars(host_list, variables=None):
|
||||
nodes_var = []
|
||||
for node_uuid, ip, user, extra in host_list:
|
||||
@ -403,49 +375,15 @@ def _get_clean_steps(node, interface=None, override_priorities=None):
|
||||
return steps
|
||||
|
||||
|
||||
# taken from agent driver
|
||||
def _notify_conductor_resume_clean(task):
|
||||
LOG.debug('Sending RPC to conductor to resume cleaning for node %s',
|
||||
task.node.uuid)
|
||||
uuid = task.node.uuid
|
||||
rpc = rpcapi.ConductorAPI()
|
||||
topic = rpc.get_topic_for(task.node)
|
||||
# Need to release the lock to let the conductor take it
|
||||
task.release_resources()
|
||||
rpc.continue_node_clean(task.context, uuid, topic=topic)
|
||||
|
||||
|
||||
def _deploy(task, node_address):
|
||||
"""Internal function for deployment to a node."""
|
||||
notags = ['wait'] if CONF.ansible.use_ramdisk_callback else []
|
||||
node = task.node
|
||||
LOG.debug('IP of node %(node)s is %(ip)s',
|
||||
{'node': node.uuid, 'ip': node_address})
|
||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||
variables = _prepare_variables(task)
|
||||
if iwdi:
|
||||
notags.append('parted')
|
||||
else:
|
||||
variables.update(_parse_partitioning_info(task.node))
|
||||
playbook, user, key = _parse_ansible_driver_info(task.node)
|
||||
node_list = [(node.uuid, node_address, user, node.extra)]
|
||||
extra_vars = _prepare_extra_vars(node_list, variables=variables)
|
||||
|
||||
LOG.debug('Starting deploy on node %s', node.uuid)
|
||||
# any caller should manage exceptions raised from here
|
||||
_run_playbook(playbook, extra_vars, key, notags=notags)
|
||||
LOG.info(_LI('Ansible complete deploy on node %s'), node.uuid)
|
||||
|
||||
LOG.debug('Rebooting node %s to instance', node.uuid)
|
||||
manager_utils.node_set_boot_device(task, 'disk', persistent=True)
|
||||
_reboot_and_finish_deploy(task)
|
||||
|
||||
task.driver.boot.clean_up_ramdisk(task)
|
||||
|
||||
|
||||
class AnsibleDeploy(base.DeployInterface):
|
||||
class AnsibleDeploy(agent_base.HeartbeatMixin, base.DeployInterface):
|
||||
"""Interface for deploy-related actions."""
|
||||
|
||||
def __init__(self):
|
||||
super(AnsibleDeploy, self).__init__()
|
||||
# NOTE(pas-ha) overriding agent creation as we won't be
|
||||
# communicating with it, only processing heartbeats
|
||||
self._client = None
|
||||
|
||||
def get_properties(self):
|
||||
"""Return the properties of the interface."""
|
||||
return COMMON_PROPERTIES
|
||||
@ -469,6 +407,26 @@ class AnsibleDeploy(base.DeployInterface):
|
||||
'parameters were missing') % node.uuid
|
||||
deploy_utils.check_for_missing_params(params, error_msg)
|
||||
|
||||
def _ansible_deploy(self, task, node_address):
|
||||
"""Internal function for deployment to a node."""
|
||||
notags = ['wait'] if CONF.ansible.use_ramdisk_callback else []
|
||||
node = task.node
|
||||
LOG.debug('IP of node %(node)s is %(ip)s',
|
||||
{'node': node.uuid, 'ip': node_address})
|
||||
variables = _prepare_variables(task)
|
||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||
if iwdi:
|
||||
notags.append('parted')
|
||||
else:
|
||||
variables.update(_parse_partitioning_info(task.node))
|
||||
playbook, user, key = _parse_ansible_driver_info(task.node)
|
||||
node_list = [(node.uuid, node_address, user, node.extra)]
|
||||
extra_vars = _prepare_extra_vars(node_list, variables=variables)
|
||||
|
||||
LOG.debug('Starting deploy on node %s', node.uuid)
|
||||
# any caller should manage exceptions raised from here
|
||||
_run_playbook(playbook, extra_vars, key, notags=notags)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def deploy(self, task):
|
||||
"""Perform a deployment to a node."""
|
||||
@ -479,7 +437,7 @@ class AnsibleDeploy(base.DeployInterface):
|
||||
node = task.node
|
||||
ip_addr = _get_node_ip_dhcp(task)
|
||||
try:
|
||||
_deploy(task, ip_addr)
|
||||
self._ansible_deploy(task, ip_addr)
|
||||
except Exception as e:
|
||||
error = _('Deploy failed for node %(node)s: '
|
||||
'Error: %(exc)s') % {'node': node.uuid,
|
||||
@ -488,7 +446,7 @@ class AnsibleDeploy(base.DeployInterface):
|
||||
deploy_utils.set_failed_state(task, error, collect_logs=False)
|
||||
|
||||
else:
|
||||
LOG.info(_LI('Deployment to node %s done'), node.uuid)
|
||||
self.reboot_to_instance(task)
|
||||
return states.DEPLOYDONE
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
@ -631,52 +589,62 @@ class AnsibleDeploy(base.DeployInterface):
|
||||
task.driver.boot.clean_up_ramdisk(task)
|
||||
task.driver.network.remove_cleaning_network(task)
|
||||
|
||||
def heartbeat(self, task, callback_url):
|
||||
"""Method for ansible ramdisk callback."""
|
||||
def continue_deploy(self, task):
|
||||
# NOTE(pas-ha) the lock should be already upgraded in heartbeat,
|
||||
# just setting its purpose for better logging
|
||||
task.upgrade_lock(purpose='deploy')
|
||||
task.process_event('resume')
|
||||
# NOTE(pas-ha) this method is called from heartbeat processing only,
|
||||
# so we are sure we need this particular method, not the general one
|
||||
node_address = _get_node_ip_heartbeat(task)
|
||||
self._ansible_deploy(task, node_address)
|
||||
self.reboot_to_instance(task)
|
||||
|
||||
def reboot_to_instance(self, task):
|
||||
node = task.node
|
||||
address = urlparse.urlparse(callback_url).netloc.split(':')[0]
|
||||
LOG.info(_LI('Ansible complete deploy on node %s'), node.uuid)
|
||||
|
||||
if node.maintenance:
|
||||
# this shouldn't happen often, but skip the rest if it does.
|
||||
LOG.debug('Heartbeat from node %(node)s in maintenance mode; '
|
||||
'not taking any action.', {'node': node.uuid})
|
||||
elif node.provision_state == states.DEPLOYWAIT:
|
||||
LOG.debug('Heartbeat from %(node)s.', {'node': node.uuid})
|
||||
task.upgrade_lock(purpose='deploy')
|
||||
node = task.node
|
||||
task.process_event('resume')
|
||||
LOG.debug('Rebooting node %s to instance', node.uuid)
|
||||
manager_utils.node_set_boot_device(task, 'disk', persistent=True)
|
||||
self.reboot_and_finish_deploy(task)
|
||||
task.driver.boot.clean_up_ramdisk(task)
|
||||
|
||||
def reboot_and_finish_deploy(self, task):
|
||||
wait = CONF.ansible.post_deploy_get_power_state_retry_interval * 1000
|
||||
attempts = CONF.ansible.post_deploy_get_power_state_retries + 1
|
||||
|
||||
@retrying.retry(
|
||||
stop_max_attempt_number=attempts,
|
||||
retry_on_result=lambda state: state != states.POWER_OFF,
|
||||
wait_fixed=wait
|
||||
)
|
||||
def _wait_until_powered_off(task):
|
||||
return task.driver.power.get_power_state(task)
|
||||
|
||||
node = task.node
|
||||
try:
|
||||
try:
|
||||
_deploy(task, address)
|
||||
_wait_until_powered_off(task)
|
||||
except Exception as e:
|
||||
error = _('Deploy failed for node %(node)s: '
|
||||
'Error: %(exc)s') % {'node': node.uuid,
|
||||
'exc': six.text_type(e)}
|
||||
LOG.exception(error)
|
||||
deploy_utils.set_failed_state(task, error, collect_logs=False)
|
||||
LOG.warning(_LW('Failed to soft power off node %(node_uuid)s '
|
||||
'in at least %(timeout)d seconds. '
|
||||
'Error: %(error)s'),
|
||||
{'node_uuid': task.node.uuid,
|
||||
'timeout': (wait * (attempts - 1)) / 1000,
|
||||
'error': e})
|
||||
# NOTE(pas-ha) flush is a part of deploy playbook
|
||||
# so if it finished successfully we can safely
|
||||
# power off the node out-of-band
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
|
||||
else:
|
||||
LOG.info(_LI('Deployment to node %s done'), node.uuid)
|
||||
task.process_event('done')
|
||||
task.driver.network.remove_provisioning_network(task)
|
||||
task.driver.network.configure_tenant_networks(task)
|
||||
manager_utils.node_power_action(task, states.POWER_ON)
|
||||
except Exception as e:
|
||||
msg = (_('Error rebooting node %(node)s after deploy. '
|
||||
'Error: %(error)s') %
|
||||
{'node': node.uuid, 'error': e})
|
||||
agent_base.log_and_raise_deployment_error(task, msg)
|
||||
|
||||
elif node.provision_state == states.CLEANWAIT:
|
||||
LOG.debug('Node %s just booted to start cleaning.',
|
||||
node.uuid)
|
||||
task.upgrade_lock(purpose='clean')
|
||||
node = task.node
|
||||
driver_internal_info = node.driver_internal_info
|
||||
driver_internal_info['agent_url'] = callback_url
|
||||
node.driver_internal_info = driver_internal_info
|
||||
node.save()
|
||||
try:
|
||||
_notify_conductor_resume_clean(task)
|
||||
except Exception as e:
|
||||
error = _('cleaning failed for node %(node)s: '
|
||||
'Error: %(exc)s') % {'node': node.uuid,
|
||||
'exc': six.text_type(e)}
|
||||
LOG.exception(error)
|
||||
manager_utils.cleaning_error_handler(task, error)
|
||||
|
||||
else:
|
||||
LOG.warning(_LW('Call back from %(node)s in invalid provision '
|
||||
'state %(state)s'),
|
||||
{'node': node.uuid, 'state': node.provision_state})
|
||||
task.process_event('done')
|
||||
LOG.info(_LI('Deployment to node %s done'), task.node.uuid)
|
||||
|
@ -15,3 +15,6 @@
|
||||
- include: grub.yaml
|
||||
tags:
|
||||
- parted
|
||||
|
||||
- name: flush
|
||||
command: sync
|
||||
|
@ -10,9 +10,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from ironic.common import dhcp_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
@ -27,13 +24,11 @@ from ironic.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.objects import utils as object_utils
|
||||
from ironic_lib import utils as irlib_utils
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_concurrency import processutils
|
||||
import six
|
||||
|
||||
from ironic_staging_drivers.ansible import deploy as ansible_deploy
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
INSTANCE_INFO = {
|
||||
'image_source': 'fake-image',
|
||||
@ -146,70 +141,76 @@ class TestAnsibleMethods(db_base.DbTestCase):
|
||||
ip_dhcp_mock.assert_called_once_with(task)
|
||||
self.assertEqual('127.0.0.1', res)
|
||||
|
||||
@mock.patch.object(utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
return_value=states.POWER_OFF)
|
||||
def test__reboot_and_finish_deploy(self, get_pow_state_mock,
|
||||
power_action_mock):
|
||||
self.config(group='ansible',
|
||||
post_deploy_get_power_state_retry_interval=0)
|
||||
@mock.patch.object(com_utils, 'execute', return_value=('out', 'err'),
|
||||
autospec=True)
|
||||
def test__run_playbook(self, execute_mock):
|
||||
self.config(group='ansible', playbooks_path='/path/to/playbooks')
|
||||
self.config(group='ansible', config_file_path='/path/to/config')
|
||||
self.config(group='ansible', verbosity=3)
|
||||
self.config(group='ansible', ansible_extra_args='--timeout=100')
|
||||
extra_vars = {'foo': 'bar'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
ansible_deploy._reboot_and_finish_deploy(task)
|
||||
get_pow_state_mock.assert_called_once_with(task)
|
||||
power_action_mock.assert_called_once_with(task, states.POWER_ON)
|
||||
ansible_deploy._run_playbook('deploy', extra_vars, '/path/to/key',
|
||||
tags=['spam'], notags=['ham'])
|
||||
|
||||
@mock.patch.object(utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
return_value=states.POWER_ON)
|
||||
def test__reboot_and_finish_deploy_retry(self, get_pow_state_mock,
|
||||
power_action_mock):
|
||||
self.config(group='ansible',
|
||||
post_deploy_get_power_state_retry_interval=0)
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
ansible_deploy._reboot_and_finish_deploy(task)
|
||||
get_pow_state_mock.assert_called_with(task)
|
||||
self.assertEqual(
|
||||
CONF.ansible.post_deploy_get_power_state_retries + 1,
|
||||
len(get_pow_state_mock.mock_calls))
|
||||
expected_power_calls = [((task, states.POWER_OFF),),
|
||||
((task, states.POWER_ON),)]
|
||||
self.assertEqual(expected_power_calls,
|
||||
power_action_mock.call_args_list)
|
||||
execute_mock.assert_called_once_with(
|
||||
'env', 'ANSIBLE_CONFIG=/path/to/config',
|
||||
'ansible-playbook', '/path/to/playbooks/deploy', '-i',
|
||||
ansible_deploy.INVENTORY_FILE, '-e', '{"foo": "bar"}',
|
||||
'--tags=spam', '--skip-tags=ham',
|
||||
'--private-key=/path/to/key', '-vvv', '--timeout=100')
|
||||
|
||||
@mock.patch.object(com_utils, 'execute', return_value=('out', 'err'),
|
||||
autospec=True)
|
||||
@mock.patch.object(os.path, 'join', return_value='/path/to/playbook',
|
||||
autospec=True)
|
||||
def test__run_playbook(self, path_join_mock, execute_mock):
|
||||
extra_vars = {"ironic_nodes": [{"name": self.node["uuid"],
|
||||
"ip": "127.0.0.1", "user": "test"}]}
|
||||
def test__run_playbook_default_verbosity_nodebug(self, execute_mock):
|
||||
self.config(group='ansible', playbooks_path='/path/to/playbooks')
|
||||
self.config(group='ansible', config_file_path='/path/to/config')
|
||||
self.config(debug=False)
|
||||
extra_vars = {'foo': 'bar'}
|
||||
|
||||
ansible_deploy._run_playbook('deploy', extra_vars, '/path/to/key')
|
||||
|
||||
execute_mock.assert_called_once_with(
|
||||
'env', 'ANSIBLE_CONFIG=%s' % CONF.ansible.config_file_path,
|
||||
'ansible-playbook', '/path/to/playbook', '-i',
|
||||
ansible_deploy.INVENTORY_FILE, '-e', json.dumps(extra_vars),
|
||||
'--private-key=/path/to/key', '-vvvv')
|
||||
'env', 'ANSIBLE_CONFIG=/path/to/config',
|
||||
'ansible-playbook', '/path/to/playbooks/deploy', '-i',
|
||||
ansible_deploy.INVENTORY_FILE, '-e', '{"foo": "bar"}',
|
||||
'--private-key=/path/to/key')
|
||||
|
||||
@mock.patch.object(com_utils, 'execute', return_value=('out', 'err'),
|
||||
autospec=True)
|
||||
@mock.patch.object(os.path, 'join', return_value='/path/to/playbook',
|
||||
autospec=True)
|
||||
def test__run_playbook_tags(self, path_join_mock, execute_mock):
|
||||
extra_vars = {"ironic_nodes": [{"name": self.node["uuid"],
|
||||
"ip": "127.0.0.1", "user": "test"}]}
|
||||
def test__run_playbook_default_verbosity_debug(self, execute_mock):
|
||||
self.config(group='ansible', playbooks_path='/path/to/playbooks')
|
||||
self.config(group='ansible', config_file_path='/path/to/config')
|
||||
self.config(debug=True)
|
||||
extra_vars = {'foo': 'bar'}
|
||||
|
||||
ansible_deploy._run_playbook('deploy', extra_vars, '/path/to/key',
|
||||
tags=['wait'])
|
||||
ansible_deploy._run_playbook('deploy', extra_vars, '/path/to/key')
|
||||
|
||||
execute_mock.assert_called_once_with(
|
||||
'env', 'ANSIBLE_CONFIG=%s' % CONF.ansible.config_file_path,
|
||||
'ansible-playbook', '/path/to/playbook', '-i',
|
||||
ansible_deploy.INVENTORY_FILE, '-e', json.dumps(extra_vars),
|
||||
'--tags=wait', '--private-key=/path/to/key', '-vvvv')
|
||||
'env', 'ANSIBLE_CONFIG=/path/to/config',
|
||||
'ansible-playbook', '/path/to/playbooks/deploy', '-i',
|
||||
ansible_deploy.INVENTORY_FILE, '-e', '{"foo": "bar"}',
|
||||
'--private-key=/path/to/key', '-vvvv')
|
||||
|
||||
@mock.patch.object(com_utils, 'execute',
|
||||
side_effect=processutils.ProcessExecutionError(
|
||||
description='VIKINGS!'),
|
||||
autospec=True)
|
||||
def test__run_playbook_fail(self, execute_mock):
|
||||
self.config(group='ansible', playbooks_path='/path/to/playbooks')
|
||||
self.config(group='ansible', config_file_path='/path/to/config')
|
||||
self.config(debug=False)
|
||||
extra_vars = {'foo': 'bar'}
|
||||
|
||||
exc = self.assertRaises(exception.InstanceDeployFailure,
|
||||
ansible_deploy._run_playbook,
|
||||
'deploy', extra_vars, '/path/to/key')
|
||||
self.assertIn('VIKINGS!', six.text_type(exc))
|
||||
execute_mock.assert_called_once_with(
|
||||
'env', 'ANSIBLE_CONFIG=/path/to/config',
|
||||
'ansible-playbook', '/path/to/playbooks/deploy', '-i',
|
||||
ansible_deploy.INVENTORY_FILE, '-e', '{"foo": "bar"}',
|
||||
'--private-key=/path/to/key')
|
||||
|
||||
def test__parse_partitioning_info(self):
|
||||
expected_info = {
|
||||
@ -258,113 +259,102 @@ class TestAnsibleMethods(db_base.DbTestCase):
|
||||
'ephemeral_format': 'ext4',
|
||||
'preserve_ephemeral': 'yes'
|
||||
}
|
||||
|
||||
i_info = ansible_deploy._parse_partitioning_info(self.node)
|
||||
|
||||
self.assertEqual(expected_info, i_info)
|
||||
|
||||
@mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk')
|
||||
@mock.patch.object(ansible_deploy, '_reboot_and_finish_deploy',
|
||||
autospec=True)
|
||||
@mock.patch.object(utils, 'node_set_boot_device', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_run_playbook', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_prepare_extra_vars', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_parse_ansible_driver_info',
|
||||
return_value=('test_pl', 'test_u', 'test_k'),
|
||||
autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_parse_partitioning_info',
|
||||
autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_prepare_variables', autospec=True)
|
||||
def test__deploy(self, prepare_vars_mock, parse_part_info_mock,
|
||||
parse_dr_info_mock, prepare_extra_mock,
|
||||
run_playbook_mock, set_boot_device_mock,
|
||||
finish_deploy_mock, clean_ramdisk_mock):
|
||||
ironic_nodes = {
|
||||
'ironic_nodes': [(self.node['uuid'],
|
||||
DRIVER_INTERNAL_INFO['ansible_cleaning_ip'],
|
||||
'test_u')]}
|
||||
prepare_extra_mock.return_value = ironic_nodes
|
||||
_vars = {
|
||||
'url': 'image_url',
|
||||
'checksum': 'aa'}
|
||||
prepare_vars_mock.return_value = _vars
|
||||
|
||||
driver_internal_info = dict(DRIVER_INTERNAL_INFO)
|
||||
driver_internal_info['is_whole_disk_image'] = False
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.extra = {'ham': 'spam'}
|
||||
self.node.save()
|
||||
@mock.patch.object(ansible_deploy.images, 'download_size', autospec=True)
|
||||
def test__calculate_memory_req(self, image_mock):
|
||||
self.config(group='ansible', extra_memory=1)
|
||||
image_mock.return_value = 2000000 # < 2MiB
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
ansible_deploy._deploy(task, '127.0.0.1')
|
||||
self.assertEqual(2, ansible_deploy._calculate_memory_req(task))
|
||||
image_mock.assert_called_once_with(task.context, 'fake-image')
|
||||
|
||||
prepare_vars_mock.assert_called_once_with(task)
|
||||
parse_part_info_mock.assert_called_once_with(task.node)
|
||||
parse_dr_info_mock.assert_called_once_with(task.node)
|
||||
prepare_extra_mock.assert_called_once_with(
|
||||
[(self.node['uuid'], '127.0.0.1', 'test_u', {'ham': 'spam'})],
|
||||
variables=_vars)
|
||||
run_playbook_mock.assert_called_once_with(
|
||||
'test_pl', {'ironic_nodes': [
|
||||
(self.node['uuid'],
|
||||
DRIVER_INTERNAL_INFO['ansible_cleaning_ip'],
|
||||
'test_u')]}, 'test_k',
|
||||
notags=['wait'])
|
||||
set_boot_device_mock.assert_called_once_with(
|
||||
task, 'disk', persistent=True)
|
||||
finish_deploy_mock.assert_called_once_with(task)
|
||||
clean_ramdisk_mock.assert_called_once_with(task)
|
||||
def test__get_configdrive_path(self):
|
||||
self.config(tempdir='/path/to/tmpdir')
|
||||
self.assertEqual('/path/to/tmpdir/spam.cndrive',
|
||||
ansible_deploy._get_configdrive_path('spam'))
|
||||
|
||||
@mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk')
|
||||
@mock.patch.object(ansible_deploy, '_reboot_and_finish_deploy',
|
||||
autospec=True)
|
||||
@mock.patch.object(utils, 'node_set_boot_device', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_run_playbook', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_prepare_extra_vars', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_parse_ansible_driver_info',
|
||||
return_value=('test_pl', 'test_u', 'test_k'),
|
||||
autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_parse_partitioning_info',
|
||||
autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_prepare_variables', autospec=True)
|
||||
def test__deploy_iwdi(self, prepare_vars_mock, parse_part_info_mock,
|
||||
parse_dr_info_mock, prepare_extra_mock,
|
||||
run_playbook_mock, set_boot_device_mock,
|
||||
finish_deploy_mock, clean_ramdisk_mock):
|
||||
ironic_nodes = {
|
||||
'ironic_nodes': [(self.node['uuid'],
|
||||
DRIVER_INTERNAL_INFO['ansible_cleaning_ip'],
|
||||
'test_u')]}
|
||||
prepare_extra_mock.return_value = ironic_nodes
|
||||
_vars = {
|
||||
'url': 'image_url',
|
||||
'checksum': 'aa'}
|
||||
prepare_vars_mock.return_value = _vars
|
||||
driver_internal_info = self.node.driver_internal_info
|
||||
driver_internal_info['is_whole_disk_image'] = True
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.extra = {'ham': 'spam'}
|
||||
self.node.save()
|
||||
def test__prepare_extra_vars(self):
|
||||
host_list = [('fake-uuid', '1.2.3.4', 'spam', 'ham'),
|
||||
('other-uuid', '5.6.7.8', 'eggs', 'vikings')]
|
||||
ansible_vars = {"foo": "bar"}
|
||||
self.assertEqual(
|
||||
{"ironic_nodes": [
|
||||
{"name": "fake-uuid", "ip": '1.2.3.4',
|
||||
"user": "spam", "extra": "ham"},
|
||||
{"name": "other-uuid", "ip": '5.6.7.8',
|
||||
"user": "eggs", "extra": "vikings"}],
|
||||
"foo": "bar"},
|
||||
ansible_deploy._prepare_extra_vars(host_list, ansible_vars))
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_calculate_memory_req', autospec=True,
|
||||
return_value=2000)
|
||||
def test__prepare_variables(self, mem_req_mock):
|
||||
expected = {"image": {"url": "http://image", "mem_req": 2000,
|
||||
"disk_format": "qcow2",
|
||||
"checksum": "md5:checksum"}}
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
ansible_deploy._deploy(task, '127.0.0.1')
|
||||
self.assertEqual(expected,
|
||||
ansible_deploy._prepare_variables(task))
|
||||
|
||||
prepare_vars_mock.assert_called_once_with(task)
|
||||
self.assertFalse(parse_part_info_mock.called)
|
||||
parse_dr_info_mock.assert_called_once_with(task.node)
|
||||
prepare_extra_mock.assert_called_once_with(
|
||||
[(self.node['uuid'], '127.0.0.1', 'test_u', {'ham': 'spam'})],
|
||||
variables=_vars)
|
||||
run_playbook_mock.assert_called_once_with(
|
||||
'test_pl', {'ironic_nodes': [
|
||||
(self.node['uuid'],
|
||||
DRIVER_INTERNAL_INFO['ansible_cleaning_ip'],
|
||||
'test_u')]}, 'test_k',
|
||||
notags=['wait', 'parted'])
|
||||
set_boot_device_mock.assert_called_once_with(
|
||||
task, 'disk', persistent=True)
|
||||
finish_deploy_mock.assert_called_once_with(task)
|
||||
clean_ramdisk_mock.assert_called_once_with(task)
|
||||
@mock.patch.object(ansible_deploy, '_calculate_memory_req', autospec=True,
|
||||
return_value=2000)
|
||||
def test__prepare_variables_noglance(self, mem_req_mock):
|
||||
i_info = self.node.instance_info
|
||||
i_info['image_checksum'] = 'sha256:checksum'
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
expected = {"image": {"url": "http://image", "mem_req": 2000,
|
||||
"disk_format": "qcow2",
|
||||
"checksum": "sha256:checksum"}}
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertEqual(expected,
|
||||
ansible_deploy._prepare_variables(task))
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_calculate_memory_req', autospec=True,
|
||||
return_value=2000)
|
||||
def test__prepare_variables_configdrive_url(self, mem_req_mock):
|
||||
i_info = self.node.instance_info
|
||||
i_info['configdrive'] = 'http://configdrive_url'
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
expected = {"image": {"url": "http://image", "mem_req": 2000,
|
||||
"disk_format": "qcow2",
|
||||
"checksum": "md5:checksum"},
|
||||
'configdrive': {'type': 'url',
|
||||
'location': 'http://configdrive_url'}}
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertEqual(expected,
|
||||
ansible_deploy._prepare_variables(task))
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_calculate_memory_req', autospec=True,
|
||||
return_value=2000)
|
||||
def test__prepare_variables_configdrive_file(self, mem_req_mock):
|
||||
i_info = self.node.instance_info
|
||||
i_info['configdrive'] = 'fake-content'
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
self.config(tempdir='/path/to/tmpfiles')
|
||||
expected = {"image": {"url": "http://image", "mem_req": 2000,
|
||||
"disk_format": "qcow2",
|
||||
"checksum": "md5:checksum"},
|
||||
'configdrive': {'type': 'file',
|
||||
'location': '/path/to/tmpfiles/%s.cndrive'
|
||||
% self.node.uuid}}
|
||||
with mock.patch.object(ansible_deploy, 'open', mock.mock_open(),
|
||||
create=True) as open_mock:
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertEqual(expected,
|
||||
ansible_deploy._prepare_variables(task))
|
||||
open_mock.assert_has_calls((
|
||||
mock.call('/path/to/tmpfiles/%s.cndrive' % self.node.uuid,
|
||||
'w'),
|
||||
mock.call().__enter__(),
|
||||
mock.call().write('fake-content'),
|
||||
mock.call().__exit__(None, None, None)))
|
||||
|
||||
def test__validate_clean_steps(self):
|
||||
steps = [{"interface": "deploy",
|
||||
@ -453,8 +443,9 @@ class TestAnsibleDeploy(db_base.DbTestCase):
|
||||
self.node = object_utils.create_test_node(self.context, **node)
|
||||
|
||||
def test_get_properties(self):
|
||||
self.assertEqual(ansible_deploy.COMMON_PROPERTIES,
|
||||
self.driver.get_properties())
|
||||
self.assertEqual(
|
||||
set(ansible_deploy.COMMON_PROPERTIES),
|
||||
set(self.driver.get_properties()))
|
||||
|
||||
@mock.patch.object(deploy_utils, 'check_for_missing_params',
|
||||
autospec=True)
|
||||
@ -495,19 +486,40 @@ class TestAnsibleDeploy(db_base.DbTestCase):
|
||||
self.assertEqual(driver_return, states.DEPLOYWAIT)
|
||||
power_mock.assert_called_once_with(task, states.REBOOT)
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_deploy', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_get_node_ip_dhcp',
|
||||
return_value='127.0.0.1', autospec=True)
|
||||
@mock.patch.object(utils, 'node_power_action', autospec=True)
|
||||
def test_deploy_done(self, power_mock, get_ip_mock, deploy_mock):
|
||||
def test_deploy_no_callback(self, power_mock, get_ip_mock):
|
||||
self.config(group='ansible', use_ramdisk_callback=False)
|
||||
with task_manager.acquire(
|
||||
self.context, self.node['uuid'], shared=False) as task:
|
||||
driver_return = self.driver.deploy(task)
|
||||
self.assertEqual(driver_return, states.DEPLOYDONE)
|
||||
power_mock.assert_called_once_with(task, states.REBOOT)
|
||||
get_ip_mock.assert_called_once_with(task)
|
||||
deploy_mock.assert_called_once_with(task, '127.0.0.1')
|
||||
with mock.patch.multiple(self.driver,
|
||||
_ansible_deploy=mock.DEFAULT,
|
||||
reboot_to_instance=mock.DEFAULT) as moks:
|
||||
with task_manager.acquire(
|
||||
self.context, self.node['uuid'], shared=False) as task:
|
||||
driver_return = self.driver.deploy(task)
|
||||
self.assertEqual(driver_return, states.DEPLOYDONE)
|
||||
power_mock.assert_called_once_with(task, states.REBOOT)
|
||||
get_ip_mock.assert_called_once_with(task)
|
||||
moks['_ansible_deploy'].assert_called_once_with(task,
|
||||
'127.0.0.1')
|
||||
moks['reboot_to_instance'].assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_get_node_ip_dhcp',
|
||||
return_value='127.0.0.1', autospec=True)
|
||||
@mock.patch.object(utils, 'node_power_action', autospec=True)
|
||||
def test_deploy_no_callback_fail(self, power_mock, get_ip_mock, fail_mock):
|
||||
self.config(group='ansible', use_ramdisk_callback=False)
|
||||
with mock.patch.object(self.driver, '_ansible_deploy',
|
||||
side_effect=ansible_deploy.PlaybookNotFound(
|
||||
'deploy')):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
self.driver.deploy(task)
|
||||
self.driver._ansible_deploy.assert_called_once_with(
|
||||
task, '127.0.0.1')
|
||||
fail_mock.assert_called_once_with(task, mock.ANY,
|
||||
collect_logs=False)
|
||||
|
||||
@mock.patch.object(utils, 'node_power_action', autospec=True)
|
||||
def test_tear_down(self, power_mock):
|
||||
@ -739,101 +751,152 @@ class TestAnsibleDeploy(db_base.DbTestCase):
|
||||
(task.driver.network.remove_cleaning_network
|
||||
.assert_called_once_with(task))
|
||||
|
||||
@mock.patch.object(ansible_deploy, 'LOG', autospec=True)
|
||||
def test_heartbeat_not_wait_state(self, log_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.driver.heartbeat(task, 'http://127.0.0.1')
|
||||
log_mock.warning.assert_called_once_with(
|
||||
mock.ANY, {'node': task.node['uuid'],
|
||||
'state': task.node['provision_state']})
|
||||
|
||||
@mock.patch.object(ansible_deploy, 'LOG', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_deploy', autospec=True)
|
||||
def test_heartbeat_deploy_wait(self, deploy_mock, log_mock):
|
||||
self.node['provision_state'] = states.DEPLOYWAIT
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.process_event = mock.Mock()
|
||||
|
||||
self.driver.heartbeat(task, 'http://127.0.0.1')
|
||||
|
||||
deploy_mock.assert_called_once_with(task, '127.0.0.1')
|
||||
log_mock.info.assert_called_once_with(mock.ANY, task.node['uuid'])
|
||||
self.assertEqual([mock.call('resume'), mock.call('done')],
|
||||
task.process_event.mock_calls)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, 'LOG', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_deploy',
|
||||
side_effect=Exception('Boo'), autospec=True)
|
||||
def test_heartbeat_deploy_wait_fail(self, deploy_mock, log_mock,
|
||||
set_fail_state_mock):
|
||||
self.node['provision_state'] = states.DEPLOYWAIT
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.process_event = mock.Mock()
|
||||
|
||||
self.driver.heartbeat(task, 'http://127.0.0.1')
|
||||
|
||||
deploy_mock.assert_called_once_with(task, '127.0.0.1')
|
||||
log_mock.exception.assert_called_once_with(mock.ANY)
|
||||
self.assertEqual([mock.call('resume')],
|
||||
task.process_event.mock_calls)
|
||||
set_fail_state_mock.assert_called_once_with(task, mock.ANY,
|
||||
collect_logs=False)
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_notify_conductor_resume_clean',
|
||||
@mock.patch.object(ansible_deploy, '_run_playbook', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_prepare_extra_vars', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_parse_ansible_driver_info',
|
||||
return_value=('test_pl', 'test_u', 'test_k'),
|
||||
autospec=True)
|
||||
def test_heartbeat_clean_wait(self, notify_resume_clean_mock):
|
||||
self.node['provision_state'] = states.CLEANWAIT
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.process_event = mock.Mock()
|
||||
|
||||
self.driver.heartbeat(task, 'http://127.0.0.1')
|
||||
|
||||
notify_resume_clean_mock.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_notify_conductor_resume_clean',
|
||||
side_effect=Exception('Boo'), autospec=True)
|
||||
@mock.patch.object(utils, 'cleaning_error_handler', autospec=True)
|
||||
def test_heartbeat_clean_wait_fail(self, cleaning_error_mock,
|
||||
notify_resume_clean_mock):
|
||||
self.node['provision_state'] = states.CLEANWAIT
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.process_event = mock.Mock()
|
||||
|
||||
self.driver.heartbeat(task, 'http://127.0.0.1')
|
||||
|
||||
notify_resume_clean_mock.assert_called_once_with(task)
|
||||
cleaning_error_mock.assert_called_once_with(task, mock.ANY)
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_notify_conductor_resume_clean',
|
||||
@mock.patch.object(ansible_deploy, '_parse_partitioning_info',
|
||||
autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_deploy', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, 'LOG', autospec=True)
|
||||
def test_heartbeat_maintenance(self, log_mock, deploy_mock,
|
||||
notify_clean_resume_mock):
|
||||
self.node['maintenance'] = True
|
||||
@mock.patch.object(ansible_deploy, '_prepare_variables', autospec=True)
|
||||
def test__ansible_deploy(self, prepare_vars_mock, parse_part_info_mock,
|
||||
parse_dr_info_mock, prepare_extra_mock,
|
||||
run_playbook_mock):
|
||||
ironic_nodes = {
|
||||
'ironic_nodes': [(self.node['uuid'],
|
||||
DRIVER_INTERNAL_INFO['ansible_cleaning_ip'],
|
||||
'test_u')]}
|
||||
prepare_extra_mock.return_value = ironic_nodes
|
||||
_vars = {
|
||||
'url': 'image_url',
|
||||
'checksum': 'aa'}
|
||||
prepare_vars_mock.return_value = _vars
|
||||
|
||||
driver_internal_info = dict(DRIVER_INTERNAL_INFO)
|
||||
driver_internal_info['is_whole_disk_image'] = False
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.extra = {'ham': 'spam'}
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.driver._ansible_deploy(task, '127.0.0.1')
|
||||
|
||||
prepare_vars_mock.assert_called_once_with(task)
|
||||
parse_part_info_mock.assert_called_once_with(task.node)
|
||||
parse_dr_info_mock.assert_called_once_with(task.node)
|
||||
prepare_extra_mock.assert_called_once_with(
|
||||
[(self.node['uuid'], '127.0.0.1', 'test_u', {'ham': 'spam'})],
|
||||
variables=_vars)
|
||||
run_playbook_mock.assert_called_once_with(
|
||||
'test_pl', {'ironic_nodes': [
|
||||
(self.node['uuid'],
|
||||
DRIVER_INTERNAL_INFO['ansible_cleaning_ip'],
|
||||
'test_u')]}, 'test_k',
|
||||
notags=['wait'])
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_run_playbook', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_prepare_extra_vars', autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_parse_ansible_driver_info',
|
||||
return_value=('test_pl', 'test_u', 'test_k'),
|
||||
autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_parse_partitioning_info',
|
||||
autospec=True)
|
||||
@mock.patch.object(ansible_deploy, '_prepare_variables', autospec=True)
|
||||
def test__ansible_deploy_iwdi(self, prepare_vars_mock,
|
||||
parse_part_info_mock, parse_dr_info_mock,
|
||||
prepare_extra_mock, run_playbook_mock):
|
||||
ironic_nodes = {
|
||||
'ironic_nodes': [(self.node['uuid'],
|
||||
DRIVER_INTERNAL_INFO['ansible_cleaning_ip'],
|
||||
'test_u')]}
|
||||
prepare_extra_mock.return_value = ironic_nodes
|
||||
_vars = {
|
||||
'url': 'image_url',
|
||||
'checksum': 'aa'}
|
||||
prepare_vars_mock.return_value = _vars
|
||||
driver_internal_info = self.node.driver_internal_info
|
||||
driver_internal_info['is_whole_disk_image'] = True
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.extra = {'ham': 'spam'}
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.driver._ansible_deploy(task, '127.0.0.1')
|
||||
|
||||
prepare_vars_mock.assert_called_once_with(task)
|
||||
self.assertFalse(parse_part_info_mock.called)
|
||||
parse_dr_info_mock.assert_called_once_with(task.node)
|
||||
prepare_extra_mock.assert_called_once_with(
|
||||
[(self.node['uuid'], '127.0.0.1', 'test_u', {'ham': 'spam'})],
|
||||
variables=_vars)
|
||||
run_playbook_mock.assert_called_once_with(
|
||||
'test_pl', {'ironic_nodes': [
|
||||
(self.node['uuid'],
|
||||
DRIVER_INTERNAL_INFO['ansible_cleaning_ip'],
|
||||
'test_u')]}, 'test_k',
|
||||
notags=['wait', 'parted'])
|
||||
|
||||
@mock.patch.object(utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||
return_value=states.POWER_ON)
|
||||
def test_reboot_and_finish_deploy_soft_poweroff_retry(self,
|
||||
get_pow_state_mock,
|
||||
power_action_mock):
|
||||
self.config(group='ansible',
|
||||
post_deploy_get_power_state_retry_interval=0)
|
||||
self.config(group='ansible',
|
||||
post_deploy_get_power_state_retries=1)
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
di_info = self.node.driver_internal_info
|
||||
di_info['agent_url'] = 'http://127.0.0.1'
|
||||
self.node.driver_internal_info = di_info
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
with mock.patch.object(task.driver, 'network') as net_mock:
|
||||
self.driver.reboot_and_finish_deploy(task)
|
||||
net_mock.remove_provisioning_network.assert_called_once_with(
|
||||
task)
|
||||
net_mock.configure_tenant_networks.assert_called_once_with(
|
||||
task)
|
||||
power_action_mock.assert_has_calls(
|
||||
[mock.call(task, states.POWER_OFF),
|
||||
mock.call(task, states.POWER_ON)])
|
||||
get_pow_state_mock.assert_called_with(task)
|
||||
self.assertEqual(2, len(get_pow_state_mock.mock_calls))
|
||||
expected_power_calls = [((task, states.POWER_OFF),),
|
||||
((task, states.POWER_ON),)]
|
||||
self.assertEqual(expected_power_calls,
|
||||
power_action_mock.call_args_list)
|
||||
|
||||
@mock.patch.object(ansible_deploy, '_get_node_ip_heartbeat', autospec=True,
|
||||
return_value='1.2.3.4')
|
||||
def test_continue_deploy(self, getip_mock):
|
||||
self.node.provision_state = states.DEPLOYWAIT
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.driver.heartbeat(task, 'http://127.0.0.1')
|
||||
with mock.patch.multiple(self.driver, autospec=True,
|
||||
_ansible_deploy=mock.DEFAULT,
|
||||
reboot_to_instance=mock.DEFAULT):
|
||||
self.driver.continue_deploy(task)
|
||||
getip_mock.assert_called_once_with(task)
|
||||
self.driver._ansible_deploy.assert_called_once_with(
|
||||
task, '1.2.3.4')
|
||||
self.driver.reboot_to_instance.assert_called_once_with(task)
|
||||
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
|
||||
self.assertEqual(states.DEPLOYING, task.node.provision_state)
|
||||
|
||||
self.node['provision_state'] = states.CLEANWAIT
|
||||
self.node.save()
|
||||
@mock.patch.object(utils, 'node_set_boot_device', autospec=True)
|
||||
def test_reboot_to_instance(self, bootdev_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.driver.heartbeat(task, 'http://127.0.0.1')
|
||||
|
||||
self.node['provision_state'] = states.DEPLOYWAIT
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.driver.heartbeat(task, 'http://127.0.0.1')
|
||||
|
||||
self.assertFalse(log_mock.warning.called)
|
||||
self.assertFalse(deploy_mock.called)
|
||||
self.assertFalse(notify_clean_resume_mock.called)
|
||||
with mock.patch.object(self.driver, 'reboot_and_finish_deploy',
|
||||
autospec=True):
|
||||
task.driver.boot = mock.Mock()
|
||||
self.driver.reboot_to_instance(task)
|
||||
bootdev_mock.assert_called_once_with(task, 'disk',
|
||||
persistent=True)
|
||||
self.driver.reboot_and_finish_deploy.assert_called_once_with(
|
||||
task)
|
||||
task.driver.boot.clean_up_ramdisk.assert_called_once_with(
|
||||
task)
|
||||
|
@ -1,3 +1,3 @@
|
||||
---
|
||||
upgrade:
|
||||
- Ansible-deploy driver requires ironic of version >= 7.0.0 (Ocata release).
|
||||
- Ansible-deploy driver requires ironic of version >= 8.0.0 (Pike release)
|
||||
|
Loading…
Reference in New Issue
Block a user