diff --git a/changelogs/fragments/4682-compatibility-virtualmedia-resource-location.yaml b/changelogs/fragments/4682-compatibility-virtualmedia-resource-location.yaml new file mode 100644 index 0000000000..c15ee4d15c --- /dev/null +++ b/changelogs/fragments/4682-compatibility-virtualmedia-resource-location.yaml @@ -0,0 +1,2 @@ +bugfixes: + - xcc_redfish_command - for compatibility due to Redfish spec changes the virtualMedia resource location changed from Manager to System (https://github.com/ansible-collections/community.general/pull/4682). \ No newline at end of file diff --git a/plugins/modules/remote_management/lenovoxcc/xcc_redfish_command.py b/plugins/modules/remote_management/lenovoxcc/xcc_redfish_command.py index f082f6cd5c..bc54d0c148 100644 --- a/plugins/modules/remote_management/lenovoxcc/xcc_redfish_command.py +++ b/plugins/modules/remote_management/lenovoxcc/xcc_redfish_command.py @@ -305,6 +305,8 @@ class XCCRedfishUtils(RedfishUtils): continue if 'RDOC' in uri: continue + if 'Remote' in uri: + continue # if ejected, 'Inserted' should be False and 'ImageName' cleared if (not data.get('Inserted', False) and not data.get('ImageName')): @@ -312,13 +314,19 @@ class XCCRedfishUtils(RedfishUtils): return None, None def virtual_media_eject_one(self, image_url): - # locate and read the VirtualMedia resources - response = self.get_request(self.root_uri + self.manager_uri) + # read the VirtualMedia resources from systems + response = self.get_request(self.root_uri + self.systems_uri) if response['ret'] is False: return response data = response['data'] if 'VirtualMedia' not in data: - return {'ret': False, 'msg': "VirtualMedia resource not found"} + # read the VirtualMedia resources from manager + response = self.get_request(self.root_uri + self.manager_uri) + if response['ret'] is False: + return response + data = response['data'] + if 'VirtualMedia' not in data: + return {'ret': False, 'msg': "VirtualMedia resource not found"} virt_media_uri = data["VirtualMedia"]["@odata.id"] response = self.get_request(self.root_uri + virt_media_uri) if response['ret'] is False: @@ -379,13 +387,20 @@ class XCCRedfishUtils(RedfishUtils): return self.virtual_media_eject_one(image_url) # eject all inserted media when no image_url specified - # read all the VirtualMedia resources - response = self.get_request(self.root_uri + self.manager_uri) + # read the VirtualMedia resources from systems + response = self.get_request(self.root_uri + self.systems_uri) if response['ret'] is False: return response data = response['data'] if 'VirtualMedia' not in data: - return {'ret': False, 'msg': "VirtualMedia resource not found"} + # read the VirtualMedia resources from manager + response = self.get_request(self.root_uri + self.manager_uri) + if response['ret'] is False: + return response + data = response['data'] + if 'VirtualMedia' not in data: + return {'ret': False, 'msg': "VirtualMedia resource not found"} + # read all the VirtualMedia resources virt_media_uri = data["VirtualMedia"]["@odata.id"] response = self.get_request(self.root_uri + virt_media_uri) if response['ret'] is False: @@ -413,6 +428,95 @@ class XCCRedfishUtils(RedfishUtils): return {'ret': True, 'changed': True, 'msg': "VirtualMedia %s ejected" % str(ejected_media_list)} + def virtual_media_insert(self, options): + param_map = { + 'Inserted': 'inserted', + 'WriteProtected': 'write_protected', + 'UserName': 'username', + 'Password': 'password', + 'TransferProtocolType': 'transfer_protocol_type', + 'TransferMethod': 'transfer_method' + } + image_url = options.get('image_url') + if not image_url: + return {'ret': False, + 'msg': "image_url option required for VirtualMediaInsert"} + media_types = options.get('media_types') + + # read the VirtualMedia resources from systems + response = self.get_request(self.root_uri + self.systems_uri) + if response['ret'] is False: + return response + data = response['data'] + if 'VirtualMedia' not in data: + # read the VirtualMedia resources from manager + response = self.get_request(self.root_uri + self.manager_uri) + if response['ret'] is False: + return response + data = response['data'] + if 'VirtualMedia' not in data: + return {'ret': False, 'msg': "VirtualMedia resource not found"} + virt_media_uri = data["VirtualMedia"]["@odata.id"] + response = self.get_request(self.root_uri + virt_media_uri) + if response['ret'] is False: + return response + data = response['data'] + virt_media_list = [] + for member in data[u'Members']: + virt_media_list.append(member[u'@odata.id']) + resources, headers = self._read_virt_media_resources(virt_media_list) + + # see if image already inserted; if so, nothing to do + if self._virt_media_image_inserted(resources, image_url): + return {'ret': True, 'changed': False, + 'msg': "VirtualMedia '%s' already inserted" % image_url} + + # find an empty slot to insert the media + # try first with strict media_type matching + uri, data = self._find_empty_virt_media_slot( + resources, media_types, media_match_strict=True) + if not uri: + # if not found, try without strict media_type matching + uri, data = self._find_empty_virt_media_slot( + resources, media_types, media_match_strict=False) + if not uri: + return {'ret': False, + 'msg': "Unable to find an available VirtualMedia resource " + "%s" % ('supporting ' + str(media_types) + if media_types else '')} + + # confirm InsertMedia action found + if ('Actions' not in data or + '#VirtualMedia.InsertMedia' not in data['Actions']): + # try to insert via PATCH if no InsertMedia action found + h = headers[uri] + if 'allow' in h: + methods = [m.strip() for m in h.get('allow').split(',')] + if 'PATCH' not in methods: + # if Allow header present and PATCH missing, return error + return {'ret': False, + 'msg': "%s action not found and PATCH not allowed" + % '#VirtualMedia.InsertMedia'} + return self.virtual_media_insert_via_patch(options, param_map, + uri, data) + + # get the action property + action = data['Actions']['#VirtualMedia.InsertMedia'] + if 'target' not in action: + return {'ret': False, + 'msg': "target URI missing from Action " + "#VirtualMedia.InsertMedia"} + action_uri = action['target'] + # get ActionInfo or AllowableValues + ai = self._get_all_action_info_values(action) + # construct payload + payload = self._insert_virt_media_payload(options, param_map, data, ai) + # POST to action + response = self.post_request(self.root_uri + action_uri, payload) + if response['ret'] is False: + return response + return {'ret': True, 'changed': True, 'msg': "VirtualMedia inserted"} + def raw_get_resource(self, resource_uri): if resource_uri is None: return {'ret': False, 'msg': "resource_uri is missing"} @@ -640,7 +744,11 @@ def main(): # Organize by Categories / Commands if category == "Manager": - # execute only if we find a Manager service resource + # For virtual media resource locates on Systems service + result = rf_utils._find_systems_resource() + if result['ret'] is False: + module.fail_json(msg=to_native(result['msg'])) + # For virtual media resource locates on Managers service result = rf_utils._find_managers_resource() if result['ret'] is False: module.fail_json(msg=to_native(result['msg'])) diff --git a/tests/unit/plugins/modules/remote_management/lenovoxcc/test_xcc_redfish_command.py b/tests/unit/plugins/modules/remote_management/lenovoxcc/test_xcc_redfish_command.py index 418474c578..7ee7ee2c18 100644 --- a/tests/unit/plugins/modules/remote_management/lenovoxcc/test_xcc_redfish_command.py +++ b/tests/unit/plugins/modules/remote_management/lenovoxcc/test_xcc_redfish_command.py @@ -72,14 +72,16 @@ class TestXCCRedfishCommand(unittest.TestCase): '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, '_find_systems_resource') as mock__find_systems_resource: + mock__find_systems_resource.return_value = {'ret': True, 'changed': True, 'msg': 'success'} + 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 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() + with self.assertRaises(AnsibleExitJson) as result: + module.main() def test_module_command_VirtualMediaEject_pass(self): set_module_args({ @@ -93,14 +95,16 @@ class TestXCCRedfishCommand(unittest.TestCase): '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, '_find_systems_resource') as mock__find_systems_resource: + mock__find_systems_resource.return_value = {'ret': True, 'changed': True, 'msg': 'success'} + 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 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() + with self.assertRaises(AnsibleExitJson) as result: + module.main() def test_module_command_VirtualMediaEject_fail_when_required_args_missing(self): with self.assertRaises(AnsibleFailJson):