# -*- coding: utf-8 -*- # Copyright (c) Ansible project # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import (absolute_import, division, print_function) __metaclass__ = type import shutil import uuid import tarfile import tempfile import os from ansible_collections.community.general.tests.unit.compat.mock import patch from ansible_collections.community.general.tests.unit.compat import unittest from ansible.module_utils import basic import ansible_collections.community.general.plugins.modules.wdc_redfish_command as module from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson from ansible_collections.community.general.tests.unit.plugins.modules.utils import set_module_args, exit_json, fail_json MOCK_SUCCESSFUL_HTTP_EMPTY_RESPONSE = { "ret": True, "data": { } } MOCK_GET_ENCLOSURE_RESPONSE_SINGLE_TENANT = { "ret": True, "data": { "SerialNumber": "12345" } } MOCK_GET_ENCLOSURE_RESPONSE_MULTI_TENANT = { "ret": True, "data": { "SerialNumber": "12345-A" } } MOCK_URL_ERROR = { "ret": False, "msg": "This is a mock URL error", "status": 500 } MOCK_SUCCESSFUL_RESPONSE_WITH_UPDATE_SERVICE_RESOURCE = { "ret": True, "data": { "UpdateService": { "@odata.id": "/UpdateService" }, "Chassis": { "@odata.id": "/Chassis" } } } MOCK_SUCCESSFUL_RESPONSE_CHASSIS = { "ret": True, "data": { "Members": [ { "@odata.id": "/redfish/v1/Chassis/Enclosure" } ] } } MOCK_SUCCESSFUL_RESPONSE_CHASSIS_ENCLOSURE = { "ret": True, "data": { "Id": "Enclosure", "IndicatorLED": "Off", "Actions": { "Oem": { "WDC": { "#Chassis.Locate": { "target": "/Chassis.Locate" }, "#Chassis.PowerMode": { "target": "/redfish/v1/Chassis/Enclosure/Actions/Chassis.PowerMode", } } } }, "Oem": { "WDC": { "PowerMode": "Normal" } } } } MOCK_SUCCESSFUL_RESPONSE_WITH_SIMPLE_UPDATE_AND_FW_ACTIVATE = { "ret": True, "data": { "Actions": { "#UpdateService.SimpleUpdate": { "target": "mocked value" }, "Oem": { "WDC": { "#UpdateService.FWActivate": { "title": "Activate the downloaded firmware.", "target": "/redfish/v1/UpdateService/Actions/UpdateService.FWActivate" } } } } } } MOCK_SUCCESSFUL_RESPONSE_WITH_ACTIONS = { "ret": True, "data": { "Actions": {} } } MOCK_GET_IOM_A_MULTI_TENANT = { "ret": True, "data": { "Id": "IOModuleAFRU" } } MOCK_GET_IOM_B_MULTI_TENANAT = { "ret": True, "data": { "error": { "message": "IOM Module B cannot be read" } } } MOCK_READY_FOR_FW_UPDATE = { "ret": True, "entries": { "Description": "Ready for FW update", "StatusCode": 0 } } MOCK_FW_UPDATE_IN_PROGRESS = { "ret": True, "entries": { "Description": "FW update in progress", "StatusCode": 1 } } MOCK_WAITING_FOR_ACTIVATION = { "ret": True, "entries": { "Description": "FW update completed. Waiting for activation.", "StatusCode": 2 } } MOCK_SIMPLE_UPDATE_STATUS_LIST = [ MOCK_READY_FOR_FW_UPDATE, MOCK_FW_UPDATE_IN_PROGRESS, MOCK_WAITING_FOR_ACTIVATION ] def get_bin_path(self, arg, required=False): """Mock AnsibleModule.get_bin_path""" return arg def get_exception_message(ansible_exit_json): """From an AnsibleExitJson exception, get the message string.""" return ansible_exit_json.exception.args[0]["msg"] def is_changed(ansible_exit_json): """From an AnsibleExitJson exception, return the value of the changed flag""" return ansible_exit_json.exception.args[0]["changed"] def mock_simple_update(*args, **kwargs): return { "ret": True } def mocked_url_response(*args, **kwargs): """Mock to just return a generic string.""" return "/mockedUrl" def mock_update_url(*args, **kwargs): """Mock of the update url""" return "/UpdateService" def mock_fw_activate_url(*args, **kwargs): """Mock of the FW Activate URL""" return "/UpdateService.FWActivate" def empty_return(*args, **kwargs): """Mock to just return an empty successful return.""" return {"ret": True} def mock_get_simple_update_status_ready_for_fw_update(*args, **kwargs): """Mock to return simple update status Ready for FW update""" return MOCK_READY_FOR_FW_UPDATE def mock_get_request_enclosure_single_tenant(*args, **kwargs): """Mock for get_request for single-tenant enclosure.""" if args[1].endswith("/redfish/v1") or args[1].endswith("/redfish/v1/"): return MOCK_SUCCESSFUL_RESPONSE_WITH_UPDATE_SERVICE_RESOURCE elif args[1].endswith("/mockedUrl"): return MOCK_SUCCESSFUL_HTTP_EMPTY_RESPONSE elif args[1].endswith("Chassis/Enclosure"): return MOCK_GET_ENCLOSURE_RESPONSE_SINGLE_TENANT elif args[1].endswith("/UpdateService"): return MOCK_SUCCESSFUL_RESPONSE_WITH_SIMPLE_UPDATE_AND_FW_ACTIVATE else: raise RuntimeError("Illegal call to get_request in test: " + args[1]) def mock_get_request_enclosure_multi_tenant(*args, **kwargs): """Mock for get_request with multi-tenant enclosure.""" if args[1].endswith("/redfish/v1") or args[1].endswith("/redfish/v1/"): return MOCK_SUCCESSFUL_RESPONSE_WITH_UPDATE_SERVICE_RESOURCE elif args[1].endswith("/mockedUrl"): return MOCK_SUCCESSFUL_HTTP_EMPTY_RESPONSE elif args[1].endswith("Chassis/Enclosure"): return MOCK_GET_ENCLOSURE_RESPONSE_MULTI_TENANT elif args[1].endswith("/UpdateService"): return MOCK_SUCCESSFUL_RESPONSE_WITH_SIMPLE_UPDATE_AND_FW_ACTIVATE elif args[1].endswith("/IOModuleAFRU"): return MOCK_GET_IOM_A_MULTI_TENANT elif args[1].endswith("/IOModuleBFRU"): return MOCK_GET_IOM_B_MULTI_TENANAT else: raise RuntimeError("Illegal call to get_request in test: " + args[1]) def mock_get_request(*args, **kwargs): """Mock for get_request for simple resource tests.""" if args[1].endswith("/redfish/v1") or args[1].endswith("/redfish/v1/"): return MOCK_SUCCESSFUL_RESPONSE_WITH_UPDATE_SERVICE_RESOURCE elif args[1].endswith("/Chassis"): return MOCK_SUCCESSFUL_RESPONSE_CHASSIS elif args[1].endswith("Chassis/Enclosure"): return MOCK_SUCCESSFUL_RESPONSE_CHASSIS_ENCLOSURE else: raise RuntimeError("Illegal call to get_request in test: " + args[1]) def mock_post_request(*args, **kwargs): """Mock post_request with successful response.""" valid_endpoints = [ "/UpdateService.FWActivate", "/Chassis.Locate", "/Chassis.PowerMode", ] for endpoint in valid_endpoints: if args[1].endswith(endpoint): return { "ret": True, "data": ACTION_WAS_SUCCESSFUL_MESSAGE } raise RuntimeError("Illegal POST call to: " + args[1]) def mock_get_firmware_inventory_version_1_2_3(*args, **kwargs): return { "ret": True, "entries": [ { "Id": "IOModuleA_OOBM", "Version": "1.2.3" }, { "Id": "IOModuleB_OOBM", "Version": "1.2.3" } ] } ERROR_MESSAGE_UNABLE_TO_EXTRACT_BUNDLE_VERSION = "Unable to extract bundle version or multi-tenant status or generation from update image file" ACTION_WAS_SUCCESSFUL_MESSAGE = "Action was successful" class TestWdcRedfishCommand(unittest.TestCase): def setUp(self): self.mock_module_helper = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json, get_bin_path=get_bin_path) self.mock_module_helper.start() self.addCleanup(self.mock_module_helper.stop) self.tempdir = tempfile.mkdtemp() def tearDown(self): shutil.rmtree(self.tempdir) def test_module_fail_when_required_args_missing(self): with self.assertRaises(AnsibleFailJson): set_module_args({}) module.main() def test_module_fail_when_unknown_category(self): with self.assertRaises(AnsibleFailJson): set_module_args({ 'category': 'unknown', 'command': 'FWActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': [], }) module.main() def test_module_fail_when_unknown_command(self): with self.assertRaises(AnsibleFailJson): set_module_args({ 'category': 'Update', 'command': 'unknown', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': [], }) module.main() def test_module_chassis_power_mode_low(self): """Test setting chassis power mode to low (happy path).""" module_args = { 'category': 'Chassis', 'command': 'PowerModeLow', 'username': 'USERID', 'password': 'PASSW0RD=21', 'resource_id': 'Enclosure', 'baseuri': 'example.com' } set_module_args(module_args) with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils", get_request=mock_get_request, post_request=mock_post_request): with self.assertRaises(AnsibleExitJson) as ansible_exit_json: module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE, get_exception_message(ansible_exit_json)) self.assertTrue(is_changed(ansible_exit_json)) def test_module_chassis_power_mode_normal_when_already_normal(self): """Test setting chassis power mode to normal when it already is. Verify we get changed=False.""" module_args = { 'category': 'Chassis', 'command': 'PowerModeNormal', 'username': 'USERID', 'password': 'PASSW0RD=21', 'resource_id': 'Enclosure', 'baseuri': 'example.com' } set_module_args(module_args) with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils", get_request=mock_get_request): with self.assertRaises(AnsibleExitJson) as ansible_exit_json: module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE, get_exception_message(ansible_exit_json)) self.assertFalse(is_changed(ansible_exit_json)) def test_module_chassis_power_mode_invalid_command(self): """Test that we get an error when issuing an invalid PowerMode command.""" module_args = { 'category': 'Chassis', 'command': 'PowerModeExtraHigh', 'username': 'USERID', 'password': 'PASSW0RD=21', 'resource_id': 'Enclosure', 'baseuri': 'example.com' } set_module_args(module_args) with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils", get_request=mock_get_request): with self.assertRaises(AnsibleFailJson) as ansible_fail_json: module.main() expected_error_message = "Invalid Command 'PowerModeExtraHigh'" self.assertIn(expected_error_message, get_exception_message(ansible_fail_json)) def test_module_enclosure_led_indicator_on(self): """Test turning on a valid LED indicator (in this case we use the Enclosure resource).""" module_args = { 'category': 'Chassis', 'command': 'IndicatorLedOn', 'username': 'USERID', 'password': 'PASSW0RD=21', "resource_id": "Enclosure", "baseuri": "example.com" } set_module_args(module_args) with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils", get_request=mock_get_request, post_request=mock_post_request): with self.assertRaises(AnsibleExitJson) as ansible_exit_json: module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE, get_exception_message(ansible_exit_json)) self.assertTrue(is_changed(ansible_exit_json)) def test_module_invalid_resource_led_indicator_on(self): """Test turning LED on for an invalid resource id.""" module_args = { 'category': 'Chassis', 'command': 'IndicatorLedOn', 'username': 'USERID', 'password': 'PASSW0RD=21', "resource_id": "Disk99", "baseuri": "example.com" } set_module_args(module_args) with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils", get_request=mock_get_request, post_request=mock_post_request): with self.assertRaises(AnsibleFailJson) as ansible_fail_json: module.main() expected_error_message = "Chassis resource Disk99 not found" self.assertEqual(expected_error_message, get_exception_message(ansible_fail_json)) def test_module_enclosure_led_off_already_off(self): """Test turning LED indicator off when it's already off. Confirm changed is False and no POST occurs.""" module_args = { 'category': 'Chassis', 'command': 'IndicatorLedOff', 'username': 'USERID', 'password': 'PASSW0RD=21', "resource_id": "Enclosure", "baseuri": "example.com" } set_module_args(module_args) with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils", get_request=mock_get_request): with self.assertRaises(AnsibleExitJson) as ansible_exit_json: module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE, get_exception_message(ansible_exit_json)) self.assertFalse(is_changed(ansible_exit_json)) def test_module_fw_activate_first_iom_unavailable(self): """Test that if the first IOM is not available, the 2nd one is used.""" ioms = [ "bad.example.com", "good.example.com" ] module_args = { 'category': 'Update', 'command': 'FWActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ioms } set_module_args(module_args) def mock_get_request(*args, **kwargs): """Mock for get_request that will fail on the 'bad' IOM.""" if "bad.example.com" in args[1]: return MOCK_URL_ERROR else: return mock_get_request_enclosure_single_tenant(*args, **kwargs) with patch.multiple(module.WdcRedfishUtils, _firmware_activate_uri=mock_fw_activate_url, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return, get_request=mock_get_request, post_request=mock_post_request): with self.assertRaises(AnsibleExitJson) as cm: module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE, get_exception_message(cm)) def test_module_fw_activate_pass(self): """Test the FW Activate command in a passing scenario.""" # Run the same test twice -- once specifying ioms, and once specifying baseuri. # Both should work the same way. uri_specifiers = [ { "ioms": ["example1.example.com"] }, { "baseuri": "example1.example.com" } ] for uri_specifier in uri_specifiers: module_args = { 'category': 'Update', 'command': 'FWActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', } module_args.update(uri_specifier) set_module_args(module_args) with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils", _firmware_activate_uri=mock_fw_activate_url, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return, get_request=mock_get_request_enclosure_single_tenant, post_request=mock_post_request): with self.assertRaises(AnsibleExitJson) as ansible_exit_json: module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE, get_exception_message(ansible_exit_json)) self.assertTrue(is_changed(ansible_exit_json)) def test_module_fw_activate_service_does_not_support_fw_activate(self): """Test FW Activate when it is not supported.""" expected_error_message = "Service does not support FWActivate" set_module_args({ 'category': 'Update', 'command': 'FWActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"] }) def mock_update_uri_response(*args, **kwargs): return { "ret": True, "data": {} # No Actions } with patch.multiple(module.WdcRedfishUtils, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return, get_request=mock_update_uri_response): with self.assertRaises(AnsibleFailJson) as cm: module.main() self.assertEqual(expected_error_message, get_exception_message(cm)) def test_module_update_and_activate_image_uri_not_http(self): """Test Update and Activate when URI is not http(s)""" expected_error_message = "Bundle URI must be HTTP or HTTPS" set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "ftp://example.com/image" }) with patch.multiple(module.WdcRedfishUtils, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return): with self.assertRaises(AnsibleFailJson) as cm: module.main() self.assertEqual(expected_error_message, get_exception_message(cm)) def test_module_update_and_activate_target_not_ready_for_fw_update(self): """Test Update and Activate when target is not in the correct state.""" mock_status_code = 999 mock_status_description = "mock status description" expected_error_message = "Target is not ready for FW update. Current status: {0} ({1})".format( mock_status_code, mock_status_description ) set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image" }) with patch.object(module.WdcRedfishUtils, "get_simple_update_status") as mock_get_simple_update_status: mock_get_simple_update_status.return_value = { "ret": True, "entries": { "StatusCode": mock_status_code, "Description": mock_status_description } } with patch.multiple(module.WdcRedfishUtils, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return): with self.assertRaises(AnsibleFailJson) as cm: module.main() self.assertEqual(expected_error_message, get_exception_message(cm)) def test_module_update_and_activate_bundle_not_a_tarfile(self): """Test Update and Activate when bundle is not a tarfile""" mock_filename = os.path.abspath(__file__) expected_error_message = ERROR_MESSAGE_UNABLE_TO_EXTRACT_BUNDLE_VERSION set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image", "update_creds": { "username": "image_user", "password": "image_password" } }) with patch('ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.fetch_file') as mock_fetch_file: mock_fetch_file.return_value = mock_filename with patch.multiple(module.WdcRedfishUtils, get_simple_update_status=mock_get_simple_update_status_ready_for_fw_update, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return): with self.assertRaises(AnsibleFailJson) as cm: module.main() self.assertEqual(expected_error_message, get_exception_message(cm)) def test_module_update_and_activate_bundle_contains_no_firmware_version(self): """Test Update and Activate when bundle contains no firmware version""" expected_error_message = ERROR_MESSAGE_UNABLE_TO_EXTRACT_BUNDLE_VERSION set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image", "update_creds": { "username": "image_user", "password": "image_password" } }) tar_name = "empty_tarfile{0}.tar".format(uuid.uuid4()) empty_tarfile = tarfile.open(os.path.join(self.tempdir, tar_name), "w") empty_tarfile.close() with patch('ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.fetch_file') as mock_fetch_file: mock_fetch_file.return_value = os.path.join(self.tempdir, tar_name) with patch.multiple(module.WdcRedfishUtils, get_simple_update_status=mock_get_simple_update_status_ready_for_fw_update, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return): with self.assertRaises(AnsibleFailJson) as cm: module.main() self.assertEqual(expected_error_message, get_exception_message(cm)) def test_module_update_and_activate_version_already_installed(self): """Test Update and Activate when the bundle version is already installed""" mock_firmware_version = "1.2.3" expected_error_message = ACTION_WAS_SUCCESSFUL_MESSAGE set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image", "update_creds": { "username": "image_user", "password": "image_password" } }) tar_name = self.generate_temp_bundlefile(mock_firmware_version=mock_firmware_version, is_multi_tenant=False) with patch('ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.fetch_file') as mock_fetch_file: mock_fetch_file.return_value = os.path.join(self.tempdir, tar_name) with patch.multiple(module.WdcRedfishUtils, get_firmware_inventory=mock_get_firmware_inventory_version_1_2_3, get_simple_update_status=mock_get_simple_update_status_ready_for_fw_update, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return, get_request=mock_get_request_enclosure_single_tenant): with self.assertRaises(AnsibleExitJson) as result: module.main() self.assertEqual(expected_error_message, get_exception_message(result)) self.assertFalse(is_changed(result)) def test_module_update_and_activate_version_already_installed_multi_tenant(self): """Test Update and Activate on multi-tenant when version is already installed""" mock_firmware_version = "1.2.3" expected_error_message = ACTION_WAS_SUCCESSFUL_MESSAGE set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image", "update_creds": { "username": "image_user", "password": "image_password" } }) tar_name = self.generate_temp_bundlefile(mock_firmware_version=mock_firmware_version, is_multi_tenant=True) with patch('ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.fetch_file') as mock_fetch_file: mock_fetch_file.return_value = os.path.join(self.tempdir, tar_name) with patch.multiple(module.WdcRedfishUtils, get_firmware_inventory=mock_get_firmware_inventory_version_1_2_3, get_simple_update_status=mock_get_simple_update_status_ready_for_fw_update, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return, get_request=mock_get_request_enclosure_multi_tenant): with self.assertRaises(AnsibleExitJson) as result: module.main() self.assertEqual(expected_error_message, get_exception_message(result)) self.assertFalse(is_changed(result)) def test_module_update_and_activate_pass(self): """Test Update and Activate (happy path)""" mock_firmware_version = "1.2.2" set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image", "update_creds": { "username": "image_user", "password": "image_password" } }) tar_name = self.generate_temp_bundlefile(mock_firmware_version=mock_firmware_version, is_multi_tenant=False) with patch('ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.fetch_file') as mock_fetch_file: mock_fetch_file.return_value = os.path.join(self.tempdir, tar_name) with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils", get_firmware_inventory=mock_get_firmware_inventory_version_1_2_3, simple_update=mock_simple_update, _simple_update_status_uri=mocked_url_response, # _find_updateservice_resource=empty_return, # _find_updateservice_additional_uris=empty_return, get_request=mock_get_request_enclosure_single_tenant, post_request=mock_post_request): with patch("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils.get_simple_update_status" ) as mock_get_simple_update_status: mock_get_simple_update_status.side_effect = MOCK_SIMPLE_UPDATE_STATUS_LIST with self.assertRaises(AnsibleExitJson) as ansible_exit_json: module.main() self.assertTrue(is_changed(ansible_exit_json)) self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE, get_exception_message(ansible_exit_json)) def test_module_update_and_activate_pass_multi_tenant(self): """Test Update and Activate with multi-tenant (happy path)""" mock_firmware_version = "1.2.2" set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image", "update_creds": { "username": "image_user", "password": "image_password" } }) tar_name = self.generate_temp_bundlefile(mock_firmware_version=mock_firmware_version, is_multi_tenant=True) with patch('ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.fetch_file') as mock_fetch_file: mock_fetch_file.return_value = os.path.join(self.tempdir, tar_name) with patch.multiple(module.WdcRedfishUtils, get_firmware_inventory=mock_get_firmware_inventory_version_1_2_3, simple_update=mock_simple_update, _simple_update_status_uri=mocked_url_response, # _find_updateservice_resource=empty_return, # _find_updateservice_additional_uris=empty_return, get_request=mock_get_request_enclosure_multi_tenant, post_request=mock_post_request): with patch("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils.get_simple_update_status" ) as mock_get_simple_update_status: mock_get_simple_update_status.side_effect = MOCK_SIMPLE_UPDATE_STATUS_LIST with self.assertRaises(AnsibleExitJson) as ansible_exit_json: module.main() self.assertTrue(is_changed(ansible_exit_json)) self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE, get_exception_message(ansible_exit_json)) def test_module_fw_update_multi_tenant_firmware_single_tenant_enclosure(self): """Test Update and Activate using multi-tenant bundle on single-tenant enclosure""" mock_firmware_version = "1.1.1" expected_error_message = "Enclosure multi-tenant is False but bundle multi-tenant is True" set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image", "update_creds": { "username": "image_user", "password": "image_password" } }) tar_name = self.generate_temp_bundlefile(mock_firmware_version=mock_firmware_version, is_multi_tenant=True) with patch('ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.fetch_file') as mock_fetch_file: mock_fetch_file.return_value = os.path.join(self.tempdir, tar_name) with patch.multiple(module.WdcRedfishUtils, get_firmware_inventory=mock_get_firmware_inventory_version_1_2_3(), get_simple_update_status=mock_get_simple_update_status_ready_for_fw_update, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return, get_request=mock_get_request_enclosure_single_tenant): with self.assertRaises(AnsibleFailJson) as result: module.main() self.assertEqual(expected_error_message, get_exception_message(result)) def test_module_fw_update_single_tentant_firmware_multi_tenant_enclosure(self): """Test Update and Activate using singe-tenant bundle on multi-tenant enclosure""" mock_firmware_version = "1.1.1" expected_error_message = "Enclosure multi-tenant is True but bundle multi-tenant is False" set_module_args({ 'category': 'Update', 'command': 'UpdateAndActivate', 'username': 'USERID', 'password': 'PASSW0RD=21', 'ioms': ["example1.example.com"], 'update_image_uri': "http://example.com/image", "update_creds": { "username": "image_user", "password": "image_password" } }) tar_name = self.generate_temp_bundlefile(mock_firmware_version=mock_firmware_version, is_multi_tenant=False) with patch('ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.fetch_file') as mock_fetch_file: mock_fetch_file.return_value = os.path.join(self.tempdir, tar_name) with patch.multiple(module.WdcRedfishUtils, get_firmware_inventory=mock_get_firmware_inventory_version_1_2_3(), get_simple_update_status=mock_get_simple_update_status_ready_for_fw_update, _firmware_activate_uri=mocked_url_response, _update_uri=mock_update_url, _find_updateservice_resource=empty_return, _find_updateservice_additional_uris=empty_return, get_request=mock_get_request_enclosure_multi_tenant): with self.assertRaises(AnsibleFailJson) as result: module.main() self.assertEqual(expected_error_message, get_exception_message(result)) def generate_temp_bundlefile(self, mock_firmware_version, is_multi_tenant): """Generate a temporary fake bundle file. :param str mock_firmware_version: The simulated firmware version for the bundle. :param bool is_multi_tenant: Is the simulated bundle multi-tenant? This can be used for a mock FW update. """ tar_name = "tarfile{0}.tar".format(uuid.uuid4()) bundle_tarfile = tarfile.open(os.path.join(self.tempdir, tar_name), "w") package_filename = "oobm-{0}.pkg".format(mock_firmware_version) package_filename_path = os.path.join(self.tempdir, package_filename) package_file = open(package_filename_path, "w") package_file.close() bundle_tarfile.add(os.path.join(self.tempdir, package_filename), arcname=package_filename) bin_filename = "firmware.bin" bin_filename_path = os.path.join(self.tempdir, bin_filename) bin_file = open(bin_filename_path, "wb") byte_to_write = b'\x80' if is_multi_tenant else b'\xFF' bin_file.write(byte_to_write * 12) bin_file.close() for filename in [package_filename, bin_filename]: bundle_tarfile.add(os.path.join(self.tempdir, filename), arcname=filename) bundle_tarfile.close() return tar_name