From 62b2a08cfbc1aeb5a45d45d9e975d74e194cd8d8 Mon Sep 17 00:00:00 2001 From: rajeevarakkal <36444805+rajeevarakkal@users.noreply.github.com> Date: Tue, 11 Dec 2018 21:08:01 +0530 Subject: [PATCH] Add DellEMC iDRAC Firmware module (#46675) Co-Authored-By: rajeevarakkal <36444805+rajeevarakkal@users.noreply.github.com> Co-Authored-By: Sviatoslav Sydorenko <578543+webknjaz@users.noreply.github.com> --- .github/BOTMETA.yml | 3 + .../remote_management/dellemc/__init__.py | 0 .../dellemc/dellemc_idrac.py | 57 +++++ .../remote_management/dellemc/__init__.py | 0 .../dellemc/idrac/__init__.py | 0 .../dellemc/idrac/dellemc_idrac_firmware.py | 209 ++++++++++++++++++ 6 files changed, 269 insertions(+) create mode 100644 lib/ansible/module_utils/remote_management/dellemc/__init__.py create mode 100644 lib/ansible/module_utils/remote_management/dellemc/dellemc_idrac.py create mode 100644 lib/ansible/modules/remote_management/dellemc/__init__.py create mode 100644 lib/ansible/modules/remote_management/dellemc/idrac/__init__.py create mode 100644 lib/ansible/modules/remote_management/dellemc/idrac/dellemc_idrac_firmware.py diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 3d8006d4213..1d1d28b07de 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -310,6 +310,7 @@ files: $modules/remote_management/redfish/: $team_redfish $modules/remote_management/stacki/stacki_host.py: bbyhuy bsanders $modules/remote_management/ucs/: $team_ucs + $modules/remote_management/dellemc/: rajeevarakkal $modules/source_control/git.py: $team_ansible $modules/source_control/github_key.py: ignored: erydo @@ -623,6 +624,8 @@ files: $module_utils/remote_management/ucs: maintainers: $team_ucs labels: ucs + $module_utils/remote_management/dellemc: + maintainers: rajeevarakkal $module_utils/pure.py: maintainers: $team_purestorage labels: pure_storage diff --git a/lib/ansible/module_utils/remote_management/dellemc/__init__.py b/lib/ansible/module_utils/remote_management/dellemc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/remote_management/dellemc/dellemc_idrac.py b/lib/ansible/module_utils/remote_management/dellemc/dellemc_idrac.py new file mode 100644 index 00000000000..209ce126934 --- /dev/null +++ b/lib/ansible/module_utils/remote_management/dellemc/dellemc_idrac.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +# +# Dell EMC OpenManage Ansible Modules +# Version 1.0 +# Copyright (C) 2018 Dell Inc. + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# All rights reserved. Dell, EMC, and other trademarks are trademarks of Dell Inc. or its subsidiaries. +# Other trademarks may be trademarks of their respective owners. +# + +from __future__ import (absolute_import, division, + print_function) +__metaclass__ = type + +try: + from omsdk.sdkinfra import sdkinfra + from omsdk.sdkcreds import UserCredentials + from omsdk.sdkfile import FileOnShare, file_share_manager + from omsdk.sdkprotopref import ProtoPreference, ProtocolEnum + from omsdk.http.sdkwsmanbase import WsManOptions + HAS_OMSDK = True +except ImportError: + HAS_OMSDK = False + + +class iDRACConnection: + + def __init__(self, module_params): + if not HAS_OMSDK: + raise ImportError("Dell EMC OMSDK library is required for this module") + self.idrac_ip = module_params['idrac_ip'] + self.idrac_user = module_params['idrac_user'] + self.idrac_pwd = module_params['idrac_pwd'] + self.idrac_port = module_params['idrac_port'] + if not all((self.idrac_ip, self.idrac_user, self.idrac_pwd)): + raise ValueError("hostname, username and password required") + self.handle = None + self.creds = UserCredentials(self.idrac_user, self.idrac_pwd) + self.pOp = WsManOptions(port=self.idrac_port) + self.sdk = sdkinfra() + if self.sdk is None: + msg = "Could not initialize iDRAC drivers." + raise RuntimeError(msg) + + def __enter__(self): + self.sdk.importPath() + self.handle = self.sdk.get_driver(self.sdk.driver_enum.iDRAC, self.idrac_ip, self.creds, pOptions=self.pOp) + if self.handle is None: + msg = "Could not find device driver for iDRAC with IP Address: {0}".format(self.idrac_ip) + raise RuntimeError(msg) + return self.handle + + def __exit__(self, exc_type, exc_val, exc_tb): + self.handle.disconnect() + return False diff --git a/lib/ansible/modules/remote_management/dellemc/__init__.py b/lib/ansible/modules/remote_management/dellemc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/modules/remote_management/dellemc/idrac/__init__.py b/lib/ansible/modules/remote_management/dellemc/idrac/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/modules/remote_management/dellemc/idrac/dellemc_idrac_firmware.py b/lib/ansible/modules/remote_management/dellemc/idrac/dellemc_idrac_firmware.py new file mode 100644 index 00000000000..12b683dd8f2 --- /dev/null +++ b/lib/ansible/modules/remote_management/dellemc/idrac/dellemc_idrac_firmware.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# +# Dell EMC OpenManage Ansible Modules +# Version 1.0 +# Copyright (C) 2018 Dell Inc. + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# All rights reserved. Dell, EMC, and other trademarks are trademarks of Dell Inc. or its subsidiaries. +# Other trademarks may be trademarks of their respective owners. +# + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +--- +module: dellemc_idrac_firmware +short_description: Firmware update from a repository on a network share (CIFS, NFS). +version_added: "2.8" +description: + - Update the Firmware by connecting to a network share (either CIFS or NFS) that contains a catalog of + available updates. + - Network share should contain a valid repository of Update Packages (DUPs) and a catalog file describing the DUPs. + - All applicable updates contained in the repository are applied to the system. + - This feature is available only with iDRAC Enterprise License. +options: + idrac_ip: + required: True + description: iDRAC IP Address. + idrac_user: + required: True + description: iDRAC username. + idrac_pwd: + required: True + description: iDRAC user password. + idrac_port: + required: False + description: iDRAC port. + default: 443 + share_name: + required: True + description: CIFS or NFS Network share. + share_user: + required: False + description: Network share user in the format 'user@domain' or 'domain\\user' if user is + part of a domain else 'user'. This option is mandatory for CIFS Network Share. + share_pwd: + required: False + description: Network share user password. This option is mandatory for CIFS Network Share. + share_mnt: + required: True + description: Local mount path of the network share with read-write permission for ansible user. + This option is mandatory for Network Share. + reboot: + required: False + description: Whether to reboots after applying the updates or not. + default: False + type: bool + job_wait: + required: False + description: Whether to wait for job completion or not. + type: bool + default: True + catalog_file_name: + required: False + description: Catalog file name relative to the I(share_name). + type: str + default: 'Catalog.xml' + +requirements: + - "omsdk" + - "python >= 2.7.5" +author: "Rajeev Arakkal (@rajeevarakkal)" +""" + +EXAMPLES = """ +--- +- name: Update firmware from repository on a Network Share + dellemc_idrac_firmware: + idrac_ip: "192.168.0.1" + idrac_user: "user_name" + idrac_pwd: "user_pwd" + share_name: "192.168.0.0:/share" + share_user: "share_user_name" + share_pwd: "share_user_pwd" + share_mnt: "/mnt/share" + reboot: True + job_wait: True + catalog_file_name: "Catalog.xml" +""" + +RETURN = """ +--- +msg: + type: string + description: Over all firmware update status. + returned: always + sample: "Successfully updated the firmware." +update_status: + type: dict + description: Firmware Update job and progress details from the iDRAC. + returned: success + sample: { + 'InstanceID': 'JID_XXXXXXXXXXXX', + 'JobState': 'Completed', + 'Message': 'Job completed successfully.', + 'MessageId': 'REDXXX', + 'Name': 'Repository Update', + 'JobStartTime': 'NA', + 'Status': 'Success', + } +""" + + +from ansible.module_utils.remote_management.dellemc.dellemc_idrac import iDRACConnection +from ansible.module_utils.basic import AnsibleModule +try: + from omsdk.sdkcreds import UserCredentials + from omsdk.sdkfile import FileOnShare + HAS_OMSDK = True +except ImportError: + HAS_OMSDK = False + + +def _validate_catalog_file(catalog_file_name): + normilized_file_name = catalog_file_name.lower() + if not normilized_file_name: + raise ValueError('catalog_file_name should be a non-empty string.') + elif not normilized_file_name.endswith("xml"): + raise ValueError('catalog_file_name should be an XML file.') + + +def update_firmware(idrac, module): + """Update firmware from a network share and return the job details.""" + msg = {} + msg['changed'] = False + msg['update_status'] = {} + + try: + upd_share = FileOnShare(remote=module.params['share_name'] + "/" + module.params['catalog_file_name'], + mount_point=module.params['share_mnt'], + isFolder=False, + creds=UserCredentials( + module.params['share_user'], + module.params['share_pwd']) + ) + + idrac.use_redfish = True + if '12' in idrac.ServerGeneration or '13' in idrac.ServerGeneration: + idrac.use_redfish = False + + apply_update = True + msg['update_status'] = idrac.update_mgr.update_from_repo(upd_share, + apply_update, + module.params['reboot'], + module.params['job_wait']) + except RuntimeError as e: + module.fail_json(msg=str(e)) + + if "Status" in msg['update_status']: + if msg['update_status']['Status'] == "Success": + if module.params['job_wait']: + msg['changed'] = True + else: + module.fail_json(msg='Failed to update firmware.', update_status=msg['update_status']) + return msg + + +def main(): + module = AnsibleModule( + argument_spec={ + "idrac_ip": {"required": True, "type": str}, + "idrac_user": {"required": True, "type": str}, + "idrac_pwd": {"required": True, "type": str, "no_log": True}, + "idrac_port": {"required": False, "default": 443, "type": int}, + + "share_name": {"required": True, "type": str}, + "share_user": {"required": False, "type": str}, + "share_pwd": {"required": False, "type": str, "no_log": True}, + "share_mnt": {"required": True, "type": str}, + + "catalog_file_name": {"required": False, "type": str, "default": "Catalog.xml"}, + "reboot": {"required": False, "type": bool, "default": False}, + "job_wait": {"required": False, "type": bool, "default": True}, + }, + + supports_check_mode=False) + + try: + # Validate the catalog file + _validate_catalog_file(module.params['catalog_file_name']) + # Connect to iDRAC and update firmware + with iDRACConnection(module.params) as idrac: + update_status = update_firmware(idrac, module) + except (ImportError, ValueError, RuntimeError) as e: + module.fail_json(msg=str(e)) + + module.exit_json(msg='Successfully updated the firmware.', update_status=update_status) + + +if __name__ == '__main__': + main()