Merge "Cache agent clean steps on node"
This commit is contained in:
commit
e6571e1a7e
@ -182,6 +182,10 @@ class BaseInterface(object):
|
||||
|
||||
:param task: A TaskManager object, useful for interfaces overriding
|
||||
this function
|
||||
:raises NodeCleaningFailure: if there is a problem getting the steps
|
||||
from the driver. For example, when a node (using an agent driver)
|
||||
has just been enrolled and the agent isn't alive yet to be queried
|
||||
for the available clean steps.
|
||||
:returns: A list of clean step dictionaries
|
||||
"""
|
||||
return self.clean_steps
|
||||
|
@ -359,7 +359,9 @@ class AgentDeploy(base.DeployInterface):
|
||||
"""Get the list of clean steps from the agent.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
|
||||
:raises NodeCleaningFailure: if the clean steps are not yet
|
||||
available (cached), for example, when a node has just been
|
||||
enrolled and has not been cleaned yet.
|
||||
:returns: A list of clean step dictionaries
|
||||
"""
|
||||
new_priorities = {
|
||||
|
@ -16,12 +16,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import collections
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import timeutils
|
||||
import retrying
|
||||
|
||||
from ironic.common import boot_devices
|
||||
@ -215,6 +216,62 @@ class BaseAgentVendor(base.VendorInterface):
|
||||
task.release_resources()
|
||||
rpc.continue_node_clean(task.context, uuid, topic=topic)
|
||||
|
||||
def _refresh_clean_steps(self, task):
|
||||
"""Refresh the node's cached clean steps from the booted agent.
|
||||
|
||||
Gets the node's clean steps from the booted agent and caches them.
|
||||
The steps are cached to make get_clean_steps() calls synchronous, and
|
||||
should be refreshed as soon as the agent boots to start cleaning or
|
||||
if cleaning is restarted because of a cleaning version mismatch.
|
||||
|
||||
:param task: a TaskManager instance
|
||||
:raises: NodeCleaningFailure if the agent returns invalid results
|
||||
"""
|
||||
node = task.node
|
||||
previous_steps = node.driver_internal_info.get(
|
||||
'agent_cached_clean_steps')
|
||||
LOG.debug('Refreshing agent clean step cache for node %(node)s. '
|
||||
'Previously cached steps: %(steps)s',
|
||||
{'node': node.uuid, 'steps': previous_steps})
|
||||
|
||||
agent_result = self._client.get_clean_steps(node, task.ports).get(
|
||||
'command_result', {})
|
||||
missing = set(['clean_steps', 'hardware_manager_version']).difference(
|
||||
agent_result)
|
||||
if missing:
|
||||
raise exception.NodeCleaningFailure(_(
|
||||
'agent get_clean_steps for node %(node)s returned an invalid '
|
||||
'result. Keys: %(keys)s are missing from result: %(result)s.')
|
||||
% ({'node': node.uuid, 'keys': missing,
|
||||
'result': agent_result}))
|
||||
|
||||
# agent_result['clean_steps'] looks like
|
||||
# {'HardwareManager': [{step1},{steps2}...], ...}
|
||||
steps = collections.defaultdict(list)
|
||||
for step_list in agent_result['clean_steps'].values():
|
||||
for step in step_list:
|
||||
missing = set(['interface', 'step', 'priority']).difference(
|
||||
step)
|
||||
if missing:
|
||||
raise exception.NodeCleaningFailure(_(
|
||||
'agent get_clean_steps for node %(node)s returned an '
|
||||
'invalid clean step. Keys: %(keys)s are missing from '
|
||||
'step: %(step)s.') % ({'node': node.uuid,
|
||||
'keys': missing, 'step': step}))
|
||||
|
||||
steps[step['interface']].append(step)
|
||||
|
||||
# Save hardware manager version, steps, and date
|
||||
info = node.driver_internal_info
|
||||
info['hardware_manager_version'] = agent_result[
|
||||
'hardware_manager_version']
|
||||
info['agent_cached_clean_steps'] = dict(steps)
|
||||
info['agent_cached_clean_steps_refreshed'] = str(timeutils.utcnow())
|
||||
node.driver_internal_info = info
|
||||
node.save()
|
||||
LOG.debug('Refreshed agent clean step cache for node %(node)s: '
|
||||
'%(steps)s', {'node': node.uuid, 'steps': steps})
|
||||
|
||||
def continue_cleaning(self, task, **kwargs):
|
||||
"""Start the next cleaning step if the previous one is complete.
|
||||
|
||||
@ -249,6 +306,16 @@ class BaseAgentVendor(base.VendorInterface):
|
||||
LOG.error(msg)
|
||||
return manager_utils.cleaning_error_handler(task, msg)
|
||||
elif command.get('command_status') == 'CLEAN_VERSION_MISMATCH':
|
||||
# Cache the new clean steps (and 'hardware_manager_version')
|
||||
try:
|
||||
self._refresh_clean_steps(task)
|
||||
except exception.NodeCleaningFailure as e:
|
||||
msg = (_('Could not continue cleaning on node '
|
||||
'%(node)s: %(err)s.') %
|
||||
{'node': node.uuid, 'err': e})
|
||||
LOG.exception(msg)
|
||||
return manager_utils.cleaning_error_handler(task, msg)
|
||||
|
||||
if manual_clean:
|
||||
# Don't restart manual cleaning if agent reboots to a new
|
||||
# version. Both are operator actions, unlike automated
|
||||
@ -259,27 +326,7 @@ class BaseAgentVendor(base.VendorInterface):
|
||||
'continuing from current step %(step)s.'),
|
||||
{'node': node.uuid, 'step': node.clean_step})
|
||||
|
||||
result = self._client.get_clean_steps(
|
||||
task.node, task.ports).get('command_result')
|
||||
|
||||
required_keys = ('clean_steps', 'hardware_manager_version')
|
||||
missing_keys = [key for key in required_keys
|
||||
if key not in result]
|
||||
if missing_keys:
|
||||
msg = (_('Could not continue manual cleaning from step '
|
||||
'%(step)s on node %(node)s. get_clean_steps '
|
||||
'returned invalid result. The keys %(keys)s are '
|
||||
'missing from result %(result)s.') %
|
||||
{'node': node.uuid,
|
||||
'step': node.clean_step,
|
||||
'keys': missing_keys,
|
||||
'result': result})
|
||||
LOG.error(msg)
|
||||
return manager_utils.cleaning_error_handler(task, msg)
|
||||
|
||||
driver_internal_info = node.driver_internal_info
|
||||
driver_internal_info['hardware_manager_version'] = result[
|
||||
'hardware_manager_version']
|
||||
driver_internal_info['skip_current_clean_step'] = False
|
||||
node.driver_internal_info = driver_internal_info
|
||||
node.save()
|
||||
@ -397,6 +444,9 @@ class BaseAgentVendor(base.VendorInterface):
|
||||
node.uuid)
|
||||
msg = _('Node failed to start the first cleaning '
|
||||
'step.')
|
||||
# First, cache the clean steps
|
||||
self._refresh_clean_steps(task)
|
||||
# Then set/verify node clean steps and start cleaning
|
||||
manager_utils.set_node_cleaning_steps(task)
|
||||
self.notify_conductor_resume_clean(task)
|
||||
else:
|
||||
|
@ -550,10 +550,12 @@ def parse_instance_info_capabilities(node):
|
||||
|
||||
|
||||
def agent_get_clean_steps(task, interface=None, override_priorities=None):
|
||||
"""Get the list of clean steps from the agent.
|
||||
"""Get the list of cached clean steps from the agent.
|
||||
|
||||
#TODO(JoshNang) move to BootInterface
|
||||
|
||||
The clean steps cache is updated at the beginning of cleaning.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:param interface: The interface for which clean steps
|
||||
are to be returned. If this is not provided, it returns the
|
||||
@ -561,42 +563,34 @@ def agent_get_clean_steps(task, interface=None, override_priorities=None):
|
||||
:param override_priorities: a dictionary with keys being step names and
|
||||
values being new priorities for them. If a step isn't in this
|
||||
dictionary, the step's original priority is used.
|
||||
:raises: NodeCleaningFailure if the agent returns invalid results
|
||||
:raises NodeCleaningFailure: if the clean steps are not yet cached,
|
||||
for example, when a node has just been enrolled and has not been
|
||||
cleaned yet.
|
||||
:returns: A list of clean step dictionaries
|
||||
"""
|
||||
override_priorities = override_priorities or {}
|
||||
client = agent_client.AgentClient()
|
||||
ports = objects.Port.list_by_node_id(
|
||||
task.context, task.node.id)
|
||||
result = client.get_clean_steps(task.node, ports).get('command_result')
|
||||
node = task.node
|
||||
try:
|
||||
all_steps = node.driver_internal_info['agent_cached_clean_steps']
|
||||
except KeyError:
|
||||
raise exception.NodeCleaningFailure(_('Cleaning steps are not yet '
|
||||
'available for node %(node)s')
|
||||
% {'node': node.uuid})
|
||||
|
||||
if ('clean_steps' not in result or
|
||||
'hardware_manager_version' not in result):
|
||||
raise exception.NodeCleaningFailure(_(
|
||||
'get_clean_steps for node %(node)s returned invalid result:'
|
||||
' %(result)s') % ({'node': task.node.uuid, 'result': result}))
|
||||
if interface:
|
||||
steps = [step.copy() for step in all_steps.get(interface, [])]
|
||||
else:
|
||||
steps = [step.copy() for step_list in all_steps.values()
|
||||
for step in step_list]
|
||||
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
driver_internal_info['hardware_manager_version'] = result[
|
||||
'hardware_manager_version']
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
if not steps or not override_priorities:
|
||||
return steps
|
||||
|
||||
# Clean steps looks like {'HardwareManager': [{step1},{steps2}..]..}
|
||||
# Flatten clean steps into one list
|
||||
steps_list = [step for step_list in
|
||||
result['clean_steps'].values()
|
||||
for step in step_list]
|
||||
result = []
|
||||
for step in steps_list:
|
||||
if interface and step.get('interface') != interface:
|
||||
continue
|
||||
for step in steps:
|
||||
new_priority = override_priorities.get(step.get('step'))
|
||||
if new_priority is not None:
|
||||
step['priority'] = new_priority
|
||||
result.append(step)
|
||||
|
||||
return result
|
||||
return steps
|
||||
|
||||
|
||||
def agent_execute_clean_step(task, step):
|
||||
|
@ -213,6 +213,9 @@ class IloVirtualMediaAgentDeploy(agent.AgentDeploy):
|
||||
"""Get the list of clean steps from the agent.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
:raises NodeCleaningFailure: if the clean steps are not yet
|
||||
available (cached), for example, when a node has just been
|
||||
enrolled and has not been cleaned yet.
|
||||
:returns: A list of clean step dictionaries
|
||||
"""
|
||||
|
||||
|
@ -751,7 +751,9 @@ class ISCSIDeploy(base.DeployInterface):
|
||||
"""Get the list of clean steps from the agent.
|
||||
|
||||
:param task: a TaskManager object containing the node
|
||||
|
||||
:raises NodeCleaningFailure: if the clean steps are not yet
|
||||
available (cached), for example, when a node has just been
|
||||
enrolled and has not been cleaned yet.
|
||||
:returns: A list of clean step dictionaries. If bash ramdisk is
|
||||
used for this node, it returns an empty list.
|
||||
"""
|
||||
|
@ -357,11 +357,13 @@ class TestBaseAgentVendor(db_base.DbTestCase):
|
||||
'is done. Exception: LlamaException')
|
||||
|
||||
@mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'_refresh_clean_steps', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'set_node_cleaning_steps', autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'notify_conductor_resume_clean', autospec=True)
|
||||
def test_heartbeat_resume_clean(self, mock_notify, mock_set_steps,
|
||||
mock_touch):
|
||||
mock_refresh, mock_touch):
|
||||
kwargs = {
|
||||
'agent_url': 'http://127.0.0.1:9999/bar'
|
||||
}
|
||||
@ -374,13 +376,55 @@ class TestBaseAgentVendor(db_base.DbTestCase):
|
||||
self.passthru.heartbeat(task, **kwargs)
|
||||
|
||||
mock_touch.assert_called_once_with(mock.ANY)
|
||||
mock_refresh.assert_called_once_with(mock.ANY, task)
|
||||
mock_notify.assert_called_once_with(mock.ANY, task)
|
||||
mock_set_steps.assert_called_once_with(task)
|
||||
# Reset mocks for the next interaction
|
||||
mock_touch.reset_mock()
|
||||
mock_refresh.reset_mock()
|
||||
mock_notify.reset_mock()
|
||||
mock_set_steps.reset_mock()
|
||||
|
||||
@mock.patch.object(manager_utils, 'cleaning_error_handler')
|
||||
@mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'_refresh_clean_steps', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'set_node_cleaning_steps', autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'notify_conductor_resume_clean', autospec=True)
|
||||
def test_heartbeat_resume_clean_fails(self, mock_notify, mock_set_steps,
|
||||
mock_refresh, mock_touch,
|
||||
mock_handler):
|
||||
mocks = [mock_refresh, mock_set_steps, mock_notify]
|
||||
kwargs = {
|
||||
'agent_url': 'http://127.0.0.1:9999/bar'
|
||||
}
|
||||
self.node.clean_step = {}
|
||||
self.node.save()
|
||||
for state in (states.CLEANWAIT, states.CLEANING):
|
||||
self.node.provision_state = state
|
||||
self.node.save()
|
||||
for i in range(len(mocks)):
|
||||
before_failed_mocks = mocks[:i]
|
||||
failed_mock = mocks[i]
|
||||
after_failed_mocks = mocks[i + 1:]
|
||||
failed_mock.side_effect = Exception()
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=True) as task:
|
||||
self.passthru.heartbeat(task, **kwargs)
|
||||
|
||||
mock_touch.assert_called_once_with(mock.ANY)
|
||||
mock_handler.assert_called_once_with(task, mock.ANY)
|
||||
for called in before_failed_mocks + [failed_mock]:
|
||||
self.assertTrue(called.called)
|
||||
for not_called in after_failed_mocks:
|
||||
self.assertFalse(not_called.called)
|
||||
|
||||
# Reset mocks for the next interaction
|
||||
for m in mocks + [mock_touch, mock_handler]:
|
||||
m.reset_mock()
|
||||
failed_mock.side_effect = None
|
||||
|
||||
@mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'continue_cleaning', autospec=True)
|
||||
@ -897,25 +941,13 @@ class TestBaseAgentVendor(db_base.DbTestCase):
|
||||
@mock.patch.object(manager_utils, 'set_node_cleaning_steps', autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'notify_conductor_resume_clean', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'_refresh_clean_steps', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_commands_status',
|
||||
autospec=True)
|
||||
def _test_continue_cleaning_clean_version_mismatch(
|
||||
self, status_mock, get_steps_mock, notify_mock, steps_mock,
|
||||
self, status_mock, refresh_steps_mock, notify_mock, steps_mock,
|
||||
manual=False):
|
||||
get_steps_mock.return_value = {
|
||||
'command_status': 'CLEAN_VERSION_MISMATCH',
|
||||
'command_name': 'get_clean_step',
|
||||
'command_result': {
|
||||
'hardware_manager_version': {'Generic': '1'},
|
||||
'clean_steps': {
|
||||
'GenericHardwareManager': [
|
||||
{'interface': 'deploy',
|
||||
'step': 'erase_devices',
|
||||
'priority': 20}]}
|
||||
}
|
||||
}
|
||||
status_mock.return_value = [{
|
||||
'command_status': 'CLEAN_VERSION_MISMATCH',
|
||||
'command_name': 'execute_clean_step',
|
||||
@ -928,17 +960,12 @@ class TestBaseAgentVendor(db_base.DbTestCase):
|
||||
shared=False) as task:
|
||||
self.passthru.continue_cleaning(task)
|
||||
notify_mock.assert_called_once_with(mock.ANY, task)
|
||||
refresh_steps_mock.assert_called_once_with(mock.ANY, task)
|
||||
if manual:
|
||||
get_steps_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
task.ports)
|
||||
self.assertFalse(
|
||||
task.node.driver_internal_info['skip_current_clean_step'])
|
||||
self.assertEqual(
|
||||
{'Generic': '1'},
|
||||
task.node.driver_internal_info['hardware_manager_version'])
|
||||
self.assertFalse(steps_mock.called)
|
||||
else:
|
||||
self.assertFalse(get_steps_mock.called)
|
||||
steps_mock.assert_called_once_with(task)
|
||||
self.assertFalse('skip_current_clean_step' in
|
||||
task.node.driver_internal_info)
|
||||
@ -950,25 +977,23 @@ class TestBaseAgentVendor(db_base.DbTestCase):
|
||||
self._test_continue_cleaning_clean_version_mismatch(manual=True)
|
||||
|
||||
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'set_node_cleaning_steps', autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'notify_conductor_resume_clean', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
|
||||
'_refresh_clean_steps', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_commands_status',
|
||||
autospec=True)
|
||||
def test_continue_cleaning_manual_version_mismatch_bad(
|
||||
self, status_mock, get_steps_mock, notify_mock, error_mock):
|
||||
get_steps_mock.return_value = {
|
||||
'command_status': 'CLEAN_VERSION_MISMATCH',
|
||||
'command_name': 'get_clean_step',
|
||||
'command_result': {
|
||||
'hardware_manager_version': {'Generic': '1'}}
|
||||
}
|
||||
def test_continue_cleaning_clean_version_mismatch_fail(
|
||||
self, status_mock, refresh_steps_mock, notify_mock, steps_mock,
|
||||
error_mock, manual=False):
|
||||
status_mock.return_value = [{
|
||||
'command_status': 'CLEAN_VERSION_MISMATCH',
|
||||
'command_name': 'execute_clean_step',
|
||||
'command_result': {'hardware_manager_version': {'Generic': '1'}}
|
||||
}]
|
||||
tgt_prov_state = states.MANAGEABLE
|
||||
refresh_steps_mock.side_effect = exception.NodeCleaningFailure("boo")
|
||||
tgt_prov_state = states.MANAGEABLE if manual else states.AVAILABLE
|
||||
self.node.provision_state = states.CLEANWAIT
|
||||
self.node.target_provision_state = tgt_prov_state
|
||||
self.node.save()
|
||||
@ -976,12 +1001,11 @@ class TestBaseAgentVendor(db_base.DbTestCase):
|
||||
shared=False) as task:
|
||||
self.passthru.continue_cleaning(task)
|
||||
|
||||
get_steps_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
task.ports)
|
||||
status_mock.assert_called_once_with(mock.ANY, task.node)
|
||||
refresh_steps_mock.assert_called_once_with(mock.ANY, task)
|
||||
error_mock.assert_called_once_with(task, mock.ANY)
|
||||
self.assertFalse(notify_mock.called)
|
||||
self.assertFalse('skip_current_clean_step' in
|
||||
task.node.driver_internal_info)
|
||||
self.assertFalse(steps_mock.called)
|
||||
|
||||
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_commands_status',
|
||||
@ -1076,3 +1100,91 @@ class TestBaseAgentVendor(db_base.DbTestCase):
|
||||
self.node.save()
|
||||
hook_returned = agent_base_vendor._get_post_clean_step_hook(self.node)
|
||||
self.assertIsNone(hook_returned)
|
||||
|
||||
|
||||
class TestRefreshCleanSteps(TestBaseAgentVendor):
|
||||
|
||||
def setUp(self):
|
||||
super(TestRefreshCleanSteps, self).setUp()
|
||||
self.node.driver_internal_info['agent_url'] = 'http://127.0.0.1:9999'
|
||||
self.ports = [object_utils.create_test_port(self.context,
|
||||
node_id=self.node.id)]
|
||||
|
||||
self.clean_steps = {
|
||||
'hardware_manager_version': '1',
|
||||
'clean_steps': {
|
||||
'GenericHardwareManager': [
|
||||
{'interface': 'deploy',
|
||||
'step': 'erase_devices',
|
||||
'priority': 20},
|
||||
],
|
||||
'SpecificHardwareManager': [
|
||||
{'interface': 'deploy',
|
||||
'step': 'update_firmware',
|
||||
'priority': 30},
|
||||
{'interface': 'raid',
|
||||
'step': 'create_configuration',
|
||||
'priority': 10},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
def test__refresh_clean_steps(self, client_mock):
|
||||
client_mock.return_value = {
|
||||
'command_result': self.clean_steps}
|
||||
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
self.passthru._refresh_clean_steps(task)
|
||||
|
||||
client_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
task.ports)
|
||||
self.assertEqual('1', task.node.driver_internal_info[
|
||||
'hardware_manager_version'])
|
||||
self.assertTrue('agent_cached_clean_steps_refreshed' in
|
||||
task.node.driver_internal_info)
|
||||
steps = task.node.driver_internal_info['agent_cached_clean_steps']
|
||||
# Since steps are returned in dicts, they have non-deterministic
|
||||
# ordering
|
||||
self.assertEqual(2, len(steps))
|
||||
self.assertIn(self.clean_steps['clean_steps'][
|
||||
'GenericHardwareManager'][0], steps['deploy'])
|
||||
self.assertIn(self.clean_steps['clean_steps'][
|
||||
'SpecificHardwareManager'][0], steps['deploy'])
|
||||
self.assertEqual([self.clean_steps['clean_steps'][
|
||||
'SpecificHardwareManager'][1]], steps['raid'])
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
def test__refresh_clean_steps_missing_steps(self, client_mock):
|
||||
del self.clean_steps['clean_steps']
|
||||
client_mock.return_value = {
|
||||
'command_result': self.clean_steps}
|
||||
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
self.assertRaisesRegex(exception.NodeCleaningFailure,
|
||||
'invalid result',
|
||||
self.passthru._refresh_clean_steps,
|
||||
task)
|
||||
client_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
task.ports)
|
||||
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
def test__refresh_clean_steps_missing_interface(self, client_mock):
|
||||
step = self.clean_steps['clean_steps']['SpecificHardwareManager'][1]
|
||||
del step['interface']
|
||||
client_mock.return_value = {
|
||||
'command_result': self.clean_steps}
|
||||
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
self.assertRaisesRegex(exception.NodeCleaningFailure,
|
||||
'invalid clean step',
|
||||
self.passthru._refresh_clean_steps,
|
||||
task)
|
||||
client_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
task.ports)
|
||||
|
@ -1521,142 +1521,75 @@ class AgentMethodsTestCase(db_base.DbTestCase):
|
||||
def setUp(self):
|
||||
super(AgentMethodsTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver='fake_agent')
|
||||
n = {'driver': 'fake_agent',
|
||||
'driver_internal_info': {'agent_url': 'http://127.0.0.1:9999'}}
|
||||
|
||||
self.clean_steps = {
|
||||
'deploy': [
|
||||
{'interface': 'deploy',
|
||||
'step': 'erase_devices',
|
||||
'priority': 20},
|
||||
{'interface': 'deploy',
|
||||
'step': 'update_firmware',
|
||||
'priority': 30}
|
||||
],
|
||||
'raid': [
|
||||
{'interface': 'raid',
|
||||
'step': 'create_configuration',
|
||||
'priority': 10}
|
||||
]
|
||||
}
|
||||
n = {'driver': 'fake_agent',
|
||||
'driver_internal_info': {
|
||||
'agent_cached_clean_steps': self.clean_steps}}
|
||||
self.node = obj_utils.create_test_node(self.context, **n)
|
||||
self.ports = [obj_utils.create_test_port(self.context,
|
||||
node_id=self.node.id)]
|
||||
|
||||
self.clean_steps = {
|
||||
'hardware_manager_version': '1',
|
||||
'clean_steps': {
|
||||
'GenericHardwareManager': [
|
||||
{'interface': 'deploy',
|
||||
'step': 'erase_devices',
|
||||
'priority': 20},
|
||||
],
|
||||
'SpecificHardwareManager': [
|
||||
{'interface': 'deploy',
|
||||
'step': 'update_firmware',
|
||||
'priority': 30},
|
||||
{'interface': 'raid',
|
||||
'step': 'create_configuration',
|
||||
'priority': 10},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||
spec_set=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
def test_get_clean_steps(self, client_mock, list_ports_mock):
|
||||
client_mock.return_value = {
|
||||
'command_result': self.clean_steps}
|
||||
list_ports_mock.return_value = self.ports
|
||||
|
||||
def test_agent_get_clean_steps(self):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node['uuid'], shared=False) as task:
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
response = utils.agent_get_clean_steps(task)
|
||||
client_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
self.ports)
|
||||
self.assertEqual('1', task.node.driver_internal_info[
|
||||
'hardware_manager_version'])
|
||||
|
||||
# Since steps are returned in dicts, they have non-deterministic
|
||||
# ordering
|
||||
self.assertThat(response, matchers.HasLength(3))
|
||||
self.assertIn(self.clean_steps['clean_steps'][
|
||||
'GenericHardwareManager'][0], response)
|
||||
self.assertIn(self.clean_steps['clean_steps'][
|
||||
'SpecificHardwareManager'][0], response)
|
||||
self.assertIn(self.clean_steps['clean_steps'][
|
||||
'SpecificHardwareManager'][1], response)
|
||||
|
||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||
spec_set=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
def test_get_clean_steps_custom_interface(
|
||||
self, client_mock, list_ports_mock):
|
||||
client_mock.return_value = {
|
||||
'command_result': self.clean_steps}
|
||||
list_ports_mock.return_value = self.ports
|
||||
self.assertIn(self.clean_steps['deploy'][0], response)
|
||||
self.assertIn(self.clean_steps['deploy'][1], response)
|
||||
self.assertIn(self.clean_steps['raid'][0], response)
|
||||
|
||||
def test_get_clean_steps_custom_interface(self):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
response = utils.agent_get_clean_steps(task, interface='raid')
|
||||
client_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
self.ports)
|
||||
self.assertEqual('1', task.node.driver_internal_info[
|
||||
'hardware_manager_version'])
|
||||
|
||||
self.assertThat(response, matchers.HasLength(1))
|
||||
self.assertIn(self.clean_steps['clean_steps'][
|
||||
'SpecificHardwareManager'][1], response)
|
||||
|
||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||
spec_set=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
def test_get_clean_steps_override_priorities(
|
||||
self, client_mock, list_ports_mock):
|
||||
client_mock.return_value = {
|
||||
'command_result': self.clean_steps}
|
||||
list_ports_mock.return_value = self.ports
|
||||
self.assertEqual(self.clean_steps['raid'], response)
|
||||
|
||||
def test_get_clean_steps_override_priorities(self):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
new_priorities = {'create_configuration': 42}
|
||||
response = utils.agent_get_clean_steps(
|
||||
task, interface='raid', override_priorities=new_priorities)
|
||||
client_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
self.ports)
|
||||
self.assertEqual('1', task.node.driver_internal_info[
|
||||
'hardware_manager_version'])
|
||||
self.assertEqual(42, response[0]['priority'])
|
||||
|
||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||
spec_set=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
def test_get_clean_steps_override_priorities_none(
|
||||
self, client_mock, list_ports_mock):
|
||||
client_mock.return_value = {
|
||||
'command_result': self.clean_steps}
|
||||
list_ports_mock.return_value = self.ports
|
||||
|
||||
def test_get_clean_steps_override_priorities_none(self):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
# this is simulating the default value of a configuration option
|
||||
new_priorities = {'create_configuration': None}
|
||||
response = utils.agent_get_clean_steps(
|
||||
task, interface='raid', override_priorities=new_priorities)
|
||||
client_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
self.ports)
|
||||
self.assertEqual('1', task.node.driver_internal_info[
|
||||
'hardware_manager_version'])
|
||||
self.assertEqual(10, response[0]['priority'])
|
||||
|
||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||
spec_set=types.FunctionType)
|
||||
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
|
||||
autospec=True)
|
||||
def test_get_clean_steps_missing_steps(self, client_mock,
|
||||
list_ports_mock):
|
||||
del self.clean_steps['clean_steps']
|
||||
client_mock.return_value = {
|
||||
'command_result': self.clean_steps}
|
||||
list_ports_mock.return_value = self.ports
|
||||
|
||||
def test_get_clean_steps_missing_steps(self):
|
||||
info = self.node.driver_internal_info
|
||||
del info['agent_cached_clean_steps']
|
||||
self.node.driver_internal_info = info
|
||||
self.node.save()
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
self.assertRaises(exception.NodeCleaningFailure,
|
||||
utils.agent_get_clean_steps,
|
||||
task)
|
||||
client_mock.assert_called_once_with(mock.ANY, task.node,
|
||||
self.ports)
|
||||
|
||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||
spec_set=types.FunctionType)
|
||||
@ -1668,10 +1601,10 @@ class AgentMethodsTestCase(db_base.DbTestCase):
|
||||
list_ports_mock.return_value = self.ports
|
||||
|
||||
with task_manager.acquire(
|
||||
self.context, self.node['uuid'], shared=False) as task:
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
response = utils.agent_execute_clean_step(
|
||||
task,
|
||||
self.clean_steps['clean_steps']['GenericHardwareManager'][0])
|
||||
self.clean_steps['deploy'][0])
|
||||
self.assertEqual(states.CLEANWAIT, response)
|
||||
|
||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||
@ -1684,10 +1617,10 @@ class AgentMethodsTestCase(db_base.DbTestCase):
|
||||
list_ports_mock.return_value = self.ports
|
||||
|
||||
with task_manager.acquire(
|
||||
self.context, self.node['uuid'], shared=False) as task:
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
response = utils.agent_execute_clean_step(
|
||||
task,
|
||||
self.clean_steps['clean_steps']['GenericHardwareManager'][0])
|
||||
self.clean_steps['deploy'][0])
|
||||
self.assertEqual(states.CLEANWAIT, response)
|
||||
|
||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||
@ -1701,16 +1634,16 @@ class AgentMethodsTestCase(db_base.DbTestCase):
|
||||
list_ports_mock.return_value = self.ports
|
||||
|
||||
with task_manager.acquire(
|
||||
self.context, self.node['uuid'], shared=False) as task:
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
response = utils.agent_execute_clean_step(
|
||||
task,
|
||||
self.clean_steps['clean_steps']['GenericHardwareManager'][0])
|
||||
self.clean_steps['deploy'][0])
|
||||
self.assertEqual(states.CLEANWAIT, response)
|
||||
|
||||
def test_agent_add_clean_params(self):
|
||||
cfg.CONF.deploy.erase_devices_iterations = 2
|
||||
with task_manager.acquire(
|
||||
self.context, self.node['uuid'], shared=False) as task:
|
||||
self.context, self.node.uuid, shared=False) as task:
|
||||
utils.agent_add_clean_params(task)
|
||||
self.assertEqual(task.node.driver_internal_info.get(
|
||||
'agent_erase_devices_iterations'), 2)
|
||||
|
Loading…
Reference in New Issue
Block a user