mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 12:50:22 -07:00
Redfish modules for Western Digital UltraStar Data102 storage enclosures (#4885)
* WDC Redfish Info / Command modules for Western Digital Ultrastar Data102 storage enclosures. Initial commands include: * FWActivate * UpdateAndActivate * SimpleUpdateStatus * delete unnecessary __init__.py modules * PR Feedback Notes list not guaranteed to be sorted Use EXAMPLES tos how specifying ioms/basuri Import missing_required_lib * Apply suggestions from code review Suggestions that could be auto-committed. Co-authored-by: Felix Fontein <felix@fontein.de> * Remove DNSCacheBypass It is now the caller's responsibility to deal with stale IP addresses. * Remove dnspython dependency. Fix bug that this uncovered. * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> * PR Feedback * Documentation, simple update status output format, unit tests. Add docs showing how to use SimpleUpdateStatus Change the format of SimpleUpateStatus format, put the results in a sub-object. Fix unit tests whose asserts weren't actually running. * PR Feedback register: result on the 2nd example * Final adjustments for merging for 5.4.0 Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
ade54bceb8
commit
be70d18e3f
7 changed files with 1827 additions and 0 deletions
|
@ -0,0 +1,733 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
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.remote_management.redfish.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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_post_request(*args, **kwargs):
|
||||
"""Mock post_request with successful response."""
|
||||
if args[1].endswith("/UpdateService.FWActivate"):
|
||||
return {
|
||||
"ret": True,
|
||||
"data": ACTION_WAS_SUCCESSFUL_MESSAGE
|
||||
}
|
||||
else:
|
||||
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 from update image tarfile"
|
||||
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_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
|
|
@ -0,0 +1,214 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
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.remote_management.redfish.wdc_redfish_info 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_RESPONSE_WITH_ACTIONS = {
|
||||
"ret": True,
|
||||
"data": {
|
||||
"Actions": {}
|
||||
}
|
||||
}
|
||||
|
||||
MOCK_SUCCESSFUL_HTTP_EMPTY_RESPONSE = {
|
||||
"ret": True,
|
||||
"data": {
|
||||
}
|
||||
}
|
||||
|
||||
MOCK_SUCCESSFUL_RESPONSE_WITH_UPDATE_SERVICE_RESOURCE = {
|
||||
"ret": True,
|
||||
"data": {
|
||||
"UpdateService": {
|
||||
"@odata.id": "/UpdateService"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MOCK_SUCCESSFUL_RESPONSE_WITH_SIMPLE_UPDATE_BUT_NO_FW_ACTIVATE = {
|
||||
"ret": True,
|
||||
"data": {
|
||||
"Actions": {
|
||||
"#UpdateService.SimpleUpdate": {
|
||||
"target": "mocked value"
|
||||
},
|
||||
"Oem": {
|
||||
"WDC": {} # No #UpdateService.FWActivate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_bin_path(self, arg, required=False):
|
||||
"""Mock AnsibleModule.get_bin_path"""
|
||||
return arg
|
||||
|
||||
|
||||
def get_redfish_facts(ansible_exit_json):
|
||||
"""From an AnsibleExitJson exception, get the redfish facts dict."""
|
||||
return ansible_exit_json.exception.args[0]["redfish_facts"]
|
||||
|
||||
|
||||
def get_exception_message(ansible_exit_json):
|
||||
"""From an AnsibleExitJson exception, get the message string."""
|
||||
return ansible_exit_json.exception.args[0]["msg"]
|
||||
|
||||
|
||||
class TestWdcRedfishInfo(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)
|
||||
|
||||
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': 'SimpleUpdateStatus',
|
||||
'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_simple_update_status_pass(self):
|
||||
set_module_args({
|
||||
'category': 'Update',
|
||||
'command': 'SimpleUpdateStatus',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'ioms': ["example1.example.com"],
|
||||
})
|
||||
|
||||
def mock_simple_update_status(*args, **kwargs):
|
||||
return {
|
||||
"ret": True,
|
||||
"data": {
|
||||
"Description": "Ready for FW update",
|
||||
"ErrorCode": 0,
|
||||
"EstimatedRemainingMinutes": 0,
|
||||
"StatusCode": 0
|
||||
}
|
||||
}
|
||||
|
||||
def mocked_string_response(*args, **kwargs):
|
||||
return "mockedUrl"
|
||||
|
||||
def empty_return(*args, **kwargs):
|
||||
return {"ret": True}
|
||||
|
||||
with patch.multiple(module.WdcRedfishUtils,
|
||||
_simple_update_status_uri=mocked_string_response,
|
||||
_find_updateservice_resource=empty_return,
|
||||
_find_updateservice_additional_uris=empty_return,
|
||||
get_request=mock_simple_update_status):
|
||||
with self.assertRaises(AnsibleExitJson) as ansible_exit_json:
|
||||
module.main()
|
||||
redfish_facts = get_redfish_facts(ansible_exit_json)
|
||||
self.assertEqual(mock_simple_update_status()["data"],
|
||||
redfish_facts["simple_update_status"]["entries"])
|
||||
|
||||
def test_module_simple_update_status_updateservice_resource_not_found(self):
|
||||
set_module_args({
|
||||
'category': 'Update',
|
||||
'command': 'SimpleUpdateStatus',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'ioms': ["example1.example.com"],
|
||||
})
|
||||
with patch.object(module.WdcRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {
|
||||
"ret": True,
|
||||
"data": {} # Missing UpdateService property
|
||||
}
|
||||
with self.assertRaises(AnsibleFailJson) as ansible_exit_json:
|
||||
module.main()
|
||||
self.assertEqual("UpdateService resource not found",
|
||||
get_exception_message(ansible_exit_json))
|
||||
|
||||
def test_module_simple_update_status_service_does_not_support_simple_update(self):
|
||||
set_module_args({
|
||||
'category': 'Update',
|
||||
'command': 'SimpleUpdateStatus',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'ioms': ["example1.example.com"],
|
||||
})
|
||||
|
||||
def mock_get_request_function(uri):
|
||||
mock_url_string = "mockURL"
|
||||
if mock_url_string in uri:
|
||||
return {
|
||||
"ret": True,
|
||||
"data": {
|
||||
"Actions": { # No #UpdateService.SimpleUpdate
|
||||
}
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"ret": True,
|
||||
"data": mock_url_string
|
||||
}
|
||||
|
||||
with patch.object(module.WdcRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.side_effect = mock_get_request_function
|
||||
with self.assertRaises(AnsibleFailJson) as ansible_exit_json:
|
||||
module.main()
|
||||
self.assertEqual("UpdateService resource not found",
|
||||
get_exception_message(ansible_exit_json))
|
||||
|
||||
def test_module_simple_update_status_service_does_not_support_fw_activate(self):
|
||||
set_module_args({
|
||||
'category': 'Update',
|
||||
'command': 'SimpleUpdateStatus',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'ioms': ["example1.example.com"],
|
||||
})
|
||||
|
||||
def mock_get_request_function(uri):
|
||||
if uri.endswith("/redfish/v1") or uri.endswith("/redfish/v1/"):
|
||||
return MOCK_SUCCESSFUL_RESPONSE_WITH_UPDATE_SERVICE_RESOURCE
|
||||
elif uri.endswith("/mockedUrl"):
|
||||
return MOCK_SUCCESSFUL_HTTP_EMPTY_RESPONSE
|
||||
elif uri.endswith("/UpdateService"):
|
||||
return MOCK_SUCCESSFUL_RESPONSE_WITH_SIMPLE_UPDATE_BUT_NO_FW_ACTIVATE
|
||||
else:
|
||||
raise RuntimeError("Illegal call to get_request in test: " + uri)
|
||||
|
||||
with patch("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils.get_request") as mock_get_request:
|
||||
mock_get_request.side_effect = mock_get_request_function
|
||||
with self.assertRaises(AnsibleFailJson) as ansible_exit_json:
|
||||
module.main()
|
||||
self.assertEqual("Service does not support FWActivate",
|
||||
get_exception_message(ansible_exit_json))
|
Loading…
Add table
Add a link
Reference in a new issue