RedfishFirmwareInterface - Unit Tests & More logs

I totally missed Julia's comment in the review, this commit
adds unit tests for the RedfishFirmwareInterface and also more
logs when a specific component is missing.

Change-Id: Ice2c946726103d9957518c2d30ddad3310ee145d
This commit is contained in:
Iury Gregory Melo Ferreira 2023-12-11 17:18:11 -03:00
parent 88fcf8aa14
commit 32c9c74459
4 changed files with 695 additions and 11 deletions

View File

@ -92,9 +92,13 @@ class RedfishFirmware(base.FirmwareInterface):
system = redfish_utils.get_system(task.node)
if system.bios_version:
bios_fw = {'component': 'bios',
bios_fw = {'component': redfish_utils.BIOS,
'current_version': system.bios_version}
settings.append(bios_fw)
else:
LOG.debug('Could not retrieve BiosVersion in node %(node_uuid)s '
'system %(system)s', {'node_uuid': task.node.uuid,
'system': system.identity})
# NOTE(iurygregory): normally we only relay on the System to
# perform actions, but to retrieve the BMC Firmware we need to
@ -102,9 +106,14 @@ class RedfishFirmware(base.FirmwareInterface):
try:
manager = redfish_utils.get_manager(task.node, system)
if manager.firmware_version:
bmc_fw = {'component': 'bmc',
bmc_fw = {'component': redfish_utils.BMC,
'current_version': manager.firmware_version}
settings.append(bmc_fw)
else:
LOG.debug('Could not retrieve FirmwareVersion in node '
'%(node_uuid)s manager %(manager)s',
{'node_uuid': task.node.uuid,
'manager': manager.identity})
except exception.RedfishError:
LOG.warning('No manager available to retrieve Firmware '
'from the bmc of node %s', task.node.uuid)
@ -160,19 +169,17 @@ class RedfishFirmware(base.FirmwareInterface):
:returns: states.CLEANWAIT if Firmware update with the settings is in
progress asynchronously of None if it is complete.
"""
firmware_utils.validate_firmware_interface_update_args(settings)
node = task.node
update_service = redfish_utils.get_update_service(node)
LOG.debug('Updating Firmware on node %(node_uuid)s with settings '
'%(settings)s',
{'node_uuid': node.uuid, 'settings': settings})
self._execute_firmware_update(node, update_service, settings)
fw_upd = settings[0]
wait_interval = fw_upd.get('wait')
deploy_utils.set_async_step_flags(
node,
reboot=True,
@ -199,8 +206,13 @@ class RedfishFirmware(base.FirmwareInterface):
'%(node_uuid)s',
{'url': fw_upd['url'], 'component': fw_upd['component'],
'node_uuid': node.uuid})
task_monitor = update_service.simple_update(component_url)
try:
task_monitor = update_service.simple_update(component_url)
except sushy.exceptions.MissingAttributeError as e:
LOG.error('The attribute #UpdateService.SimpleUpdate is missing '
'on node %(node)s. Error: %(error)s',
{'node': node.uuid, 'error': e.message})
raise exception.RedfishError(error=e)
fw_upd['task_monitor'] = task_monitor.task_monitor_uri
node.set_driver_internal_info('redfish_fw_updates', settings)
@ -326,8 +338,7 @@ class RedfishFirmware(base.FirmwareInterface):
LOG.warning('Unable to communicate with firmware update service '
'on node %(node)s. Will try again on the next poll. '
'Error: %(error)s',
{'node': node.uuid,
'error': e})
{'node': node.uuid, 'error': e})
return
wait_start_time = current_update.get('wait_start_time')
@ -453,3 +464,4 @@ class RedfishFirmware(base.FirmwareInterface):
except exception.IronicException:
firmware_utils.cleanup(node)
raise

View File

@ -25,6 +25,7 @@ from ironic.common.i18n import _
from ironic.common import image_service
from ironic.common import swift
from ironic.conf import CONF
from ironic.drivers.modules.redfish import utils as redfish_utils
LOG = log.getLogger(__name__)
@ -65,9 +66,10 @@ _UPDATE_FIRMWARE_SCHEMA = {
}
_FIRMWARE_INTERFACE_UPDATE_SCHEMA = {
"$schema": "http://json-schema.org/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "update_firmware clean step schema",
"type": "array",
"minItems": 1,
# list of firmware update images
"items": {
"type": "object",
@ -76,7 +78,7 @@ _FIRMWARE_INTERFACE_UPDATE_SCHEMA = {
"component": {
"description": "name of the firmware component to be updated",
"type": "string",
"minLenght": 1
"enum": redfish_utils.FIRMWARE_COMPONENTS
},
"url": {
"description": "URL for firmware file",

View File

@ -80,6 +80,17 @@ COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
# All available FIRMWARE COMPONENTS
BIOS = 'bios'
"BIOS Firmware Component"
BMC = 'bmc'
"BMC Firmware Component"
FIRMWARE_COMPONENTS = [BIOS, BMC]
"""Firmware Components available to update"""
def parse_driver_info(node):
"""Parse the information required for Ironic to connect to Redfish.

View File

@ -11,10 +11,20 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from unittest import mock
from oslo_utils import importutils
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.conf import CONF
from ironic.drivers.modules.redfish import firmware as redfish_fw
from ironic.drivers.modules.redfish import firmware_utils
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic import objects
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils
@ -38,3 +48,652 @@ class RedfishFirmwareTestCase(db_base.DbTestCase):
enabled_firmware_interfaces=['redfish'])
self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT)
def test_get_properties(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
properties = task.driver.get_properties()
for prop in redfish_utils.COMMON_PROPERTIES:
self.assertIn(prop, properties)
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
def test_validate(self, mock_parse_driver_info):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.firmware.validate(task)
mock_parse_driver_info.assert_called_once_with(task.node)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(redfish_utils, 'get_manager', autospec=True)
@mock.patch.object(objects.FirmwareComponentList,
'sync_firmware_components', autospec=True)
def test_missing_all_components(self, sync_fw_cmp_mock, manager_mock,
system_mock, log_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
system_mock.return_value.identity = "System1"
manager_mock.return_value.identity = "Manager1"
system_mock.return_value.bios_version = None
manager_mock.return_value.firmware_version = None
self.assertRaises(exception.UnsupportedDriverExtension,
task.driver.firmware.cache_firmware_components,
task)
sync_fw_cmp_mock.assert_not_called()
error_msg = (
'Cannot retrieve firmware for node %s: '
'no supported components'
% self.node.uuid)
log_mock.error.assert_called_once_with(error_msg)
warning_calls = [
mock.call('Could not retrieve BiosVersion in node '
'%(node_uuid)s system %(system)s',
{'node_uuid': self.node.uuid,
'system': "System1"}),
mock.call('Could not retrieve FirmwareVersion in node '
'%(node_uuid)s manager %(manager)s',
{'node_uuid': self.node.uuid,
'manager': "Manager1"})]
log_mock.debug.assert_has_calls(warning_calls)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(redfish_utils, 'get_manager', autospec=True)
@mock.patch.object(objects.FirmwareComponentList,
'sync_firmware_components', autospec=True)
@mock.patch.object(objects, 'FirmwareComponent', spec_set=True,
autospec=True)
def test_missing_bios_component(self, fw_cmp_mock, sync_fw_cmp_mock,
manager_mock, system_mock, log_mock):
create_list = [{'component': 'bmc', 'current_version': 'v1.0.0'}]
sync_fw_cmp_mock.return_value = (
create_list, [], []
)
bmc_component = {'component': 'bmc', 'current_version': 'v1.0.0',
'node_id': self.node.id}
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
system_mock.return_value.identity = "System1"
system_mock.return_value.bios_version = None
manager_mock.return_value.firmware_version = "v1.0.0"
task.driver.firmware.cache_firmware_components(task)
system_mock.assert_called_once_with(task.node)
log_mock.debug.assert_called_once_with(
'Could not retrieve BiosVersion in node '
'%(node_uuid)s system %(system)s',
{'node_uuid': self.node.uuid, 'system': 'System1'})
sync_fw_cmp_mock.assert_called_once_with(
task.context, task.node.id,
[{'component': 'bmc', 'current_version': 'v1.0.0'}])
self.assertTrue(fw_cmp_mock.called)
fw_cmp_mock.assert_called_once_with(task.context, **bmc_component)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(redfish_utils, 'get_manager', autospec=True)
@mock.patch.object(objects.FirmwareComponentList,
'sync_firmware_components', autospec=True)
@mock.patch.object(objects, 'FirmwareComponent', spec_set=True,
autospec=True)
def test_missing_bmc_component(self, fw_cmp_mock, sync_fw_cmp_mock,
manager_mock, system_mock, log_mock):
create_list = [{'component': 'bios', 'current_version': 'v1.0.0'}]
sync_fw_cmp_mock.return_value = (
create_list, [], []
)
bios_component = {'component': 'bios', 'current_version': 'v1.0.0',
'node_id': self.node.id}
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
manager_mock.return_value.identity = "Manager1"
manager_mock.return_value.firmware_version = None
system_mock.return_value.bios_version = "v1.0.0"
task.driver.firmware.cache_firmware_components(task)
log_mock.debug.assert_called_once_with(
'Could not retrieve FirmwareVersion in node '
'%(node_uuid)s manager %(manager)s',
{'node_uuid': self.node.uuid, 'manager': "Manager1"})
system_mock.assert_called_once_with(task.node)
sync_fw_cmp_mock.assert_called_once_with(
task.context, task.node.id,
[{'component': 'bios', 'current_version': 'v1.0.0'}])
self.assertTrue(fw_cmp_mock.called)
fw_cmp_mock.assert_called_once_with(task.context, **bios_component)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(redfish_utils, 'get_manager', autospec=True)
@mock.patch.object(objects, 'FirmwareComponentList', autospec=True)
@mock.patch.object(objects, 'FirmwareComponent', spec_set=True,
autospec=True)
def test_create_all_components(self, fw_cmp_mock, fw_cmp_list_mock,
manager_mock, system_mock, log_mock):
create_list = [{'component': 'bios', 'current_version': 'v1.0.0'},
{'component': 'bmc', 'current_version': 'v1.0.0'}]
fw_cmp_list_mock.sync_firmware_components.return_value = (
create_list, [], []
)
bios_component = {'component': 'bios', 'current_version': 'v1.0.0',
'node_id': self.node.id}
bmc_component = {'component': 'bmc', 'current_version': 'v1.0.0',
'node_id': self.node.id}
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
manager_mock.return_value.firmware_version = "v1.0.0"
system_mock.return_value.bios_version = "v1.0.0"
task.driver.firmware.cache_firmware_components(task)
log_mock.warning.assert_not_called()
log_mock.debug.assert_not_called()
system_mock.assert_called_once_with(task.node)
fw_cmp_list_mock.sync_firmware_components.assert_called_once_with(
task.context, task.node.id,
[{'component': 'bios', 'current_version': 'v1.0.0'},
{'component': 'bmc', 'current_version': 'v1.0.0'}])
fw_cmp_calls = [
mock.call(task.context, **bios_component),
mock.call().create(),
mock.call(task.context, **bmc_component),
mock.call().create(),
]
fw_cmp_mock.assert_has_calls(fw_cmp_calls)
@mock.patch.object(redfish_utils, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, '_get_connection', autospec=True)
def test_missing_updateservice(self, conn_mock, log_mock):
settings = [{'component': 'bmc', 'url': 'http://upfwbmc/v2.0.0'}]
conn_mock.side_effect = sushy.exceptions.MissingAttributeError(
attribute='UpdateService', resource='redfish/v1')
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
error_msg = ('The attribute UpdateService is missing from the '
'resource redfish/v1')
self.assertRaisesRegex(
exception.RedfishError, error_msg,
task.driver.firmware.update,
task, settings)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_update_service', autospec=True)
def test_missing_simple_update_action(self, update_service_mock, log_mock):
settings = [{'component': 'bmc', 'url': 'http://upfwbmc/v2.0.0'}]
update_service = update_service_mock.return_value
update_service.simple_update.side_effect = \
sushy.exceptions.MissingAttributeError(
attribute='#UpdateService.SimpleUpdate',
resource='redfish/v1/UpdateService')
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(
exception.RedfishError,
task.driver.firmware.update,
task, settings)
expected_err_msg = (
'The attribute #UpdateService.SimpleUpdate is missing '
'from the resource redfish/v1/UpdateService')
log_mock.error.assert_called_once_with(
'The attribute #UpdateService.SimpleUpdate is missing '
'on node %(node)s. Error: %(error)s',
{'node': self.node.uuid, 'error': expected_err_msg})
component = settings[0].get('component')
url = settings[0].get('url')
log_call = [
mock.call('Updating Firmware on node %(node_uuid)s '
'with settings %(settings)s',
{'node_uuid': self.node.uuid,
'settings': settings}),
mock.call('For node %(node)s serving firmware for '
'%(component)s from original location %(url)s',
{'node': self.node.uuid,
'component': component, 'url': url}),
mock.call('Applying new firmware %(url)s for '
'%(component)s on node %(node_uuid)s',
{'url': url, 'component': component,
'node_uuid': self.node.uuid})
]
log_mock.debug.assert_has_calls(log_call)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
def _test_invalid_settings(self, log_mock):
step = self.node.clean_step
settings = step['argsinfo'].get('settings', None)
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(
exception.InvalidParameterValue,
task.driver.firmware.update,
task, settings)
log_mock.debug.assert_not_called()
def test_invalid_component_in_settings(self):
argsinfo = {'settings': [
{'component': 'nic', 'url': 'https://nic-update/v1.1.0'}
]}
self.node.clean_step = {'priority': 100, 'interface': 'firmware',
'step': 'update',
'argsinfo': argsinfo}
self.node.save()
self._test_invalid_settings()
def test_missing_required_field_in_settings(self):
argsinfo = {'settings': [
{'url': 'https://nic-update/v1.1.0'},
{'component': "bmc"}
]}
self.node.clean_step = {'priority': 100, 'interface': 'firmware',
'step': 'update',
'argsinfo': argsinfo}
self.node.save()
self._test_invalid_settings()
def test_empty_settings(self):
argsinfo = {'settings': []}
self.node.clean_step = {'priority': 100, 'interface': 'firmware',
'step': 'update',
'argsinfo': argsinfo}
self.node.save()
self._test_invalid_settings()
def _generate_new_driver_internal_info(self, components=[], invalid=False,
add_wait=False, wait=1):
bmc_component = {'component': 'bmc', 'url': 'https://bmc/v1.0.1'}
bios_component = {'component': 'bios', 'url': 'https://bios/v1.0.1'}
if add_wait:
wait_start_time = datetime.datetime.utcnow() -\
datetime.timedelta(minutes=1)
bmc_component['wait_start_time'] = wait_start_time.isoformat()
bios_component['wait_start_time'] = wait_start_time.isoformat()
bmc_component['wait'] = wait
bios_component['wait'] = wait
self.node.clean_step = {'priority': 100, 'interface': 'bios',
'step': 'apply_configuration',
'argsinfo': {'settings': []}}
updates = []
if 'bmc' in components:
self.node.clean_step['argsinfo']['settings'].append(
bmc_component)
bmc_component['task_monitor'] = '/task/1'
updates.append(bmc_component)
if 'bios' in components:
self.node.clean_step['argsinfo']['settings'].append(
bios_component)
bios_component['task_monitor'] = '/task/2'
updates.append(bios_component)
if invalid:
self.node.provision_state = states.CLEANING
self.node.driver_internal_info = {'something': 'else'}
else:
self.node.provision_state = states.CLEANING
self.node.driver_internal_info = {
'redfish_fw_updates': updates,
}
self.node.save()
@mock.patch.object(task_manager, 'acquire', autospec=True)
def _test__query_methods(self, acquire_mock):
firmware = redfish_fw.RedfishFirmware()
mock_manager = mock.Mock()
node_list = [(self.node.uuid, 'redfish', '',
self.node.driver_internal_info)]
mock_manager.iter_nodes.return_value = node_list
task = mock.Mock(node=self.node,
driver=mock.Mock(firmware=firmware))
acquire_mock.return_value = mock.MagicMock(
__enter__=mock.MagicMock(return_value=task))
firmware._check_node_redfish_firmware_update = mock.Mock()
firmware._clear_updates = mock.Mock()
# _query_update_status
firmware._query_update_status(mock_manager, self.context)
if not self.node.driver_internal_info.get('redfish_fw_updates'):
firmware._check_node_redfish_firmware_update.assert_not_called()
else:
firmware._check_node_redfish_firmware_update.\
assert_called_once_with(task)
# _query_update_failed
firmware._query_update_failed(mock_manager, self.context)
if not self.node.driver_internal_info.get('redfish_fw_updates'):
firmware._clear_updates.assert_not_called()
else:
firmware._clear_updates.assert_called_once_with(self.node)
def test_redfish_fw_updates(self):
self._generate_new_driver_internal_info(['bmc'])
self._test__query_methods()
def test_redfish_fw_updates_empty(self):
self._generate_new_driver_internal_info(invalid=True)
self._test__query_methods()
def _test__check_node_redfish_firmware_update(self):
firmware = redfish_fw.RedfishFirmware()
firmware._continue_updates = mock.Mock()
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.upgrade_lock = mock.Mock()
task.process_event = mock.Mock()
firmware._check_node_redfish_firmware_update(task)
return task, firmware
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_update_service', autospec=True)
def test_check_conn_error(self, get_us_mock, log_mock):
self._generate_new_driver_internal_info(['bmc'])
get_us_mock.side_effect = exception.RedfishConnectionError('Error')
try:
self._test__check_node_redfish_firmware_update()
except exception.RedfishError as e:
exception_error = e.kwargs.get('error')
warning_calls = [
mock.call('Unable to communicate with firmware update '
'service on node %(node)s. Will try again on '
'the next poll. Error: %(error)s',
{'node': self.node.uuid,
'error': exception_error})
]
log_mock.warning.assert_has_calls(warning_calls)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_update_service', autospec=True)
def test_check_update_wait_elapsed(self, get_us_mock, log_mock):
mock_update_service = mock.Mock()
get_us_mock.return_value = mock_update_service
self._generate_new_driver_internal_info(['bmc'], add_wait=True)
task, interface = self._test__check_node_redfish_firmware_update()
debug_calls = [
mock.call('Finished waiting after firmware update '
'%(firmware_image)s on node %(node)s. '
'Elapsed time: %(seconds)s seconds',
{'firmware_image': 'https://bmc/v1.0.1',
'node': self.node.uuid, 'seconds': 60})]
log_mock.debug.assert_has_calls(debug_calls)
interface._continue_updates.assert_called_once_with(
task,
mock_update_service,
[{'component': 'bmc', 'url': 'https://bmc/v1.0.1',
'task_monitor': '/task/1'}])
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_update_service', autospec=True)
def test_check_update_still_waiting(self, get_us_mock, log_mock):
mock_update_service = mock.Mock()
get_us_mock.return_value = mock_update_service
self._generate_new_driver_internal_info(
['bios'], add_wait=True, wait=600)
_, interface = self._test__check_node_redfish_firmware_update()
debug_calls = [
mock.call('Continuing to wait after firmware update '
'%(firmware_image)s on node %(node)s. '
'Elapsed time: %(seconds)s seconds',
{'firmware_image': 'https://bios/v1.0.1',
'node': self.node.uuid, 'seconds': 60})]
log_mock.debug.assert_has_calls(debug_calls)
interface._continue_updates.assert_not_called()
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_update_service', autospec=True)
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
def test_check_update_task_monitor_not_found(self, tm_mock, get_us_mock,
log_mock):
tm_mock.side_effect = exception.RedfishError()
self._generate_new_driver_internal_info(['bios'])
task, interface = self._test__check_node_redfish_firmware_update()
warning_calls = [
mock.call('Firmware update completed for node %(node)s, '
'firmware %(firmware_image)s, but success of the '
'update is unknown. Assuming update was successful.',
{'node': self.node.uuid,
'firmware_image': 'https://bios/v1.0.1'})]
log_mock.warning.assert_has_calls(warning_calls)
interface._continue_updates.assert_called_once_with(
task, get_us_mock.return_value,
[{'component': 'bios', 'url': 'https://bios/v1.0.1',
'task_monitor': '/task/2'}]
)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_update_service', autospec=True)
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
def test__check_update_in_progress(self, tm_mock, get_us_mock, log_mock):
tm_mock.return_value.is_processing = True
self._generate_new_driver_internal_info(['bmc'])
_, interface = self._test__check_node_redfish_firmware_update()
debug_calls = [
mock.call('Firmware update in progress for node %(node)s, '
'firmware %(firmware_image)s.',
{'node': self.node.uuid,
'firmware_image': 'https://bmc/v1.0.1'})]
log_mock.debug.assert_has_calls(debug_calls)
interface._continue_updates.assert_not_called()
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
@mock.patch.object(redfish_utils, 'get_update_service', autospec=True)
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
def test__check_node_firmware_update_fail(self, tm_mock, get_us_mock,
cleaning_error_handler_mock):
mock_sushy_task = mock.Mock()
mock_sushy_task.task_state = 'exception'
mock_message_unparsed = mock.Mock()
mock_message_unparsed.message = None
message_mock = mock.Mock()
message_mock.message = 'Firmware upgrade failed'
messages = mock.MagicMock(return_value=[[mock_message_unparsed],
[message_mock],
[message_mock]])
mock_sushy_task.messages = messages
mock_task_monitor = mock.Mock()
mock_task_monitor.is_processing = False
mock_task_monitor.get_task.return_value = mock_sushy_task
tm_mock.return_value = mock_task_monitor
self._generate_new_driver_internal_info(['bmc'])
task, interface = self._test__check_node_redfish_firmware_update()
task.upgrade_lock.assert_called_once_with()
cleaning_error_handler_mock.assert_called_once()
interface._continue_updates.assert_not_called()
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(redfish_utils, 'get_update_service', autospec=True)
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
def test__check_node_firmware_update_done(self, tm_mock, get_us_mock,
log_mock):
task_mock = mock.Mock()
task_mock.task_state = sushy.TASK_STATE_COMPLETED
task_mock.task_status = sushy.HEALTH_OK
message_mock = mock.Mock()
message_mock.message = 'Firmware update done'
task_mock.messages = [message_mock]
mock_task_monitor = mock.Mock()
mock_task_monitor.is_processing = False
mock_task_monitor.get_task.return_value = task_mock
tm_mock.return_value = mock_task_monitor
self._generate_new_driver_internal_info(['bmc'])
task, interface = self._test__check_node_redfish_firmware_update()
task.upgrade_lock.assert_called_once_with()
info_calls = [
mock.call('Firmware update succeeded for node %(node)s, '
'firmware %(firmware_image)s: %(messages)s',
{'node': self.node.uuid,
'firmware_image': 'https://bmc/v1.0.1',
'messages': 'Firmware update done'})]
log_mock.info.assert_has_calls(info_calls)
interface._continue_updates.assert_called_once_with(
task, get_us_mock.return_value,
[{'component': 'bmc', 'url': 'https://bmc/v1.0.1',
'task_monitor': '/task/1'}]
)
@mock.patch.object(firmware_utils, 'download_to_temp', autospec=True)
@mock.patch.object(firmware_utils, 'stage', autospec=True)
def test__stage_firmware_file_https(self, stage_mock, dwl_tmp_mock):
CONF.set_override('firmware_source', 'local', 'redfish')
firmware_update = {'url': 'https://test1', 'component': 'bmc'}
node = mock.Mock()
dwl_tmp_mock.return_value = '/tmp/test1'
stage_mock.return_value = ('http://staged/test1', 'http')
firmware = redfish_fw.RedfishFirmware()
staged_url, needs_cleanup = firmware._stage_firmware_file(
node, firmware_update)
self.assertEqual(staged_url, 'http://staged/test1')
self.assertEqual(needs_cleanup, 'http')
dwl_tmp_mock.assert_called_with(node, 'https://test1')
stage_mock.assert_called_with(node, 'local', '/tmp/test1')
@mock.patch.object(firmware_utils, 'download_to_temp', autospec=True)
@mock.patch.object(firmware_utils, 'stage', autospec=True)
@mock.patch.object(firmware_utils, 'get_swift_temp_url', autospec=True)
def test__stage_firmware_file_swift(
self, get_swift_tmp_url_mock, stage_mock, dwl_tmp_mock):
CONF.set_override('firmware_source', 'swift', 'redfish')
firmware_update = {'url': 'swift://container/bios.exe',
'component': 'bios'}
node = mock.Mock()
get_swift_tmp_url_mock.return_value = 'http://temp'
firmware = redfish_fw.RedfishFirmware()
staged_url, needs_cleanup = firmware._stage_firmware_file(
node, firmware_update)
self.assertEqual(staged_url, 'http://temp')
self.assertIsNone(needs_cleanup)
dwl_tmp_mock.assert_not_called()
stage_mock.assert_not_called()
@mock.patch.object(firmware_utils, 'cleanup', autospec=True)
@mock.patch.object(firmware_utils, 'download_to_temp', autospec=True)
@mock.patch.object(firmware_utils, 'stage', autospec=True)
def test__stage_firmware_file_error(self, stage_mock, dwl_tmp_mock,
cleanup_mock):
CONF.set_override('firmware_source', 'local', 'redfish')
node = mock.Mock()
firmware_update = {'url': 'https://test1', 'component': 'bmc'}
dwl_tmp_mock.return_value = '/tmp/test1'
stage_mock.side_effect = exception.IronicException
firmware = redfish_fw.RedfishFirmware()
self.assertRaises(exception.IronicException,
firmware._stage_firmware_file, node,
firmware_update)
dwl_tmp_mock.assert_called_with(node, 'https://test1')
stage_mock.assert_called_with(node, 'local', '/tmp/test1')
cleanup_mock.assert_called_with(node)
def _test_continue_updates(self):
update_service_mock = mock.Mock()
firmware = redfish_fw.RedfishFirmware()
updates = self.node.driver_internal_info.get('redfish_fw_updates')
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
firmware._continue_updates(
task,
update_service_mock,
updates
)
return task
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
def test_continue_update_waitting(self, log_mock):
self._generate_new_driver_internal_info(['bmc', 'bios'],
add_wait=True, wait=120)
self._test_continue_updates()
debug_call = [
mock.call('Waiting at %(time)s for %(seconds)s seconds '
'after %(component)s firmware update %(url)s '
'on node %(node)s',
{'time': mock.ANY, 'seconds': 120,
'component': 'bmc', 'url': 'https://bmc/v1.0.1',
'node': self.node.uuid})
]
log_mock.debug.assert_has_calls(debug_call)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
autospec=True)
def test_continue_updates_last(self, cond_resume_clean_mock, log_mock):
self._generate_new_driver_internal_info(['bmc'])
task = self._test_continue_updates()
cond_resume_clean_mock.assert_called_once_with(task)
info_call = [
mock.call('Firmware updates completed for node %(node)s',
{'node': self.node.uuid})
]
log_mock.info.assert_has_calls(info_call)
@mock.patch.object(redfish_fw, 'LOG', autospec=True)
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
def test_continue_updates_more_updates(self, node_power_action_mock,
log_mock):
self._generate_new_driver_internal_info(['bmc', 'bios'])
task_monitor_mock = mock.Mock()
task_monitor_mock.task_monitor_uri = '/task/2'
update_service_mock = mock.Mock()
update_service_mock.simple_update.return_value = task_monitor_mock
firmware = redfish_fw.RedfishFirmware()
updates = self.node.driver_internal_info.get('redfish_fw_updates')
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.save = mock.Mock()
firmware._continue_updates(task, update_service_mock, updates)
debug_calls = [
mock.call('Applying new firmware %(url)s for '
'%(component)s on node %(node_uuid)s',
{'url': 'https://bios/v1.0.1', 'component': 'bios',
'node_uuid': self.node.uuid})
]
log_mock.debug.assert_has_calls(debug_calls)
self.assertEqual(
[{'component': 'bios', 'url': 'https://bios/v1.0.1',
'task_monitor': '/task/2'}],
task.node.driver_internal_info['redfish_fw_updates'])
update_service_mock.simple_update.assert_called_once_with(
'https://bios/v1.0.1')
task.node.save.assert_called_once_with()
node_power_action_mock.assert_called_once_with(task, states.REBOOT)