add new module xcc_redfish_command to manage Lenovo servers using Redfish APIs (#2007) (#2095)

* add new module xcc_redfish_command to manage Lenovo servers using Redfish APIs

* Update plugins/modules/remote_management/lenovoxcc/xcc_redfish_command.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* fix some errors detected by ansible-test sanity

* end all descriptions (except short_description) with a period

* fix return definition problem and other errors detected by ansible-test sanity

* Always use true/false for booleans in YAML

* It is usually a good idea to leave away required: false

* fix errors detected by ansible-test sanity

* fix elements of command is not defined

* check whether resource_uri is specified for Raw commands

* if no Members property, return false; if empty array, return true

* get @odata.etag from patch body instead of getting again

* add request_body checking

* add unit test for the module

* fix errors detected by ansible-test sanity --test pep8

* update class name xcc_RedfishUtils to XCCRedfishUtils to follow convention; import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, exit_json, fail_json from ansible_collections.community.general.tests.unit.plugins.modules.utils instead of inline them

* support using security token for auth

* fix line too long error

* As 2.3.0 got released yesterday, move to 2.4.0

* add maintainers for lenovoxcc

* update to make sure that it's sorted alphabetically

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 04f46f0435)

Co-authored-by: panyy3 <panyy3@lenovo.com>
This commit is contained in:
patchback[bot] 2021-03-24 16:43:11 +01:00 committed by GitHub
commit 6d2d364a00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 1302 additions and 0 deletions

View file

