mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
* 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:
parent
e781dd3c9b
commit
6d2d364a00
4 changed files with 1302 additions and 0 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
@ -708,6 +708,8 @@ files:
|
||||||
labels: cisco
|
labels: cisco
|
||||||
$modules/remote_management/ipmi/:
|
$modules/remote_management/ipmi/:
|
||||||
maintainers: bgaifullin cloudnull
|
maintainers: bgaifullin cloudnull
|
||||||
|
$modules/remote_management/lenovoxcc/:
|
||||||
|
maintainers: panyy3 renxulei
|
||||||
$modules/remote_management/lxca/:
|
$modules/remote_management/lxca/:
|
||||||
maintainers: navalkp prabhosa
|
maintainers: navalkp prabhosa
|
||||||
$modules/remote_management/manageiq/:
|
$modules/remote_management/manageiq/:
|
||||||
|
|
|
@ -0,0 +1,673 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: xcc_redfish_command
|
||||||
|
short_description: Manages Lenovo Out-Of-Band controllers using Redfish APIs
|
||||||
|
version_added: 2.4.0
|
||||||
|
description:
|
||||||
|
- Builds Redfish URIs locally and sends them to remote OOB controllers to
|
||||||
|
perform an action or get information back or update a configuration attribute.
|
||||||
|
- Manages virtual media.
|
||||||
|
- Supports getting information back via GET method.
|
||||||
|
- Supports updating a configuration attribute via PATCH method.
|
||||||
|
- Supports performing an action via POST method.
|
||||||
|
options:
|
||||||
|
category:
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
- Category to execute on OOB controller.
|
||||||
|
type: str
|
||||||
|
command:
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
- List of commands to execute on OOB controller.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
baseuri:
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
- Base URI of OOB controller.
|
||||||
|
type: str
|
||||||
|
username:
|
||||||
|
description:
|
||||||
|
- Username for authentication with OOB controller.
|
||||||
|
type: str
|
||||||
|
password:
|
||||||
|
description:
|
||||||
|
- Password for authentication with OOB controller.
|
||||||
|
type: str
|
||||||
|
auth_token:
|
||||||
|
description:
|
||||||
|
- Security token for authentication with OOB controller
|
||||||
|
type: str
|
||||||
|
timeout:
|
||||||
|
description:
|
||||||
|
- Timeout in seconds for URL requests to OOB controller.
|
||||||
|
default: 10
|
||||||
|
type: int
|
||||||
|
resource_id:
|
||||||
|
required: false
|
||||||
|
description:
|
||||||
|
- The ID of the System, Manager or Chassis to modify.
|
||||||
|
type: str
|
||||||
|
virtual_media:
|
||||||
|
required: false
|
||||||
|
description:
|
||||||
|
- The options for VirtualMedia commands.
|
||||||
|
type: dict
|
||||||
|
suboptions:
|
||||||
|
media_types:
|
||||||
|
description:
|
||||||
|
- The list of media types appropriate for the image.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
image_url:
|
||||||
|
description:
|
||||||
|
- The URL of the image to insert or eject.
|
||||||
|
type: str
|
||||||
|
inserted:
|
||||||
|
description:
|
||||||
|
- Indicates if the image is treated as inserted on command completion.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
write_protected:
|
||||||
|
description:
|
||||||
|
- Indicates if the media is treated as write-protected.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
username:
|
||||||
|
description:
|
||||||
|
- The username for accessing the image URL.
|
||||||
|
type: str
|
||||||
|
password:
|
||||||
|
description:
|
||||||
|
- The password for accessing the image URL.
|
||||||
|
type: str
|
||||||
|
transfer_protocol_type:
|
||||||
|
description:
|
||||||
|
- The network protocol to use with the image.
|
||||||
|
type: str
|
||||||
|
transfer_method:
|
||||||
|
description:
|
||||||
|
- The transfer method to use with the image.
|
||||||
|
type: str
|
||||||
|
resource_uri:
|
||||||
|
required: false
|
||||||
|
description:
|
||||||
|
- The resource uri to get or patch or post.
|
||||||
|
type: str
|
||||||
|
request_body:
|
||||||
|
required: false
|
||||||
|
description:
|
||||||
|
- The request body to patch or post.
|
||||||
|
type: dict
|
||||||
|
|
||||||
|
author: "Yuyan Pan (@panyy3)"
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Insert Virtual Media
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Manager
|
||||||
|
command: VirtualMediaInsert
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
virtual_media:
|
||||||
|
image_url: "http://example.com/images/SomeLinux-current.iso"
|
||||||
|
media_types:
|
||||||
|
- CD
|
||||||
|
- DVD
|
||||||
|
resource_id: "1"
|
||||||
|
|
||||||
|
- name: Eject Virtual Media
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Manager
|
||||||
|
command: VirtualMediaEject
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
virtual_media:
|
||||||
|
image_url: "http://example.com/images/SomeLinux-current.iso"
|
||||||
|
resource_id: "1"
|
||||||
|
|
||||||
|
- name: Eject all Virtual Media
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Manager
|
||||||
|
command: VirtualMediaEject
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
resource_id: "1"
|
||||||
|
|
||||||
|
- name: Get ComputeSystem Oem property SystemStatus via GetResource command
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Raw
|
||||||
|
command: GetResource
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
resource_uri: "/redfish/v1/Systems/1"
|
||||||
|
register: result
|
||||||
|
- ansible.builtin.debug:
|
||||||
|
msg: "{{ result.redfish_facts.data.Oem.Lenovo.SystemStatus }}"
|
||||||
|
|
||||||
|
- name: Get Oem DNS setting via GetResource command
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Raw
|
||||||
|
command: GetResource
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
resource_uri: "/redfish/v1/Managers/1/NetworkProtocol/Oem/Lenovo/DNS"
|
||||||
|
register: result
|
||||||
|
- ansible.builtin.debug:
|
||||||
|
msg: "{{ result.redfish_facts.data }}"
|
||||||
|
|
||||||
|
- name: Get Lenovo FoD key collection resource via GetCollectionResource command
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Raw
|
||||||
|
command: GetCollectionResource
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
resource_uri: "/redfish/v1/Managers/1/Oem/Lenovo/FoD/Keys"
|
||||||
|
register: result
|
||||||
|
- ansible.builtin.debug:
|
||||||
|
msg: "{{ result.redfish_facts.data_list }}"
|
||||||
|
|
||||||
|
- name: Update ComputeSystem property AssetTag via PatchResource command
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Raw
|
||||||
|
command: PatchResource
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
resource_uri: "/redfish/v1/Systems/1"
|
||||||
|
request_body:
|
||||||
|
AssetTag: "new_asset_tag"
|
||||||
|
|
||||||
|
- name: Perform BootToBIOSSetup action via PostResource command
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Raw
|
||||||
|
command: PostResource
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
resource_uri: "/redfish/v1/Systems/1/Actions/Oem/LenovoComputerSystem.BootToBIOSSetup"
|
||||||
|
request_body: {}
|
||||||
|
|
||||||
|
- name: Perform SecureBoot.ResetKeys action via PostResource command
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Raw
|
||||||
|
command: PostResource
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
resource_uri: "/redfish/v1/Systems/1/SecureBoot/Actions/SecureBoot.ResetKeys"
|
||||||
|
request_body:
|
||||||
|
ResetKeysType: DeleteAllKeys
|
||||||
|
|
||||||
|
- name: Create session
|
||||||
|
community.general.redfish_command:
|
||||||
|
category: Sessions
|
||||||
|
command: CreateSession
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Update Manager DateTimeLocalOffset property using security token for auth
|
||||||
|
community.general.xcc_redfish_command:
|
||||||
|
category: Raw
|
||||||
|
command: PatchResource
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
auth_token: "{{ result.session.token }}"
|
||||||
|
resource_uri: "/redfish/v1/Managers/1"
|
||||||
|
request_body:
|
||||||
|
DateTimeLocalOffset: "+08:00"
|
||||||
|
|
||||||
|
- name: Delete session using security token created by CreateSesssion above
|
||||||
|
community.general.redfish_command:
|
||||||
|
category: Sessions
|
||||||
|
command: DeleteSession
|
||||||
|
baseuri: "{{ baseuri }}"
|
||||||
|
auth_token: "{{ result.session.token }}"
|
||||||
|
session_uri: "{{ result.session.uri }}"
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
msg:
|
||||||
|
description: A message related to the performed action(s).
|
||||||
|
returned: when failure or action/update success
|
||||||
|
type: str
|
||||||
|
sample: "Action was successful"
|
||||||
|
redfish_facts:
|
||||||
|
description: Resource content.
|
||||||
|
returned: when command == GetResource or command == GetCollectionResource
|
||||||
|
type: dict
|
||||||
|
sample: '{
|
||||||
|
"redfish_facts": {
|
||||||
|
"data": {
|
||||||
|
"@odata.etag": "\"3179bf00d69f25a8b3c\"",
|
||||||
|
"@odata.id": "/redfish/v1/Managers/1/NetworkProtocol/Oem/Lenovo/DNS",
|
||||||
|
"@odata.type": "#LenovoDNS.v1_0_0.LenovoDNS",
|
||||||
|
"DDNS": [
|
||||||
|
{
|
||||||
|
"DDNSEnable": true,
|
||||||
|
"DomainName": "",
|
||||||
|
"DomainNameSource": "DHCP"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DNSEnable": true,
|
||||||
|
"Description": "This resource is used to represent a DNS resource for a Redfish implementation.",
|
||||||
|
"IPv4Address1": "10.103.62.178",
|
||||||
|
"IPv4Address2": "0.0.0.0",
|
||||||
|
"IPv4Address3": "0.0.0.0",
|
||||||
|
"IPv6Address1": "::",
|
||||||
|
"IPv6Address2": "::",
|
||||||
|
"IPv6Address3": "::",
|
||||||
|
"Id": "LenovoDNS",
|
||||||
|
"PreferredAddresstype": "IPv4"
|
||||||
|
},
|
||||||
|
"ret": true
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils._text import to_native
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils
|
||||||
|
|
||||||
|
|
||||||
|
class XCCRedfishUtils(RedfishUtils):
|
||||||
|
@staticmethod
|
||||||
|
def _find_empty_virt_media_slot(resources, media_types,
|
||||||
|
media_match_strict=True):
|
||||||
|
for uri, data in resources.items():
|
||||||
|
# check MediaTypes
|
||||||
|
if 'MediaTypes' in data and media_types:
|
||||||
|
if not set(media_types).intersection(set(data['MediaTypes'])):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if media_match_strict:
|
||||||
|
continue
|
||||||
|
if 'RDOC' in uri:
|
||||||
|
continue
|
||||||
|
# if ejected, 'Inserted' should be False and 'ImageName' cleared
|
||||||
|
if (not data.get('Inserted', False) and
|
||||||
|
not data.get('ImageName')):
|
||||||
|
return uri, data
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# find the VirtualMedia resource to eject
|
||||||
|
uri, data, eject = self._find_virt_media_to_eject(resources, image_url)
|
||||||
|
if uri and eject:
|
||||||
|
if ('Actions' not in data or
|
||||||
|
'#VirtualMedia.EjectMedia' not in data['Actions']):
|
||||||
|
# try to eject via PATCH if no EjectMedia 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.EjectMedia'}
|
||||||
|
return self.virtual_media_eject_via_patch(uri)
|
||||||
|
else:
|
||||||
|
# POST to the EjectMedia Action
|
||||||
|
action = data['Actions']['#VirtualMedia.EjectMedia']
|
||||||
|
if 'target' not in action:
|
||||||
|
return {'ret': False,
|
||||||
|
'msg': "target URI property missing from Action "
|
||||||
|
"#VirtualMedia.EjectMedia"}
|
||||||
|
action_uri = action['target']
|
||||||
|
# empty payload for Eject action
|
||||||
|
payload = {}
|
||||||
|
# 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 ejected"}
|
||||||
|
elif uri and not eject:
|
||||||
|
# already ejected: return success but changed=False
|
||||||
|
return {'ret': True, 'changed': False,
|
||||||
|
'msg': "VirtualMedia image '%s' already ejected" %
|
||||||
|
image_url}
|
||||||
|
else:
|
||||||
|
# return failure (no resources matching image_url found)
|
||||||
|
return {'ret': False, 'changed': False,
|
||||||
|
'msg': "No VirtualMedia resource found with image '%s' "
|
||||||
|
"inserted" % image_url}
|
||||||
|
|
||||||
|
def virtual_media_eject(self, options):
|
||||||
|
if options:
|
||||||
|
image_url = options.get('image_url')
|
||||||
|
if image_url: # eject specified one media
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# eject all inserted media one by one
|
||||||
|
ejected_media_list = []
|
||||||
|
for uri, data in resources.items():
|
||||||
|
if data.get('Image') and data.get('Inserted', True):
|
||||||
|
returndict = self.virtual_media_eject_one(data.get('Image'))
|
||||||
|
if not returndict['ret']:
|
||||||
|
return returndict
|
||||||
|
ejected_media_list.append(data.get('Image'))
|
||||||
|
|
||||||
|
if len(ejected_media_list) == 0:
|
||||||
|
# no media inserted: return success but changed=False
|
||||||
|
return {'ret': True, 'changed': False,
|
||||||
|
'msg': "No VirtualMedia image inserted"}
|
||||||
|
else:
|
||||||
|
return {'ret': True, 'changed': True,
|
||||||
|
'msg': "VirtualMedia %s ejected" % str(ejected_media_list)}
|
||||||
|
|
||||||
|
def raw_get_resource(self, resource_uri):
|
||||||
|
if resource_uri is None:
|
||||||
|
return {'ret': False, 'msg': "resource_uri is missing"}
|
||||||
|
response = self.get_request(self.root_uri + resource_uri)
|
||||||
|
if response['ret'] is False:
|
||||||
|
return response
|
||||||
|
data = response['data']
|
||||||
|
return {'ret': True, 'data': data}
|
||||||
|
|
||||||
|
def raw_get_collection_resource(self, resource_uri):
|
||||||
|
if resource_uri is None:
|
||||||
|
return {'ret': False, 'msg': "resource_uri is missing"}
|
||||||
|
response = self.get_request(self.root_uri + resource_uri)
|
||||||
|
if response['ret'] is False:
|
||||||
|
return response
|
||||||
|
if 'Members' not in response['data']:
|
||||||
|
return {'ret': False, 'msg': "Specified resource_uri doesn't have Members property"}
|
||||||
|
member_list = [i['@odata.id'] for i in response['data'].get('Members', [])]
|
||||||
|
|
||||||
|
# get member resource one by one
|
||||||
|
data_list = []
|
||||||
|
for member_uri in member_list:
|
||||||
|
uri = self.root_uri + member_uri
|
||||||
|
response = self.get_request(uri)
|
||||||
|
if response['ret'] is False:
|
||||||
|
return response
|
||||||
|
data = response['data']
|
||||||
|
data_list.append(data)
|
||||||
|
|
||||||
|
return {'ret': True, 'data_list': data_list}
|
||||||
|
|
||||||
|
def raw_patch_resource(self, resource_uri, request_body):
|
||||||
|
if resource_uri is None:
|
||||||
|
return {'ret': False, 'msg': "resource_uri is missing"}
|
||||||
|
if request_body is None:
|
||||||
|
return {'ret': False, 'msg': "request_body is missing"}
|
||||||
|
# check whether resource_uri existing or not
|
||||||
|
response = self.get_request(self.root_uri + resource_uri)
|
||||||
|
if response['ret'] is False:
|
||||||
|
return response
|
||||||
|
original_etag = response['data']['@odata.etag']
|
||||||
|
|
||||||
|
# check validity of keys in request_body
|
||||||
|
data = response['data']
|
||||||
|
for key in request_body.keys():
|
||||||
|
if key not in data:
|
||||||
|
return {'ret': False, 'msg': "Key %s not found. Supported key list: %s" % (key, str(data.keys()))}
|
||||||
|
|
||||||
|
# perform patch
|
||||||
|
response = self.patch_request(self.root_uri + resource_uri, request_body)
|
||||||
|
if response['ret'] is False:
|
||||||
|
return response
|
||||||
|
|
||||||
|
# check whether changed or not
|
||||||
|
current_etag = ''
|
||||||
|
if 'data' in response and '@odata.etag' in response['data']:
|
||||||
|
current_etag = response['data']['@odata.etag']
|
||||||
|
if current_etag != original_etag:
|
||||||
|
return {'ret': True, 'changed': True}
|
||||||
|
else:
|
||||||
|
return {'ret': True, 'changed': False}
|
||||||
|
|
||||||
|
def raw_post_resource(self, resource_uri, request_body):
|
||||||
|
if resource_uri is None:
|
||||||
|
return {'ret': False, 'msg': "resource_uri is missing"}
|
||||||
|
if '/Actions/' not in resource_uri:
|
||||||
|
return {'ret': False, 'msg': "Bad uri %s. Keyword /Actions/ should be included in uri" % resource_uri}
|
||||||
|
if request_body is None:
|
||||||
|
return {'ret': False, 'msg': "request_body is missing"}
|
||||||
|
# get action base uri data for further checking
|
||||||
|
action_base_uri = resource_uri.split('/Actions/')[0]
|
||||||
|
response = self.get_request(self.root_uri + action_base_uri)
|
||||||
|
if response['ret'] is False:
|
||||||
|
return response
|
||||||
|
if 'Actions' not in response['data']:
|
||||||
|
return {'ret': False, 'msg': "Actions property not found in %s" % action_base_uri}
|
||||||
|
|
||||||
|
# check resouce_uri with target uri found in action base uri data
|
||||||
|
action_found = False
|
||||||
|
action_info_uri = None
|
||||||
|
action_target_uri_list = []
|
||||||
|
for key in response['data']['Actions'].keys():
|
||||||
|
if action_found:
|
||||||
|
break
|
||||||
|
if not key.startswith('#'):
|
||||||
|
continue
|
||||||
|
if 'target' in response['data']['Actions'][key]:
|
||||||
|
if resource_uri == response['data']['Actions'][key]['target']:
|
||||||
|
action_found = True
|
||||||
|
if '@Redfish.ActionInfo' in response['data']['Actions'][key]:
|
||||||
|
action_info_uri = response['data']['Actions'][key]['@Redfish.ActionInfo']
|
||||||
|
else:
|
||||||
|
action_target_uri_list.append(response['data']['Actions'][key]['target'])
|
||||||
|
if not action_found and 'Oem' in response['data']['Actions']:
|
||||||
|
for key in response['data']['Actions']['Oem'].keys():
|
||||||
|
if action_found:
|
||||||
|
break
|
||||||
|
if not key.startswith('#'):
|
||||||
|
continue
|
||||||
|
if 'target' in response['data']['Actions']['Oem'][key]:
|
||||||
|
if resource_uri == response['data']['Actions']['Oem'][key]['target']:
|
||||||
|
action_found = True
|
||||||
|
if '@Redfish.ActionInfo' in response['data']['Actions']['Oem'][key]:
|
||||||
|
action_info_uri = response['data']['Actions']['Oem'][key]['@Redfish.ActionInfo']
|
||||||
|
else:
|
||||||
|
action_target_uri_list.append(response['data']['Actions']['Oem'][key]['target'])
|
||||||
|
|
||||||
|
if not action_found:
|
||||||
|
return {'ret': False,
|
||||||
|
'msg': 'Specified resource_uri is not a supported action target uri, please specify a supported target uri instead. Supported uri: %s'
|
||||||
|
% (str(action_target_uri_list))}
|
||||||
|
|
||||||
|
# check request_body with parameter name defined by @Redfish.ActionInfo
|
||||||
|
if action_info_uri is not None:
|
||||||
|
response = self.get_request(self.root_uri + action_info_uri)
|
||||||
|
if response['ret'] is False:
|
||||||
|
return response
|
||||||
|
for key in request_body.keys():
|
||||||
|
key_found = False
|
||||||
|
for para in response['data']['Parameters']:
|
||||||
|
if key == para['Name']:
|
||||||
|
key_found = True
|
||||||
|
break
|
||||||
|
if not key_found:
|
||||||
|
return {'ret': False,
|
||||||
|
'msg': 'Invalid property %s found in request_body. Please refer to @Redfish.ActionInfo Parameters: %s'
|
||||||
|
% (key, str(response['data']['Parameters']))}
|
||||||
|
|
||||||
|
# perform post
|
||||||
|
response = self.post_request(self.root_uri + resource_uri, request_body)
|
||||||
|
if response['ret'] is False:
|
||||||
|
return response
|
||||||
|
return {'ret': True, 'changed': True}
|
||||||
|
|
||||||
|
|
||||||
|
# More will be added as module features are expanded
|
||||||
|
CATEGORY_COMMANDS_ALL = {
|
||||||
|
"Manager": ["VirtualMediaInsert",
|
||||||
|
"VirtualMediaEject"],
|
||||||
|
"Raw": ["GetResource",
|
||||||
|
"GetCollectionResource",
|
||||||
|
"PatchResource",
|
||||||
|
"PostResource"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
result = {}
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
category=dict(required=True),
|
||||||
|
command=dict(required=True, type='list', elements='str'),
|
||||||
|
baseuri=dict(required=True),
|
||||||
|
username=dict(),
|
||||||
|
password=dict(no_log=True),
|
||||||
|
auth_token=dict(no_log=True),
|
||||||
|
timeout=dict(type='int', default=10),
|
||||||
|
resource_id=dict(),
|
||||||
|
virtual_media=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
media_types=dict(type='list', elements='str', default=[]),
|
||||||
|
image_url=dict(),
|
||||||
|
inserted=dict(type='bool', default=True),
|
||||||
|
write_protected=dict(type='bool', default=True),
|
||||||
|
username=dict(),
|
||||||
|
password=dict(no_log=True),
|
||||||
|
transfer_protocol_type=dict(),
|
||||||
|
transfer_method=dict(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
resource_uri=dict(),
|
||||||
|
request_body=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
required_together=[
|
||||||
|
('username', 'password'),
|
||||||
|
],
|
||||||
|
required_one_of=[
|
||||||
|
('username', 'auth_token'),
|
||||||
|
],
|
||||||
|
mutually_exclusive=[
|
||||||
|
('username', 'auth_token'),
|
||||||
|
],
|
||||||
|
supports_check_mode=False
|
||||||
|
)
|
||||||
|
|
||||||
|
category = module.params['category']
|
||||||
|
command_list = module.params['command']
|
||||||
|
|
||||||
|
# admin credentials used for authentication
|
||||||
|
creds = {'user': module.params['username'],
|
||||||
|
'pswd': module.params['password'],
|
||||||
|
'token': module.params['auth_token']}
|
||||||
|
|
||||||
|
# timeout
|
||||||
|
timeout = module.params['timeout']
|
||||||
|
|
||||||
|
# System, Manager or Chassis ID to modify
|
||||||
|
resource_id = module.params['resource_id']
|
||||||
|
|
||||||
|
# VirtualMedia options
|
||||||
|
virtual_media = module.params['virtual_media']
|
||||||
|
|
||||||
|
# resource_uri
|
||||||
|
resource_uri = module.params['resource_uri']
|
||||||
|
|
||||||
|
# request_body
|
||||||
|
request_body = module.params['request_body']
|
||||||
|
|
||||||
|
# Build root URI
|
||||||
|
root_uri = "https://" + module.params['baseuri']
|
||||||
|
rf_utils = XCCRedfishUtils(creds, root_uri, timeout, module, resource_id=resource_id, data_modification=True)
|
||||||
|
|
||||||
|
# Check that Category is valid
|
||||||
|
if category not in CATEGORY_COMMANDS_ALL:
|
||||||
|
module.fail_json(msg=to_native("Invalid Category '%s'. Valid Categories = %s" % (category, CATEGORY_COMMANDS_ALL.keys())))
|
||||||
|
|
||||||
|
# Check that all commands are valid
|
||||||
|
for cmd in command_list:
|
||||||
|
# Fail if even one command given is invalid
|
||||||
|
if cmd not in CATEGORY_COMMANDS_ALL[category]:
|
||||||
|
module.fail_json(msg=to_native("Invalid Command '%s'. Valid Commands = %s" % (cmd, CATEGORY_COMMANDS_ALL[category])))
|
||||||
|
|
||||||
|
# Organize by Categories / Commands
|
||||||
|
if category == "Manager":
|
||||||
|
# execute only if we find a Manager service resource
|
||||||
|
result = rf_utils._find_managers_resource()
|
||||||
|
if result['ret'] is False:
|
||||||
|
module.fail_json(msg=to_native(result['msg']))
|
||||||
|
|
||||||
|
for command in command_list:
|
||||||
|
if command == 'VirtualMediaInsert':
|
||||||
|
result = rf_utils.virtual_media_insert(virtual_media)
|
||||||
|
elif command == 'VirtualMediaEject':
|
||||||
|
result = rf_utils.virtual_media_eject(virtual_media)
|
||||||
|
elif category == "Raw":
|
||||||
|
for command in command_list:
|
||||||
|
if command == 'GetResource':
|
||||||
|
result = rf_utils.raw_get_resource(resource_uri)
|
||||||
|
elif command == 'GetCollectionResource':
|
||||||
|
result = rf_utils.raw_get_collection_resource(resource_uri)
|
||||||
|
elif command == 'PatchResource':
|
||||||
|
result = rf_utils.raw_patch_resource(resource_uri, request_body)
|
||||||
|
elif command == 'PostResource':
|
||||||
|
result = rf_utils.raw_post_resource(resource_uri, request_body)
|
||||||
|
|
||||||
|
# Return data back or fail with proper message
|
||||||
|
if result['ret'] is True:
|
||||||
|
if command == 'GetResource' or command == 'GetCollectionResource':
|
||||||
|
module.exit_json(redfish_facts=result)
|
||||||
|
else:
|
||||||
|
changed = result.get('changed', True)
|
||||||
|
msg = result.get('msg', 'Action was successful')
|
||||||
|
module.exit_json(changed=changed, msg=msg)
|
||||||
|
else:
|
||||||
|
module.fail_json(msg=to_native(result['msg']))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
1
plugins/modules/xcc_redfish_command.py
Symbolic link
1
plugins/modules/xcc_redfish_command.py
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
remote_management/lenovoxcc/xcc_redfish_command.py
|
|
@ -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()
|
Loading…
Reference in a new issue