Files
ironic-python-agent/ironic_python_agent/tests/unit/extensions/test_clean.py
Josh Gachnang 61b4387b95 Allow hardware managers to override clean step priority
If two hardware managers have the same clean step, for example
'erase_devices' in the GenericHardwareManager and a custom manager,
IPA must determine which step should be kept and which should be run
in order to prevent running the step multiple times.

This patch uses the following filtering logic to decide which step
"wins":
- Keep the step that belongs to HardwareManager with highest
  HardwareSupport (larger int) value.
- If equal support level, keep the step with the higher defined
  priority (larger int).
- If equal support level and priority, keep the step associated with
  the HardwareManager whose name comes earlier in the alphabet.

Other than individual step priority, picking which step to keep does
not actually impact the cleaning run. However, in order to make
testing easier, this change ensures deterministic, predictable
results.

Co-Authored-By: Mario Villaplana <mario.villaplana@gmail.com>
Co-Authored-By: Jay Faulkner <jay@jvf.cc>
Co-Authored-By: Brad Morgan <brad@morgabra.com>
Change-Id: Iaeea4200c38ee22cab72ba81c1dbae3389e675e4
2016-01-14 13:12:52 -08:00

241 lines
9.4 KiB
Python

# Copyright 2015 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from oslotest import base as test_base
from ironic_python_agent import errors
from ironic_python_agent.extensions import clean
class TestCleanExtension(test_base.BaseTestCase):
def setUp(self):
super(TestCleanExtension, self).setUp()
self.agent_extension = clean.CleanExtension()
self.node = {'uuid': 'dda135fb-732d-4742-8e72-df8f3199d244'}
self.ports = []
self.step = {
'GenericHardwareManager':
[{'step': 'erase_devices',
'priority': 10,
'interface': 'deploy'}]
}
self.version = {'generic': '1', 'specific': '1'}
@mock.patch('ironic_python_agent.extensions.clean.'
'_get_current_clean_version')
@mock.patch('ironic_python_agent.hardware.dispatch_to_all_managers')
def test_get_clean_steps(self, mock_dispatch, mock_version):
mock_version.return_value = self.version
manager_steps = {
'SpecificHardwareManager': [
{
'step': 'erase_devices',
'priority': 10,
'interface': 'deploy',
'reboot_requested': False
},
{
'step': 'upgrade_bios',
'priority': 20,
'interface': 'deploy',
'reboot_requested': True
},
{
'step': 'upgrade_firmware',
'priority': 60,
'interface': 'deploy',
'reboot_requested': False
},
],
'FirmwareHardwareManager': [
{
'step': 'upgrade_firmware',
'priority': 10,
'interface': 'deploy',
'reboot_requested': False
},
{
'step': 'erase_devices',
'priority': 40,
'interface': 'deploy',
'reboot_requested': False
},
],
'DiskHardwareManager': [
{
'step': 'erase_devices',
'priority': 50,
'interface': 'deploy',
'reboot_requested': False
},
]
}
expected_steps = {
'SpecificHardwareManager': [
# Only manager upgrading BIOS
{
'step': 'upgrade_bios',
'priority': 20,
'interface': 'deploy',
'reboot_requested': True
}
],
'FirmwareHardwareManager': [
# Higher support than specific, even though lower priority
{
'step': 'upgrade_firmware',
'priority': 10,
'interface': 'deploy',
'reboot_requested': False
},
],
'DiskHardwareManager': [
# Higher support than specific, higher priority than firmware
{
'step': 'erase_devices',
'priority': 50,
'interface': 'deploy',
'reboot_requested': False
},
]
}
hardware_support = {
'SpecificHardwareManager': 3,
'FirmwareHardwareManager': 4,
'DiskHardwareManager': 4
}
mock_dispatch.side_effect = [manager_steps, hardware_support]
expected_return = {
'hardware_manager_version': self.version,
'clean_steps': expected_steps
}
async_results = self.agent_extension.get_clean_steps(node=self.node,
ports=self.ports)
# Ordering of the clean steps doesn't matter; they're sorted by
# 'priority' in Ironic
self.assertEqual(expected_return,
async_results.join().command_result)
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers')
@mock.patch('ironic_python_agent.extensions.clean._check_clean_version')
def test_execute_clean_step(self, mock_version, mock_dispatch):
result = 'cleaned'
mock_dispatch.return_value = result
expected_result = {
'clean_step': self.step['GenericHardwareManager'][0],
'clean_result': result
}
async_result = self.agent_extension.execute_clean_step(
step=self.step['GenericHardwareManager'][0],
node=self.node, ports=self.ports,
clean_version=self.version)
async_result.join()
mock_version.assert_called_once_with(self.version)
mock_dispatch.assert_called_once_with(
self.step['GenericHardwareManager'][0]['step'],
self.node, self.ports)
self.assertEqual(expected_result, async_result.command_result)
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers')
@mock.patch('ironic_python_agent.extensions.clean._check_clean_version')
def test_execute_clean_step_tuple_result(self, mock_version,
mock_dispatch):
result = ('stdout', 'stderr')
mock_dispatch.return_value = result
expected_result = {
'clean_step': self.step['GenericHardwareManager'][0],
'clean_result': ['stdout', 'stderr']
}
async_result = self.agent_extension.execute_clean_step(
step=self.step['GenericHardwareManager'][0],
node=self.node, ports=self.ports,
clean_version=self.version)
async_result.join()
mock_version.assert_called_once_with(self.version)
mock_dispatch.assert_called_once_with(
self.step['GenericHardwareManager'][0]['step'],
self.node, self.ports)
self.assertEqual(expected_result, async_result.command_result)
@mock.patch('ironic_python_agent.extensions.clean._check_clean_version')
def test_execute_clean_step_no_step(self, mock_version):
async_result = self.agent_extension.execute_clean_step(
step={}, node=self.node, ports=self.ports,
clean_version=self.version)
async_result.join()
self.assertEqual('FAILED', async_result.command_status)
mock_version.assert_called_once_with(self.version)
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers')
@mock.patch('ironic_python_agent.extensions.clean._check_clean_version')
def test_execute_clean_step_fail(self, mock_version, mock_dispatch):
mock_dispatch.side_effect = RuntimeError
async_result = self.agent_extension.execute_clean_step(
step=self.step['GenericHardwareManager'][0], node=self.node,
ports=self.ports, clean_version=self.version)
async_result.join()
self.assertEqual('FAILED', async_result.command_status)
mock_version.assert_called_once_with(self.version)
mock_dispatch.assert_called_once_with(
self.step['GenericHardwareManager'][0]['step'],
self.node, self.ports)
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers')
@mock.patch('ironic_python_agent.extensions.clean._check_clean_version')
def test_execute_clean_step_version_mismatch(self, mock_version,
mock_dispatch):
mock_version.side_effect = errors.CleanVersionMismatch(
{'GenericHardwareManager': 1}, {'GenericHardwareManager': 2})
async_result = self.agent_extension.execute_clean_step(
step=self.step['GenericHardwareManager'][0], node=self.node,
ports=self.ports, clean_version=self.version)
async_result.join()
self.assertEqual('CLEAN_VERSION_MISMATCH', async_result.command_status)
mock_version.assert_called_once_with(self.version)
@mock.patch('ironic_python_agent.hardware.dispatch_to_all_managers')
def _get_current_clean_version(self, mock_dispatch):
mock_dispatch.return_value = {'SpecificHardwareManager':
{'name': 'specific', 'version': '1'},
'GenericHardwareManager':
{'name': 'generic', 'version': '1'}}
self.assertEqual(self.version, clean._get_current_clean_version())
@mock.patch('ironic_python_agent.hardware.dispatch_to_all_managers')
def test__check_clean_version_fail(self, mock_dispatch):
mock_dispatch.return_value = {'SpecificHardwareManager':
{'name': 'specific', 'version': '1'}}
self.assertRaises(errors.CleanVersionMismatch,
clean._check_clean_version,
{'not_specific': '1'})