@ -0,0 +1,626 @@
# 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 json
from ansible_collections.community.general.tests.unit.compat import mock
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
from ansible.module_utils._text import to_bytes
import ansible_collections.community.general.plugins.modules.remote_management.lenovoxcc.xcc_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
def get_bin_path(self, arg, required=False):
"""Mock AnsibleModule.get_bin_path"""
return arg
class TestXCCRedfishCommand(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': 'VirtualMediaEject',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
})
module.main()
def test_module_fail_when_unknown_command(self):
with self.assertRaises(AnsibleFailJson):
set_module_args({
'category': 'Manager',
'command': 'unknown',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
})
module.main()
def test_module_command_VirtualMediaInsert_pass(self):
set_module_args({
'category': 'Manager',
'command': 'VirtualMediaInsert',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'timeout': 30,
'virtual_media': {
'image_url': "nfs://10.245.52.18:/home/nfs/bootable-sr635-20210111-autorun.iso",
'media_types': ['CD'],
'inserted': True,
'write_protected': True,
'transfer_protocol_type': 'NFS'
}
})
with patch.object(module.XCCRedfishUtils, '_find_managers_resource') as mock__find_managers_resource:
mock__find_managers_resource.return_value = {'ret': True, 'changed': True, 'msg': 'success'}
with patch.object(module.XCCRedfishUtils, 'virtual_media_insert') as mock_virtual_media_insert:
mock_virtual_media_insert.return_value = {'ret': True, 'changed': True, 'msg': 'success'}
with self.assertRaises(AnsibleExitJson) as result:
module.main()
def test_module_command_VirtualMediaEject_pass(self):
set_module_args({
'category': 'Manager',
'command': 'VirtualMediaEject',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'timeout': 30,
'virtual_media': {
'image_url': "nfs://10.245.52.18:/home/nfs/bootable-sr635-20210111-autorun.iso",
}
})
with patch.object(module.XCCRedfishUtils, '_find_managers_resource') as mock__find_managers_resource:
mock__find_managers_resource.return_value = {'ret': True, 'changed': True, 'msg': 'success'}
with patch.object(module.XCCRedfishUtils, 'virtual_media_eject') as mock_virtual_media_eject:
mock_virtual_media_eject.return_value = {'ret': True, 'changed': True, 'msg': 'success'}
with self.assertRaises(AnsibleExitJson) as result:
module.main()
def test_module_command_VirtualMediaEject_fail_when_required_args_missing(self):
with self.assertRaises(AnsibleFailJson):
set_module_args({
'category': 'Manager',
'command': 'VirtualMediaEject',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
})
module.main()
def test_module_command_GetResource_fail_when_required_args_missing(self):
set_module_args({
'category': 'Raw',
'command': 'GetResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_GetResource_fail_when_get_return_false(self):
set_module_args({
'category': 'Raw',
'command': 'GetResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': False, 'msg': '404 error'}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_GetResource_pass(self):
set_module_args({
'category': 'Raw',
'command': 'GetResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
with self.assertRaises(AnsibleExitJson) as result:
module.main()
def test_module_command_GetCollectionResource_fail_when_required_args_missing(self):
set_module_args({
'category': 'Raw',
'command': 'GetCollectionResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_GetCollectionResource_fail_when_get_return_false(self):
set_module_args({
'category': 'Raw',
'command': 'GetCollectionResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': False, 'msg': '404 error'}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_GetCollectionResource_fail_when_get_not_colection(self):
set_module_args({
'category': 'Raw',
'command': 'GetCollectionResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_GetCollectionResource_pass_when_get_empty_collection(self):
set_module_args({
'category': 'Raw',
'command': 'GetCollectionResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'Members': [], 'Members@odata.count': 0}}
with self.assertRaises(AnsibleExitJson) as result:
module.main()
def test_module_command_GetCollectionResource_pass_when_get_collection(self):
set_module_args({
'category': 'Raw',
'command': 'GetCollectionResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'Members': [{'@odata.id': '/redfish/v1/testuri/1'}], 'Members@odata.count': 1}}
with self.assertRaises(AnsibleExitJson) as result:
module.main()
def test_module_command_PatchResource_fail_when_required_args_missing(self):
set_module_args({
'category': 'Raw',
'command': 'PatchResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
mock_patch_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PatchResource_fail_when_required_args_missing_no_requestbody(self):
set_module_args({
'category': 'Raw',
'command': 'PatchResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
mock_patch_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PatchResource_fail_when_noexisting_property_in_requestbody(self):
set_module_args({
'category': 'Raw',
'command': 'PatchResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
'request_body': {'teststr': 'yyyy', 'otherkey': 'unknownkey'}
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
mock_patch_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PatchResource_fail_when_get_return_false(self):
set_module_args({
'category': 'Raw',
'command': 'PatchResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
'request_body': {'teststr': 'yyyy'}
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
mock_patch_request.return_value = {'ret': False, 'msg': '500 internal error'}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PatchResource_pass(self):
set_module_args({
'category': 'Raw',
'command': 'PatchResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
'request_body': {'teststr': 'yyyy'}
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
mock_patch_request.return_value = {'ret': True, 'data': {'teststr': 'yyyy', '@odata.etag': '322e0d45d9572723c98'}}
with self.assertRaises(AnsibleExitJson) as result:
module.main()
def test_module_command_PostResource_fail_when_required_args_missing(self):
set_module_args({
'category': 'Raw',
'command': 'PostResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {
'ret': True,
'data': {
'Actions': {
'#Bios.ChangePassword': {
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
'title': "ChangePassword",
'PasswordName@Redfish.AllowableValues': [
"UefiAdminPassword",
"UefiPowerOnPassword"
]
},
'#Bios.ResetBios': {
'title': "ResetBios",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
}
},
}
}
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
mock_post_request.return_value = {'ret': True}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PostResource_fail_when_invalid_resourceuri(self):
set_module_args({
'category': 'Raw',
'command': 'PostResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/testuri',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {
'ret': True,
'data': {
'Actions': {
'#Bios.ChangePassword': {
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
'title': "ChangePassword",
'PasswordName@Redfish.AllowableValues': [
"UefiAdminPassword",
"UefiPowerOnPassword"
]
},
'#Bios.ResetBios': {
'title': "ResetBios",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
}
},
}
}
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
mock_post_request.return_value = {'ret': True}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PostResource_fail_when_no_requestbody(self):
set_module_args({
'category': 'Raw',
'command': 'PostResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {
'ret': True,
'data': {
'Actions': {
'#Bios.ChangePassword': {
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
'title': "ChangePassword",
'PasswordName@Redfish.AllowableValues': [
"UefiAdminPassword",
"UefiPowerOnPassword"
]
},
'#Bios.ResetBios': {
'title': "ResetBios",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
}
},
}
}
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
mock_post_request.return_value = {'ret': True}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PostResource_fail_when_no_requestbody(self):
set_module_args({
'category': 'Raw',
'command': 'PostResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword',
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {
'ret': True,
'data': {
'Actions': {
'#Bios.ChangePassword': {
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
'title': "ChangePassword",
'PasswordName@Redfish.AllowableValues': [
"UefiAdminPassword",
"UefiPowerOnPassword"
]
},
'#Bios.ResetBios': {
'title': "ResetBios",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
}
},
}
}
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
mock_post_request.return_value = {'ret': True}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PostResource_fail_when_requestbody_mismatch_with_data_from_actioninfo_uri(self):
set_module_args({
'category': 'Raw',
'command': 'PostResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword',
'request_body': {'PasswordName': 'UefiAdminPassword', 'NewPassword': 'PASSW0RD=='}
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {
'ret': True,
'data': {
'Parameters': [],
'Actions': {
'#Bios.ChangePassword': {
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
'title': "ChangePassword",
'PasswordName@Redfish.AllowableValues': [
"UefiAdminPassword",
"UefiPowerOnPassword"
]
},
'#Bios.ResetBios': {
'title': "ResetBios",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
}
},
}
}
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
mock_post_request.return_value = {'ret': True}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PostResource_fail_when_get_return_false(self):
set_module_args({
'category': 'Raw',
'command': 'PostResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword',
'request_body': {'PasswordName': 'UefiAdminPassword', 'NewPassword': 'PASSW0RD=='}
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {'ret': False, 'msg': '404 error'}
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
mock_post_request.return_value = {'ret': True}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PostResource_fail_when_post_return_false(self):
set_module_args({
'category': 'Raw',
'command': 'PostResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios',
'request_body': {}
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {
'ret': True,
'data': {
'Actions': {
'#Bios.ChangePassword': {
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
'title': "ChangePassword",
'PasswordName@Redfish.AllowableValues': [
"UefiAdminPassword",
"UefiPowerOnPassword"
]
},
'#Bios.ResetBios': {
'title': "ResetBios",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
}
},
}
}
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
mock_post_request.return_value = {'ret': False, 'msg': '500 internal error'}
with self.assertRaises(AnsibleFailJson) as result:
module.main()
def test_module_command_PostResource_pass(self):
set_module_args({
'category': 'Raw',
'command': 'PostResource',
'baseuri': '10.245.39.251',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios',
'request_body': {}
})
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
mock_get_request.return_value = {
'ret': True,
'data': {
'Actions': {
'#Bios.ChangePassword': {
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
'title': "ChangePassword",
'PasswordName@Redfish.AllowableValues': [
"UefiAdminPassword",
"UefiPowerOnPassword"
]
},
'#Bios.ResetBios': {
'title': "ResetBios",
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
}
},
}
}
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
mock_post_request.return_value = {'ret': True, 'msg': 'post success'}
with self.assertRaises(AnsibleExitJson) as result:
module.main()