From 8edea560ad5489383de349d2e60fc380d9e03445 Mon Sep 17 00:00:00 2001 From: akumch <37585199+akumch@users.noreply.github.com> Date: Fri, 14 Dec 2018 10:40:26 +0100 Subject: [PATCH] Ansible Integration V1.2 --- DOCUMENTATION.md | 445 ++++++++++++++-- README.md | 7 +- ...r_configuration_against_saved_profiles.yml | 5 +- ...delete_all_arrays_and_create_new_array.yml | 77 +++ examples/elcm_offline_update.yml | 90 ++++ examples/elcm_online_update.yml | 98 ++++ ...server_configuration_profiles_to_files.yml | 4 +- .../irmc_elcm_offline_update_examples.yml | 47 ++ examples/irmc_elcm_online_update_examples.yml | 80 +++ examples/irmc_elcm_repository_examples.yml | 51 ++ examples/irmc_fwbios_update_examples.yml | 2 +- examples/irmc_raid_examples.yml | 63 +++ group_vars/all | 16 + library/irmc_biosbootorder.py | 12 +- library/irmc_cas.py | 4 +- library/irmc_certificate.py | 47 +- library/irmc_compare_profiles.py | 2 +- library/irmc_connectvm.py | 4 +- library/irmc_elcm_offline_update.py | 202 +++++++ library/irmc_elcm_online_update.py | 336 ++++++++++++ library/irmc_elcm_repository.py | 270 ++++++++++ library/irmc_eventlog.py | 4 +- library/irmc_facts.py | 25 +- library/irmc_fwbios_update.py | 23 +- library/irmc_getvm.py | 4 +- library/irmc_idled.py | 4 +- library/irmc_ldap.py | 4 +- library/irmc_license.py | 4 +- library/irmc_ntp.py | 4 +- library/irmc_powerstate.py | 4 +- library/irmc_profiles.py | 6 +- library/irmc_raid.py | 497 ++++++++++++++++++ library/irmc_scci.py | 4 +- library/irmc_session.py | 17 +- library/irmc_setnextboot.py | 4 +- library/irmc_setvm.py | 6 +- library/irmc_task.py | 4 +- library/irmc_user.py | 111 ++-- module_utils/irmc.py | 104 +++- module_utils/irmc_scci_utils.py | 21 +- module_utils/irmc_upload_file.py | 32 +- module_utils/irmc_utils.py | 2 - tests/test_irmc.py | 12 +- tests/test_irmc_scci_utils.py | 2 +- 44 files changed, 2529 insertions(+), 231 deletions(-) create mode 100644 examples/delete_all_arrays_and_create_new_array.yml create mode 100644 examples/elcm_offline_update.yml create mode 100644 examples/elcm_online_update.yml create mode 100644 examples/irmc_elcm_offline_update_examples.yml create mode 100644 examples/irmc_elcm_online_update_examples.yml create mode 100644 examples/irmc_elcm_repository_examples.yml create mode 100644 examples/irmc_raid_examples.yml create mode 100644 library/irmc_elcm_offline_update.py create mode 100644 library/irmc_elcm_online_update.py create mode 100644 library/irmc_elcm_repository.py create mode 100644 library/irmc_raid.py diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 950c6e6..267f705 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -7,6 +7,9 @@ * [irmc_certificate - manage iRMC certificates](#irmc_certificate) * [irmc_compare_profiles - compare two iRMC profiles](#irmc_compare_profiles) * [irmc_connectvm - connect iRMC Virtual Media Data](#irmc_connectvm) + * [irmc_elcm_offline_update - offline update a server via iRMC](#irmc_elcm_offline_update) + * [irmc_elcm_online_update - online update a server via iRMC](#irmc_elcm_online_update) + * [irmc_elcm_repository - configure the eLCM repostory in iRMC](#irmc_elcm_repository) * [irmc_eventlog - handle iRMC eventlogs](#irmc_eventlog) * [irmc_facts - get or set PRIMERGY server and iRMC facts](#irmc_facts) * [irmc_fwbios_update - update iRMC Firmware or server BIOS](#irmc_fwbios_update) @@ -17,6 +20,7 @@ * [irmc_ntp - manage iRMC time options](#irmc_ntp) * [irmc_powerstate - get or set server power state](#irmc_powerstate) * [irmc_profiles - handle iRMC profiles](#irmc_profiles) + * [irmc_raid - handle iRMC RAID](#irmc_raid) * [irmc_scci - execute iRMC remote SCCI commands](#irmc_scci) * [irmc_session - handle iRMC sessions](#irmc_session) * [irmc_setnextboot - configure iRMC to force next boot to specified option](#irmc_setnextboot) @@ -31,14 +35,14 @@ * Ansible module to configure the BIOS boot oder via iRMC. * Using this module will force server into several reboots. * The module will abort by default if the PRIMERGY server is powered on. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * The PRIMERGY server needs to be at least a M2 model. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -104,12 +108,12 @@ Default return values #### Description * Ansible module to manage iRMC CAS settings via iRMC remote scripting interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -199,12 +203,12 @@ Default return values #### Description * Ansible module to manage iRMC certificates via iRMC remote scripting interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -272,7 +276,7 @@ Default return values #### Description * Ansible module to compare two iRMC profiles. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. @@ -312,13 +316,13 @@ Default return values #### Description * Ansible module to connect iRMC Virtual Media Data via the iRMC RedFish interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -362,18 +366,277 @@ Default return values - See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf - See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf +--- +### irmc_elcm_offline_update + +#### Description +* Ansible module to offline update a server via iRMC. +* Using this module may force the server into reboot. +* See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). +* Module Version V1.2. + +#### Requirements + * The module needs to run locally. + * eLCM needs to be licensed in iRMC. + * eLCM SD card needs to be mounted. + * The PRIMERGY server needs to be at least a M2 model. + * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. + * The module assumes that the Update Repository is set correctly in iRMC. + * Python >= 2.6 + * Python modules 'future', 'requests', 'urllib3' + +#### Options + +| Parameter | Required | Default | Choices | Description | +|:----------|:---------|:--------|:--------|:----------- | +| command | No | | prepare
execute
| How to handle iRMC eLCM Offline Update. | +| ignore_power_on | No | False | | Ignore that server is powered on. Server will reboot during update process. Only valid for option 'execute'. | +| irmc_password | Yes | | | Password for iRMC user for basic authentication. | +| irmc_url | Yes | | | IP address of the iRMC to be requested for data. | +| irmc_username | Yes | | | iRMC user for basic authentication. | +| skip_hcl_verify | No | False | | For VMware OS the Hardware Compatibility List (HCL) verification will be skipped and updates will be offered regardless of their compatibility with the current VMware OS version. Irrelevant for other OS. | +| validate_certs | No | True | | Evaluate SSL certificate (set to false for self-signed certificate). | +| wait_for_finish | No | True | | Wait for session to finish. | + +#### Examples +```yaml +# Prepare eLCM Offline Update +- name: Prepare eLCM Offline Update + irmc_elcm_offline_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "prepare" + skip_hcl_verify: "{{ elcm_skip_hcl_verify }}" + ignore_power_on: false + delegate_to: localhost + +# Execute eLCM Offline Update +- name: Execute eLCM Offline Update + irmc_elcm_offline_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "execute" + ignore_power_on: false + wait_for_finish: true +``` + +#### Return Values + +| Name | Description | Returned | Type | Example | +|:-----|:------------|:---------|:-----|:--------| +| For all commands | | | | | + +#### Notes + +- See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf +- See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf + +--- +### irmc_elcm_online_update + +#### Description +* Ansible module to online update a server via iRMC. +* Using this module may force the server into reboot. +* See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). +* PRIMERGY servers running ESXi are not capable of eLCM Online Update due to missing agent. Please run eLCM Offline Update on ESXi servers. +* Module Version V1.2. + +#### Requirements + * The module needs to run locally. + * eLCM needs to be licensed in iRMC. + * eLCM SD card needs to be mounted. + * The PRIMERGY server needs to be at least a M2 model. + * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. + * The module assumes that the Update Repository is set correctly in iRMC. + * Python >= 2.6 + * Python modules 'future', 'requests', 'urllib3' + +#### Options + +| Parameter | Required | Default | Choices | Description | +|:----------|:---------|:--------|:--------|:----------- | +| command | No | | get
set
check
execute
delete
| How to handle iRMC eLCM Online Update. | +| component | No | | | Component whose execution selection is to be changed. | +| irmc_password | Yes | | | Password for iRMC user for basic authentication. | +| irmc_url | Yes | | | IP address of the iRMC to be requested for data. | +| irmc_username | Yes | | | iRMC user for basic authentication. | +| select | No | | | Execution selection for specified component/subcomponent. | +| skip_hcl_verify | No | False | | For VMware OS the Hardware Compatibility List (HCL) verification will be skipped and updates will be offered regardless of their compatibility with the current VMware OS version. Irrelevant for other OS. | +| subcomponent | No | | | Subcomponent whose execution selection is to be changed. | +| validate_certs | No | True | | Evaluate SSL certificate (set to false for self-signed certificate). | +| wait_for_finish | No | True | | Wait for session to finish. | + +#### Examples +```yaml +# Generate eLCM Online Update List +- name: Generate eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "check" + skip_hcl_verify: "{{ elcm_skip_hcl_verify }}" + wait_for_finish: true + delegate_to: localhost + +# Read eLCM Online Update List +- name: Read eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + delegate_to: localhost + +# De-select entry in eLCM Online Update List +- name: De-select entry in eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "set" + component: "{{ elcm_component }}" + subcomponent: "{{ elcm_subcomponent }}" + select: false + wait_for_finish: true + delegate_to: localhost + +# Execute eLCM Online Update +- name: Execute eLCM Online Update + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "execute" + wait_for_finish: true + +# Delete eLCM Online Update List +- name: Delete eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "delete" + delegate_to: localhost +``` + +#### Return Values + +**online update collection returned for command "get":** + +| Name | Description | Returned | Type | Example | +|:-----|:------------|:---------|:-----|:--------| +| update_collection | list of components which require update with specific data (component, subcomponent, status, severity, selected, reboot, current, new) | always | dict | | + +**For all other commands:** + +Default return values + +#### Notes + +- See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf +- See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf + +--- +### irmc_elcm_repository + +#### Description +* Ansible module to configure the eLCM repostory in iRMC. +* iRMC tests access to specified repository and refuses to accept data in case of failure. +* See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). +* Module Version V1.2. + +#### Requirements + * The module needs to run locally. + * The PRIMERGY server needs to be at least a M2 model. + * eLCM needs to be licensed in iRMC. + * eLCM SD card needs to be mounted. + * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. + * Python >= 2.6 + * Python modules 'future', 'requests', 'urllib3' + +#### Options + +| Parameter | Required | Default | Choices | Description | +|:----------|:---------|:--------|:--------|:----------- | +| catalog | No | | | Path to eLCM Update Repository on server. Needs to be set together with 'server'. | +| command | No | get | get
set
| How to handle iRMC eLCM respository data. | +| irmc_password | Yes | | | Password for iRMC user for basic authentication. | +| irmc_url | Yes | | | IP address of the iRMC to be requested for data. | +| irmc_username | Yes | | | iRMC user for basic authentication. | +| proxy_password | No | | | Proxy password to access eLCM Update Repository. | +| proxy_port | No | | | Proxy port to access eLCM Update Repository. | +| proxy_url | No | | | Proxy server to access eLCM Update Repository. | +| proxy_user | No | | | Proxy user to access eLCM Update Repository. | +| server | No | | | Server where eLCM Update Repository is located. Needs to be set together with 'catalog'. | +| use_proxy | No | | | Whether to use proxy to access eLCM Update Repository. | +| validate_certs | No | True | | Evaluate SSL certificate (set to false for self-signed certificate). | +| wait_for_finish | No | True | | Wait for session to finish. | + +#### Examples +```yaml +# Get eLCM repository data +- name: Get eLCM repository data + irmc_elcm_repository: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + delegate_to: localhost + +# Set eLCM repository data +- name: Set eLCM repository data + irmc_elcm_repository: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "set" + server: "{{ elcm_server }}" + catalog: "{{ elcm_catalog }}" + use_proxy: "{{ elcm_use_proxy }}" + proxy_url: "{{ elcm_proxy_url }}" + proxy_port: "{{ elcm_proxy_port }}" + proxy_user: "{{ elcm_proxy_user }}" + proxy_password: "{{ elcm_proxy_password }}" + wait_for_finish: true +``` + +#### Return Values + +**eLCM data returned for command "get":** + +| Name | Description | Returned | Type | Example | +|:-----|:------------|:---------|:-----|:--------| +| repository | eLCM repository data | always | dict | | + +#### Notes + +- See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf +- See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf + --- ### irmc_eventlog #### Description * Ansible module to handle iRMC eventlogs via Restful API. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -446,20 +709,20 @@ Default return values #### Description * Ansible module to get or set basic iRMC and PRIMERGY server data via iRMC RedFish interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options | Parameter | Required | Default | Choices | Description | |:----------|:---------|:--------|:--------|:----------- | | asset_tag | No | | | Server asset tag. | -| command | No | get | get
set
| Get or set server facts. | +| command | No | get | get
set
| How to access server facts. | | contact | No | | | System contact. | | description | No | | | Server description. | | helpdesk_message | No | | | Help desk message. | @@ -499,7 +762,7 @@ Default return values #### Return Values -**facts returned by command "get":** +**facts returned by "get":** | Name | Description | Returned | Type | Example | |:-----|:------------|:---------|:-----|:--------| @@ -528,14 +791,13 @@ Default return values #### Description * Ansible module to get current iRMC update settings or update iRMC Firmware or BIOS via iRMC RedFish interface. * BIOS or firmware flash can be initiated from TFTP server or local file. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' - * python module 'requests_toolbelt' + * Python modules 'future', 'requests', 'urllib3', 'requests_toolbelt' #### Options @@ -550,6 +812,7 @@ Default return values | irmc_url | Yes | | | IP address of the iRMC to be requested for data. | | irmc_username | Yes | | | iRMC user for basic authentication. | | server_name | No | | | TFTP server name or IP. ignored if update_source is set to 'file' | +| timeout | No | 30 | | Timeout for BIOS/iRMC FW flash process in minutes. | | update_source | No | | tftp
file
| Where to get the FW or BIOS update file. | | update_type | No | | irmc
bios
| Whether to update iRMC FW or server BIOS. | | validate_certs | No | True | | Evaluate SSL certificate (set to false for self-signed certificate). | @@ -583,7 +846,7 @@ Default return values file_name: "{{ bios_filename }}" delegate_to: localhost -# Update iRMC FW +# Update iRMC FW via TFTP - name: Update iRMC FW via TFTP irmc_fwbios_update: irmc_url: "{{ inventory_hostname }}" @@ -635,13 +898,13 @@ Default return values #### Description * Ansible module to get iRMC Virtual Media Data via iRMC RedFish interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -698,13 +961,13 @@ Default return values #### Description * Ansible module to get or set server ID LED via iRMC RedFish interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -767,12 +1030,12 @@ Default return values #### Description * Ansible module to manage iRMC LDAP settings via iRMC remote scripting interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -883,12 +1146,12 @@ Default return values #### Description * Ansible module to manage iRMC user accounts via iRMC remote scripting interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -952,12 +1215,12 @@ Default return values #### Description * Ansible module to manage iRMC time options via iRMC remote scripting interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -1028,13 +1291,13 @@ Default return values #### Description * Ansible module to get or set server power state via iRMC RedFish interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -1099,14 +1362,14 @@ Default return values * Ansible module to configure the BIOS boot oder via iRMC. * Using this module may force server into several reboots. * See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * The PRIMERGY server needs to be at least a M2 model. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -1171,17 +1434,109 @@ Default return values - See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf - See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf +--- +### irmc_raid + +#### Description +* Ansible module to configure a PRIMERGY server's RAID via iRMC. +* Using this module may force the server into several reboots. +* See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). +* Module Version V1.2. + +#### Requirements + * The module needs to run locally. + * The PRIMERGY server needs to be at least a M2 model. + * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. + * Python >= 2.6 + * Python modules 'future', 'requests', 'urllib3' + +#### Options + +| Parameter | Required | Default | Choices | Description | +|:----------|:---------|:--------|:--------|:----------- | +| adapter | No | | | The logical number of the adapter to create/delete RAID arrays on/from. | +| array | No | | | The logical number of the RAID array to delete. Use -1 for all arrays. Ignored for 'create'. | +| command | No | get | get
create
delete
| How to handle iRMC RAID. | +| irmc_password | Yes | | | Password for iRMC user for basic authentication. | +| irmc_url | Yes | | | IP address of the iRMC to be requested for data. | +| irmc_username | Yes | | | iRMC user for basic authentication. | +| level | No | | | Raid level of array to be created. Ignored for 'delete'. | +| name | No | | | Name of the array to be created. Ignored for 'delete'. | +| validate_certs | No | True | | Evaluate SSL certificate (set to false for self-signed certificate). | +| wait_for_finish | No | True | | Wait for raid session to finish. | + +#### Examples +```yaml +# Get RAID configuration +- name: Get RAID configuration + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + register: raid + delegate_to: localhost +- name: Show RAID configuration + debug: + msg: "{{ raid.configuration }}" + +# Create RAID array +- name: Create RAID array + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "create" + adapter: "{{ adapter }}" + level: "{{ level }}" + name: "{{ name }}" + delegate_to: localhost + +# Delete RAID array +- name: Delete RAID array + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "delete" + adapter: "{{ adapter }}" + array: "{{ array }}" + delegate_to: localhost +``` + +#### Return Values + +**data returned for command "get":** + +| Name | Description | Returned | Type | Example | +|:-----|:------------|:---------|:-----|:--------| +| configuration | list of available RAID adapters with attached logical and physical disks | always | dict | [{u'raid_level': u'0,1,5,6,10,50,60', u'logical_drives': [{u'id': 0, u'disks': [{u'slot': 0, u'id': u'0', u'name': u'WDC WD5003ABYX-', u'size': u'465 GB'}, {u'slot': 1, u'id': u'1', u'name': u'WDC WD5003ABYX-', u'size': u'465 GB'}], u'raid_level': u'1', u'name': u'LogicalDrive_0'}, {u'id': 1, u'disks': [{u'slot': 2, u'id': u'2', u'name': u'WDC WD5003ABYX-', u'size': u'465 GB'}], u'raid_level': u'0', u'name': u'LogicalDrive_1'}], u'id': u'RAIDAdapter0', u'name': u'RAIDAdapter0', u'unused_disks': [{u'slot': 3, u'id': u'3', u'name': u'WDC WD5003ABYX-', u'size': u'465 GB'}]}] | + +**For all commands:** + +| Name | Description | Returned | Type | Example | +|:-----|:------------|:---------|:-----|:--------| +| log | detailed log data of RAID session | in case of error | dict | {u'SessionLog': {u'Tag': u'', u'WorkSequence': u'obtainProfileParameters', u'Id': 6, u'Entries': {u'Entry': [{u'@date': u'2018/11/09 09:39:19', u'#text': u"CreateSession: Session 'obtainProfile' created with id 6"}, {u'@date': u'2018/11/09 09:39:19', u'#text': u"AttachWorkSequence: Attached work sequence 'obtainProfileParameters' to session 6"}, {u'@date': u'2018/11/09 09:39:45', u'#text': u"ObtainProfileParameters: Finished processing of profile path 'Server/HWConfigurationIrmc/Adapters/RAIDAdapter' with status 'Error'"}, {u'@date': u'2018/11/09 09:39:45', u'#text': u"TerminateSession: 'obtainProfileParameters' is being terminated"}]}}} | + +#### Notes + +- See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf +- See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf + --- ### irmc_scci #### Description * Ansible module to execute iRMC Remote Scripting (SCCI) commands. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -1248,13 +1603,13 @@ Default return values #### Description * Ansible module to handle iRMC sessions via Restful API. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -1328,13 +1683,13 @@ Default return values #### Description * Ansible module to configure iRMC to force next boot to specified option. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -1378,13 +1733,13 @@ Default return values #### Description * Ansible module to set iRMC Virtual Media Data via iRMC RedFish interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -1435,13 +1790,13 @@ Default return values #### Description * Ansible module to handle iRMC tasks via Restful API. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options @@ -1507,12 +1862,12 @@ List of individual task entries (see above) #### Description * Ansible module to manage iRMC user accounts via iRMC remote scripting interface. -* Module Version V1.1. +* Module Version V1.2. #### Requirements * The module needs to run locally. * Python >= 2.6 - * Python module 'future' + * Python modules 'future', 'requests', 'urllib3' #### Options diff --git a/README.md b/README.md index e6c471f..8fd579c 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,14 @@ Fujitsu PRIMERGY servers via iRMC. These modules and examples are intended to provide easy-to-follow and understandable solutions to manage Fujitsu PRIMERY server settings via iRMC. -##### Version: V1.1 +##### Version: V1.2 ## Requirements - Fujitsu PRIMERGY Server with iRMC S4 FW >= 9.04 or iRMC S5 FW >= 1.25 -- Ansible >= 2.1 +- Ansible >= 2.4 - Python >= 2.6 -- Python module 'future' +- Python modules 'future', 'requests', 'urllib3', 'requests_toolbelt' ## Getting started @@ -133,6 +133,7 @@ bare-metal-server provisioning tasks: * V1.0: Initial version * V1.1: New: iRMC FW/BIOS update, BIOS boot order, iRMC profile management +* V1.2: New: eLCM Offline/Online Update, RAID configuration ## License diff --git a/examples/compare_server_configuration_against_saved_profiles.yml b/examples/compare_server_configuration_against_saved_profiles.yml index 8aa21bf..4a8ddb8 100644 --- a/examples/compare_server_configuration_against_saved_profiles.yml +++ b/examples/compare_server_configuration_against_saved_profiles.yml @@ -56,7 +56,7 @@ # compare iRMC Profiles to file - name: Compare SystemConfig profile - irmc_compare: + irmc_compare_profiles: profile_path1: "{{ irmc_sysconfig_file }}" profile_json2: "{{ system_config.profile }}" register: system_config_result @@ -67,7 +67,7 @@ - "{{ system_config_result.comparison_list }}" when: system_config_result.comparison_result == false - name: Compare HWConfigurationIrmc profile - irmc_compare: + irmc_compare_profiles: profile_path1: "{{ irmc_hwconfig_file }}" profile_json2: "{{ hardware_config.profile }}" register: hardware_config_result @@ -77,4 +77,3 @@ - HWConfigurationIrmc profile differs from saved profile - "{{ hardware_config_result.comparison_list }}" when: hardware_config_result.comparison_result == false - diff --git a/examples/delete_all_arrays_and_create_new_array.yml b/examples/delete_all_arrays_and_create_new_array.yml new file mode 100644 index 0000000..30990bc --- /dev/null +++ b/examples/delete_all_arrays_and_create_new_array.yml @@ -0,0 +1,77 @@ +--- +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see [LICENSE.md](LICENSE.md) or https://www.gnu.org/licenses/gpl-3.0.txt) + +# example playbook to get user data from iRMC and store +# in a file in JSON format + +# variables not defined in this playbook are expected to be provided +# elsewhere, e.g. in group_vars/all + +- name: Delete all arrays on controller and create new array + connection: local + hosts: iRMC_group + + vars: + # iRMC login credentials + # irmc_user: "admin" + # irmc_password: "admin" + # Note: set validate_certificate to false for self-signed certificate + # validate_certificate: false + # adapter: 0 + # array_all: -1 + # level: 1 + # name: "TestRaid-1" + + gather_facts: false + + tasks: + - name: Get current RAID configuration + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + register: raid + delegate_to: localhost + - name: Show current RAID configuration + debug: + msg: "{{ raid.configuration }}" + + - name: Delete all RAID arrays on adapter + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "delete" + adapter: "{{ adapter }}" + array: "{{ array_all }}" + delegate_to: localhost + + - name: Create RAID array + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "create" + adapter: "{{ adapter }}" + level: "{{ level }}" + name: "{{ name }}" + delegate_to: localhost + + - name: Get new RAID configuration + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + register: new + delegate_to: localhost + - name: Show new RAID configuration + debug: + msg: "{{ new.configuration }}" diff --git a/examples/elcm_offline_update.yml b/examples/elcm_offline_update.yml new file mode 100644 index 0000000..c47f14b --- /dev/null +++ b/examples/elcm_offline_update.yml @@ -0,0 +1,90 @@ +--- +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see [LICENSE.md](LICENSE.md) or https://www.gnu.org/licenses/gpl-3.0.txt) + +# example playbook to offline update a PRIMERGY server via eLCM + +# variables not defined in this playbook are expected to be provided +# elsewhere, e.g. in group_vars/all + +# Notes: +# - iRMC needs to be supplied with an eLCM License +# - iRMC needs to be supplied with an eLCM SD-Card + +- name: offline update a PRIMERGY server via eLCM + connection: local + hosts: iRMC_group + + vars: + # iRMC login credentials + # irmc_user: "admin" + # irmc_password: "admin" + # Note: set validate_certificate to false for self-signed certificate + # validate_certificate: false + # elcm_server: "https://support.ts.fujitsu.com" + # elcm_catalog: "DownloadManager/globalflash/GF_par_tree.exe" + # elcm_use_proxy: false + # elcm_proxy_url: "http://proxy.local" + # elcm_proxy_port: "8080" + # elcm_proxy_user: "user" + # elcm_proxy_password: "password" + # elcm_component: "PrimSupportPack-Win" + # elcm_subcomponent: "FSC_SCAN" + # elcm_skip_hcl_verify: true + + gather_facts: false + + tasks: + - name: Get system power state + irmc_powerstate: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + register: powerstate + delegate_to: localhost + + - name: Check that server is 'Off' + fail: + msg: "Cannot continue, server is 'On'" + when: powerstate.power_state=="On" + + - name: Configure eLCM Update Repository + irmc_elcm_repository: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "set" + server: "{{ elcm_server }}" + catalog: "{{ elcm_catalog }}" + use_proxy: "{{ elcm_use_proxy }}" + proxy_url: "{{ elcm_proxy_url }}" + proxy_port: "{{ elcm_proxy_port }}" + proxy_user: "{{ elcm_proxy_user }}" + proxy_password: "{{ elcm_proxy_password }}" + delegate_to: localhost + + - name: Prepare eLCM Offline Update + irmc_elcm_offline_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "prepare" + skip_hcl_verify: "{{ elcm_skip_hcl_verify }}" + wait_for_finish: true + delegate_to: localhost + + - name: Execute eLCM Offline Update + irmc_elcm_offline_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "execute" + ignore_power_on: false + wait_for_finish: true + delegate_to: localhost diff --git a/examples/elcm_online_update.yml b/examples/elcm_online_update.yml new file mode 100644 index 0000000..9bcef5a --- /dev/null +++ b/examples/elcm_online_update.yml @@ -0,0 +1,98 @@ +--- +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see [LICENSE.md](LICENSE.md) or https://www.gnu.org/licenses/gpl-3.0.txt) + +# example playbook to online update a PRIMERGY server via eLCM + +# variables not defined in this playbook are expected to be provided +# elsewhere, e.g. in group_vars/all + +# Notes: +# - iRMC needs to be supplied with an eLCM License +# - iRMC needs to be supplied with an eLCM SD-Card + +- name: online update a PRIMERGY server via eLCM + connection: local + hosts: iRMC_group + + vars: + # iRMC login credentials + # irmc_user: "admin" + # irmc_password: "admin" + # Note: set validate_certificate to false for self-signed certificate + # validate_certificate: false + # elcm_server: "https://support.ts.fujitsu.com" + # elcm_catalog: "DownloadManager/globalflash/GF_par_tree.exe" + # elcm_use_proxy: false + # elcm_proxy_url: "http://proxy.local" + # elcm_proxy_port: "8080" + # elcm_proxy_user: "user" + # elcm_proxy_password: "password" + # elcm_component: "PrimSupportPack-Win" + # elcm_subcomponent: "FSC_SCAN" + # elcm_skip_hcl_verify: true + + gather_facts: false + + tasks: + - name: Get system power state + irmc_powerstate: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + register: powerstate + delegate_to: localhost + + - name: Check that server is 'On' + fail: + msg: "Cannot continue, server is 'Off'" + when: powerstate.power_state=="Off" + + - name: Configure eLCM Update Repository + irmc_elcm_repository: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "set" + server: "{{ elcm_server }}" + catalog: "{{ elcm_catalog }}" + use_proxy: "{{ elcm_use_proxy }}" + proxy_url: "{{ elcm_proxy_url }}" + proxy_port: "{{ elcm_proxy_port }}" + proxy_user: "{{ elcm_proxy_user }}" + proxy_password: "{{ elcm_proxy_password }}" + delegate_to: localhost + + - name: Generate eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "check" + skip_hcl_verify: "{{ elcm_skip_hcl_verify }}" + wait_for_finish: true + delegate_to: localhost + + - name: Execute eLCM Online Update + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "execute" + wait_for_finish: true + delegate_to: localhost + + - name: Delete eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "delete" + delegate_to: localhost diff --git a/examples/export_server_configuration_profiles_to_files.yml b/examples/export_server_configuration_profiles_to_files.yml index 4d614af..dc61880 100644 --- a/examples/export_server_configuration_profiles_to_files.yml +++ b/examples/export_server_configuration_profiles_to_files.yml @@ -58,9 +58,9 @@ # Write iRMC Profiles to file - name: Write SystemConfig profile copy: - content: "{{ system_config.ldap }}" + content: "{{ system_config.profile }}" dest: "{{ irmc_sysconfig_file }}" - name: Write HWConfigurationIrmc profile copy: - content: "{{ hardware_config.ldap }}" + content: "{{ hardware_config.profile }}" dest: "{{ irmc_hwconfig_file }}" diff --git a/examples/irmc_elcm_offline_update_examples.yml b/examples/irmc_elcm_offline_update_examples.yml new file mode 100644 index 0000000..b1454cf --- /dev/null +++ b/examples/irmc_elcm_offline_update_examples.yml @@ -0,0 +1,47 @@ +--- +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see [LICENSE.md](LICENSE.md) or https://www.gnu.org/licenses/gpl-3.0.txt) + +# example playbook for module 'irmc_elcm_offline_update' +# to offline update a server via iRMC + +# variables not defined in this playbook are expected to be provided +# elsewhere, e.g. in group_vars/all + +- name: irmc_elcm_offline_update - usage examples + connection: local + hosts: iRMC_group + + vars: + # iRMC login credentials + # irmc_user: "admin" + # irmc_password: "admin" + # Note: set validate_certificate to false for self-signed certificate + # validate_certificate: false + + gather_facts: false + + tasks: + # Prepare eLCM Offline Update + - name: Prepare eLCM Offline Update + irmc_elcm_offline_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "prepare" + skip_hcl_verify: "{{ elcm_skip_hcl_verify }}" + ignore_power_on: false + delegate_to: localhost + + # Execute eLCM Offline Update + - name: Execute eLCM Offline Update + irmc_elcm_offline_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "execute" + ignore_power_on: false + wait_for_finish: true diff --git a/examples/irmc_elcm_online_update_examples.yml b/examples/irmc_elcm_online_update_examples.yml new file mode 100644 index 0000000..b138d1d --- /dev/null +++ b/examples/irmc_elcm_online_update_examples.yml @@ -0,0 +1,80 @@ +--- +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see [LICENSE.md](LICENSE.md) or https://www.gnu.org/licenses/gpl-3.0.txt) + +# example playbook for module 'irmc_elcm_online_update' +# to online update a server via iRMC + +# variables not defined in this playbook are expected to be provided +# elsewhere, e.g. in group_vars/all + +- name: irmc_elcm_online_update - usage examples + connection: local + hosts: iRMC_group + + vars: + # iRMC login credentials + # irmc_user: "admin" + # irmc_password: "admin" + # Note: set validate_certificate to false for self-signed certificate + # validate_certificate: false + + gather_facts: false + + tasks: + # Generate eLCM Online Update List + - name: Generate eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "check" + skip_hcl_verify: "{{ elcm_skip_hcl_verify }}" + wait_for_finish: true + delegate_to: localhost + + # Read eLCM Online Update List + - name: Read eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + delegate_to: localhost + + # De-select entry in eLCM Online Update List + - name: De-select entry in eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "set" + component: "{{ elcm_component }}" + subcomponent: "{{ elcm_subcomponent }}" + select: false + wait_for_finish: true + delegate_to: localhost + + # Execute eLCM Online Update + - name: Execute eLCM Online Update + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "execute" + wait_for_finish: true + + # Delete eLCM Online Update List + - name: Delete eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "delete" + delegate_to: localhost diff --git a/examples/irmc_elcm_repository_examples.yml b/examples/irmc_elcm_repository_examples.yml new file mode 100644 index 0000000..5a00c5e --- /dev/null +++ b/examples/irmc_elcm_repository_examples.yml @@ -0,0 +1,51 @@ +--- +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see [LICENSE.md](LICENSE.md) or https://www.gnu.org/licenses/gpl-3.0.txt) + +# example playbook for module 'irmc_elcm_repository' +# to configure the eLCM repostory in iRMC + +# variables not defined in this playbook are expected to be provided +# elsewhere, e.g. in group_vars/all + +- name: irmc_elcm_repository - usage examples + connection: local + hosts: iRMC_group + + vars: + # iRMC login credentials + # irmc_user: "admin" + # irmc_password: "admin" + # Note: set validate_certificate to false for self-signed certificate + # validate_certificate: false + + gather_facts: false + + tasks: + # Get eLCM repository data + - name: Get eLCM repository data + irmc_elcm_repository: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + delegate_to: localhost + + # Set eLCM repository data + - name: Set eLCM repository data + irmc_elcm_repository: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "set" + server: "{{ elcm_server }}" + catalog: "{{ elcm_catalog }}" + use_proxy: "{{ elcm_use_proxy }}" + proxy_url: "{{ elcm_proxy_url }}" + proxy_port: "{{ elcm_proxy_port }}" + proxy_user: "{{ elcm_proxy_user }}" + proxy_password: "{{ elcm_proxy_password }}" + wait_for_finish: true diff --git a/examples/irmc_fwbios_update_examples.yml b/examples/irmc_fwbios_update_examples.yml index 28eaa2d..d564a23 100644 --- a/examples/irmc_fwbios_update_examples.yml +++ b/examples/irmc_fwbios_update_examples.yml @@ -50,7 +50,7 @@ file_name: "{{ bios_filename }}" delegate_to: localhost - # Update iRMC FW + # Update iRMC FW via TFTP - name: Update iRMC FW via TFTP irmc_fwbios_update: irmc_url: "{{ inventory_hostname }}" diff --git a/examples/irmc_raid_examples.yml b/examples/irmc_raid_examples.yml new file mode 100644 index 0000000..b6880f0 --- /dev/null +++ b/examples/irmc_raid_examples.yml @@ -0,0 +1,63 @@ +--- +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see [LICENSE.md](LICENSE.md) or https://www.gnu.org/licenses/gpl-3.0.txt) + +# example playbook for module 'irmc_raid' +# to handle iRMC RAID + +# variables not defined in this playbook are expected to be provided +# elsewhere, e.g. in group_vars/all + +- name: irmc_raid - usage examples + connection: local + hosts: iRMC_group + + vars: + # iRMC login credentials + # irmc_user: "admin" + # irmc_password: "admin" + # Note: set validate_certificate to false for self-signed certificate + # validate_certificate: false + + gather_facts: false + + tasks: + # Get RAID configuration + - name: Get RAID configuration + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + register: raid + delegate_to: localhost + - name: Show RAID configuration + debug: + msg: "{{ raid.configuration }}" + + # Create RAID array + - name: Create RAID array + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "create" + adapter: "{{ adapter }}" + level: "{{ level }}" + name: "{{ name }}" + delegate_to: localhost + + # Delete RAID array + - name: Delete RAID array + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "delete" + adapter: "{{ adapter }}" + array: "{{ array }}" + delegate_to: localhost diff --git a/group_vars/all b/group_vars/all index a9dd26b..ba5dff3 100644 --- a/group_vars/all +++ b/group_vars/all @@ -48,3 +48,19 @@ # irmc_sysconfig_file: "./SystemConfig.json" # irmc_hwconfig_file: "./HWConfiguration.json" +# elcm_server: "https://support.ts.fujitsu.com" +# elcm_catalog: "DownloadManager/globalflash/GF_par_tree.exe" +# elcm_use_proxy: false +# elcm_proxy_url: "http://proxy.local" +# elcm_proxy_port: "8080" +# elcm_proxy_user: "user" +# elcm_proxy_password: "password" +# elcm_component: "PrimSupportPack-Win" +# elcm_subcomponent: "FSC_SCAN" +# elcm_skip_hcl_verify: true + +# adapter: 0 +# array_all: -1 +# array: 0 +# level: 1 +# name: "TestRaid" diff --git a/library/irmc_biosbootorder.py b/library/irmc_biosbootorder.py index 854c6bc..f6e7170 100644 --- a/library/irmc_biosbootorder.py +++ b/library/irmc_biosbootorder.py @@ -24,14 +24,14 @@ - Ansible module to configure the BIOS boot oder via iRMC. - Using this module will force server into several reboots. - The module will abort by default if the PRIMERGY server is powered on. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - The PRIMERGY server needs to be at least a M2 model. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" @@ -161,8 +161,6 @@ def irmc_biosbootorder(module): # check that all iRMC Profile processing states are terminated waitForIrmcSessionsInactive(module) - # ToDo: Check that next_boot_device exists - # ToDo: Set *only* next_boot_device if module.params['command'] == "default": set_default_bootorder(module) result['changed'] = True @@ -176,7 +174,7 @@ def irmc_biosbootorder(module): devices = get_irmc_json(boot_profile_data, ["Server", "SystemConfig", "BiosConfig", "BiosBootOrder", "Devices"]) if module.params['command'] == "get": - for key, devicelist in devices.items(): + for status, devicelist in devices.items(): result['boot_order'] = [] for device in devicelist: bo = {} @@ -259,7 +257,7 @@ def setup_new_boot_profile(module, profile): devices = get_irmc_json(new_profile, ["Server", "SystemConfig", "BiosConfig", "BiosBootOrder", "Devices"]) new_bootorder = [] - for key, devicelist in devices.items(): + for msg, devicelist in devices.items(): for item in devicelist: for ikey, value in item.items(): if ikey == module.params['boot_key'] and value == module.params['boot_device']: @@ -360,7 +358,7 @@ def waitForIrmcSessionsInactive(module): module.fail_json(msg=msg, status=status) sessions = get_irmc_json(sessiondata.json(), ["SessionList"]) - for key, session in sessions.items(): + for status, session in sessions.items(): for item in session: for ikey, value in item.items(): if ikey == "#text" and "Profile" in value: diff --git a/library/irmc_cas.py b/library/irmc_cas.py index 9ecec9d..a42419e 100644 --- a/library/irmc_cas.py +++ b/library/irmc_cas.py @@ -22,12 +22,12 @@ description: - Ansible module to manage iRMC CAS settings via iRMC remote scripting interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_certificate.py b/library/irmc_certificate.py index 03c68e2..5916d6f 100644 --- a/library/irmc_certificate.py +++ b/library/irmc_certificate.py @@ -22,12 +22,12 @@ description: - Ansible module to manage iRMC certificates via iRMC remote scripting interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" @@ -131,11 +131,14 @@ ] +# Global +result = dict() + + def irmc_certificate(module): - result = dict( - changed=False, - status=0 - ) + # initialize result + result['changed'] = False + result['status'] = 0 if module.check_mode: result['msg'] = "module was not run" @@ -145,17 +148,7 @@ def irmc_certificate(module): # parameter check if module.params['command'] == "set": - if setparam_count == 0: - result['msg'] = "Command 'set' requires at least one parameter to be set!" - result['status'] = 10 - module.fail_json(**result) - - if certdata['private_key_path'] is not None and certdata['ssl_cert_path'] is None or \ - certdata['ssl_cert_path'] is not None and certdata['private_key_path'] is None: - result['msg'] = "Both 'private_key_path' and 'ssl_cert_path' are required to successfully " + \ - "import SSL key pair!" - result['status'] = 11 - module.fail_json(**result) + check_parameters(module, certdata, setparam_count) certdata, status, msg = read_keyfile(certdata, 'private_key_path') if status != 0: @@ -192,19 +185,33 @@ def irmc_certificate(module): module.exit_json(**result) +def check_parameters(module, certdata, setparam_count): + if setparam_count == 0: + result['msg'] = "Command 'set' requires at least one parameter to be set!" + result['status'] = 10 + module.fail_json(**result) + + if certdata['private_key_path'] is not None and certdata['ssl_cert_path'] is None or \ + certdata['ssl_cert_path'] is not None and certdata['private_key_path'] is None: + result['msg'] = "Both 'private_key_path' and 'ssl_cert_path' are required to successfully " + \ + "import SSL key pair!" + result['status'] = 11 + module.fail_json(**result) + + def read_keyfile(data, param): context = cert = "" - result = 0 + retcode = 0 if data[param] != "": try: f = open(data[param], 'r') cert = f.read() except Exception as e: - result = 89 + retcode = 89 context = "Could not read key/certificate file at '{0}': {1}".format(data[param], str(e)) data[param] = cert - return data, result, context + return data, retcode, context def setup_resultdata(data): diff --git a/library/irmc_compare_profiles.py b/library/irmc_compare_profiles.py index 650c7f2..9e49f1b 100644 --- a/library/irmc_compare_profiles.py +++ b/library/irmc_compare_profiles.py @@ -22,7 +22,7 @@ description: - Ansible module to compare two iRMC profiles. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. diff --git a/library/irmc_connectvm.py b/library/irmc_connectvm.py index 9b8ba72..427dbba 100644 --- a/library/irmc_connectvm.py +++ b/library/irmc_connectvm.py @@ -22,13 +22,13 @@ description: - Ansible module to connect iRMC Virtual Media Data via the iRMC RedFish interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_elcm_offline_update.py b/library/irmc_elcm_offline_update.py new file mode 100644 index 0000000..4b3caca --- /dev/null +++ b/library/irmc_elcm_offline_update.py @@ -0,0 +1,202 @@ +#!/usr/bin/python + +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division) +__metaclass__ = type + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + + +DOCUMENTATION = ''' +--- +module: irmc_elcm_offline_update + +short_description: offline update a server via iRMC + +description: + - Ansible module to offline update a server via iRMC. + - Using this module may force the server into reboot. + - See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). + - Module Version V1.2. + +requirements: + - The module needs to run locally. + - eLCM needs to be licensed in iRMC. + - eLCM SD card needs to be mounted. + - The PRIMERGY server needs to be at least a M2 model. + - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. + - The module assumes that the Update Repository is set correctly in iRMC. + - Python >= 2.6 + - Python modules 'future', 'requests', 'urllib3' + +version_added: "2.4" + +author: + - Fujitsu Server PRIMERGY (@FujitsuPrimergy) + +options: + irmc_url: + description: IP address of the iRMC to be requested for data. + required: true + irmc_username: + description: iRMC user for basic authentication. + required: true + irmc_password: + description: Password for iRMC user for basic authentication. + required: true + validate_certs: + description: Evaluate SSL certificate (set to false for self-signed certificate). + required: false + default: true + command: + description: How to handle iRMC eLCM Offline Update. + required: false + choices: ['prepare', 'execute'] + ignore_power_on: + description: Ignore that server is powered on. Server will reboot during update process. + Only valid for option 'execute'. + required: false + default: false + skip_hcl_verify: + description: For VMware OS the Hardware Compatibility List (HCL) verification will be skipped and + updates will be offered regardless of their compatibility with the current VMware OS version. + Irrelevant for other OS. + required: false + default: false + wait_for_finish: + description: Wait for session to finish. + required: false + default: true + +notes: + - See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf + - See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf +''' + +EXAMPLES = ''' +# Prepare eLCM Offline Update +- name: Prepare eLCM Offline Update + irmc_elcm_offline_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "prepare" + skip_hcl_verify: "{{ elcm_skip_hcl_verify }}" + ignore_power_on: false + delegate_to: localhost + +# Execute eLCM Offline Update +- name: Execute eLCM Offline Update + irmc_elcm_offline_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "execute" + ignore_power_on: false + wait_for_finish: true +''' + +RETURN = ''' +# For all commands: + Default return values: +''' + + +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.irmc import irmc_redfish_get, irmc_redfish_put, irmc_redfish_post, get_irmc_json, \ + waitForSessionToFinish, elcm_check_status + + +# Global +result = dict() + + +def irmc_elcm_offline_update(module): + # initialize result + result['changed'] = False + result['status'] = 0 + + if module.check_mode: + result['msg'] = "module was not run" + module.exit_json(**result) + + # check eLCM status + status, data, msg = elcm_check_status(module) + if status > 30 and status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status < 30 or status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + if module.params['command'] == "execute" and module.params['ignore_power_on'] is False: + # Get server power state + status, sysdata, msg = irmc_redfish_get(module, "redfish/v1/Systems/0/") + if status < 100: + module.fail_json(msg=msg, status=status, exception=sysdata) + elif status != 200: + module.fail_json(msg=msg, status=status) + if get_irmc_json(sysdata.json(), "PowerState") == "On": + result['msg'] = "Server is powered on. Cannot continue." + result['status'] = 10 + module.fail_json(**result) + + if module.params['command'] == "prepare": + uri = "rest/v1/Oem/eLCM/OfflineUpdate" + if module.params['skip_hcl_verify'] is True: + uri = uri + "?skipHCLVerification=yes" + status, elcmdata, msg = irmc_redfish_post(module, uri, "") + else: + status, elcmdata, msg = irmc_redfish_put(module, "rest/v1/Oem/eLCM/OfflineUpdate", "") + if status < 100: + module.fail_json(msg=msg, status=status, exception=elcmdata) + elif status == 409: + result['msg'] = "Cannot {0} eLCM update, another session is in progress.".format(module.params['command']) + result['status'] = status + module.fail_json(**result) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + if module.params['wait_for_finish'] is True: + # check that current session is terminated + status, data, msg = waitForSessionToFinish(module, get_irmc_json(elcmdata.json(), ["Session", "Id"])) + if status > 30 and status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, log=data, status=status) + + result['changed'] = True + + module.exit_json(**result) + + +def main(): + # import pdb; pdb.set_trace() + module_args = dict( + irmc_url=dict(required=True, type="str"), + irmc_username=dict(required=True, type="str"), + irmc_password=dict(required=True, type="str", no_log=True), + validate_certs=dict(required=False, type="bool", default=True), + command=dict(required=True, type="str", choices=['prepare', 'execute']), + ignore_power_on=dict(required=False, type="bool", default=False), + skip_hcl_verify=dict(required=False, type="bool", default=False), + wait_for_finish=dict(required=False, type="bool", default=True), + ) + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=False + ) + + irmc_elcm_offline_update(module) + + +if __name__ == '__main__': + main() diff --git a/library/irmc_elcm_online_update.py b/library/irmc_elcm_online_update.py new file mode 100644 index 0000000..6da85eb --- /dev/null +++ b/library/irmc_elcm_online_update.py @@ -0,0 +1,336 @@ +#!/usr/bin/python + +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division) +__metaclass__ = type + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + + +DOCUMENTATION = ''' +--- +module: irmc_elcm_online_update + +short_description: online update a server via iRMC + +description: + - Ansible module to online update a server via iRMC. + - Using this module may force the server into reboot. + - See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). + - PRIMERGY servers running ESXi are not capable of eLCM Online Update due to missing agent. + Please run eLCM Offline Update on ESXi servers. + - Module Version V1.2. + +requirements: + - The module needs to run locally. + - eLCM needs to be licensed in iRMC. + - eLCM SD card needs to be mounted. + - The PRIMERGY server needs to be at least a M2 model. + - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. + - The module assumes that the Update Repository is set correctly in iRMC. + - Python >= 2.6 + - Python modules 'future', 'requests', 'urllib3' + +version_added: "2.4" + +author: + - Fujitsu Server PRIMERGY (@FujitsuPrimergy) + +options: + irmc_url: + description: IP address of the iRMC to be requested for data. + required: true + irmc_username: + description: iRMC user for basic authentication. + required: true + irmc_password: + description: Password for iRMC user for basic authentication. + required: true + validate_certs: + description: Evaluate SSL certificate (set to false for self-signed certificate). + required: false + default: true + command: + description: How to handle iRMC eLCM Online Update. + required: false + choices: ['get', 'set', 'check', 'execute', 'delete'] + skip_hcl_verify: + description: For VMware OS the Hardware Compatibility List (HCL) verification will be skipped and + updates will be offered regardless of their compatibility with the current VMware OS version. + Irrelevant for other OS. + required: false + default: false + wait_for_finish: + description: Wait for session to finish. + required: false + default: true + component: + description: Component whose execution selection is to be changed. + required: false + subcomponent: + description: Subcomponent whose execution selection is to be changed. + required: false + select: + description: Execution selection for specified component/subcomponent. + required: false + + +notes: + - See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf + - See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf +''' + +EXAMPLES = ''' +# Generate eLCM Online Update List +- name: Generate eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "check" + skip_hcl_verify: "{{ elcm_skip_hcl_verify }}" + wait_for_finish: true + delegate_to: localhost + +# Read eLCM Online Update List +- name: Read eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + delegate_to: localhost + +# De-select entry in eLCM Online Update List +- name: De-select entry in eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "set" + component: "{{ elcm_component }}" + subcomponent: "{{ elcm_subcomponent }}" + select: false + wait_for_finish: true + delegate_to: localhost + +# Execute eLCM Online Update +- name: Execute eLCM Online Update + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "execute" + wait_for_finish: true + +# Delete eLCM Online Update List +- name: Delete eLCM Online Update List + irmc_elcm_online_update: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "delete" + delegate_to: localhost +''' + +RETURN = ''' +# online update collection returned for command "get": + update_collection: + description: list of components which require update with specific data + (component, subcomponent, status, severity, selected, reboot, current, new) + returned: always + type: dict + +# For all other commands: + Default return values: +''' + + +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.irmc import irmc_redfish_get, irmc_redfish_put, irmc_redfish_patch, irmc_redfish_post, \ + irmc_redfish_delete, get_irmc_json, elcm_check_status, waitForSessionToFinish + + +# Global +result = dict() +true_false = {False: "deselected", True: "selected"} + + +def irmc_elcm_online_update(module): + # initialize result + result['changed'] = False + result['status'] = 0 + + if module.check_mode: + result['msg'] = "module was not run" + module.exit_json(**result) + + # check eLCM status + status, data, msg = elcm_check_status(module) + if status > 30 and status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status < 30 or status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + # Get server power state + status, sysdata, msg = irmc_redfish_get(module, "redfish/v1/Systems/0/") + if status < 100: + module.fail_json(msg=msg, status=status, exception=sysdata) + elif status != 200: + module.fail_json(msg=msg, status=status) + if get_irmc_json(sysdata.json(), "PowerState") == "Off": + result['msg'] = "Server is powered off. Cannot continue." + result['status'] = 12 + module.fail_json(**result) + + # preliminary parameter check + if module.params['command'] == "set": + if module.params['component'] is None and module.params['subcomponent'] is None: + result['msg'] = "Command 'set' requires 'component' and 'subcomponent' parameters to be set!" + result['status'] = 10 + module.fail_json(**result) + if module.params['select'] is None: + result['msg'] = "Command 'set' requires 'select' parameter to be set!" + result['status'] = 11 + module.fail_json(**result) + + # start doing the actual work + if module.params['command'] == 'set': + elcm_change_component(module) + + if module.params['command'] in ("get", "delete"): + elcm_online_collection(module) + + if module.params['command'] in ("check", "execute"): + elcm_online_update(module) + + module.exit_json(**result) + + +def elcm_change_component(module): + uri = "rest/v1/Oem/eLCM/OnlineUpdate/updateCollection/{0}/{1}?Execution={2}". \ + format(module.params['component'], module.params['subcomponent'], + true_false.get(module.params['select'])) + status, elcmdata, msg = irmc_redfish_patch(module, uri, "", 0) + if status < 100: + module.fail_json(msg=msg, status=status, exception=elcmdata) + elif status == 400: + result['msg'] = "Component '{0}/{1}' does not exist in updateCollection". \ + format(module.params['component'], module.params['subcomponent']) + result['status'] = status + module.fail_json(**result) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + result['changed'] = True + + +def elcm_online_update(module): + if module.params['command'] == "check": + uri = "rest/v1/Oem/eLCM/OnlineUpdate" + if module.params['skip_hcl_verify'] is True: + uri = uri + "?skipHCLVerification=yes" + status, elcmdata, msg = irmc_redfish_post(module, uri, "") + else: + status, elcmdata, msg = irmc_redfish_put(module, "rest/v1/Oem/eLCM/OnlineUpdate/updateCollection", "") + if status < 100: + module.fail_json(msg=msg, status=status, exception=elcmdata) + elif status == 409: + result['msg'] = "Cannot {0} eLCM update, another session is in progress.".format(module.params['command']) + result['status'] = status + module.fail_json(**result) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + if module.params['wait_for_finish'] is True: + # check that current session is terminated + status, data, msg = waitForSessionToFinish(module, get_irmc_json(elcmdata.json(), ["Session", "Id"])) + if status > 30 and status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, log=data, status=status) + + result['changed'] = True + + +def elcm_online_collection(module): + if module.params['command'] == "get": + status, elcmdata, msg = irmc_redfish_get(module, "rest/v1/Oem/eLCM/OnlineUpdate/updateCollection") + else: + status, elcmdata, msg = irmc_redfish_delete(module, "rest/v1/Oem/eLCM/OnlineUpdate/updateCollection") + if status < 100: + module.fail_json(msg=msg, status=status, exception=elcmdata) + elif status == 404: + result['msg'] = "updateCollection does not exist." + if module.params['command'] == "get": + result['status'] = status + module.fail_json(**result) + else: + result['skipped'] = True + module.exit_json(**result) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + if module.params['command'] == "get": + result['update_collection'] = [] + for item in get_irmc_json(elcmdata.json(), ["Links", "Contains"]): + sw = {} +# sw['link'] = get_irmc_json(item, ["@odata.id"]) +# sw['name'] = sw['link'].replace("rest/v1/Oem/eLCM/OnlineUpdate/updateCollection/PrimSupportPack-Win/", "") + + status, swdata, msg = irmc_redfish_get(module, get_irmc_json(item, ["@odata.id"])) + if status < 100: + module.fail_json(msg=msg, status=status, exception=swdata) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + sw['component'] = get_irmc_json(swdata.json(), ["Update", "Component"]) + sw['subcomponent'] = get_irmc_json(swdata.json(), ["Update", "SubComponent"]) + sw['current'] = get_irmc_json(swdata.json(), ["Update", "Current"]) + sw['new'] = get_irmc_json(swdata.json(), ["Update", "New"]) + sw['severity'] = get_irmc_json(swdata.json(), ["Update", "Severity"]) + sw['status'] = get_irmc_json(swdata.json(), ["Update", "Status"]) + sw['reboot'] = get_irmc_json(swdata.json(), ["Update", "Reboot"]) + sw['selected'] = get_irmc_json(swdata.json(), ["Update", "Execution"]) + result['update_collection'].append(sw) + else: + result['changed'] = True + + +def main(): + # import pdb; pdb.set_trace() + module_args = dict( + irmc_url=dict(required=True, type="str"), + irmc_username=dict(required=True, type="str"), + irmc_password=dict(required=True, type="str", no_log=True), + validate_certs=dict(required=False, type="bool", default=True), + command=dict(required=False, type="str", default="get", + choices=['get', 'set', 'check', 'execute', 'delete']), + skip_hcl_verify=dict(required=False, type="bool", default=False), + wait_for_finish=dict(required=False, type="bool", default=True), + component=dict(required=False, type="str"), + subcomponent=dict(required=False, type="str"), + select=dict(required=False, type="bool"), + ) + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=False + ) + + irmc_elcm_online_update(module) + + +if __name__ == '__main__': + main() diff --git a/library/irmc_elcm_repository.py b/library/irmc_elcm_repository.py new file mode 100644 index 0000000..9e77090 --- /dev/null +++ b/library/irmc_elcm_repository.py @@ -0,0 +1,270 @@ +#!/usr/bin/python + +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division) +__metaclass__ = type + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + + +DOCUMENTATION = ''' +--- +module: irmc_elcm_repository + +short_description: configure the eLCM repostory in iRMC + +description: + - Ansible module to configure the eLCM repostory in iRMC. + - iRMC tests access to specified repository and refuses to accept data in case of failure. + - See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). + - Module Version V1.2. + +requirements: + - The module needs to run locally. + - The PRIMERGY server needs to be at least a M2 model. + - eLCM needs to be licensed in iRMC. + - eLCM SD card needs to be mounted. + - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. + - Python >= 2.6 + - Python modules 'future', 'requests', 'urllib3' + +version_added: "2.4" + +author: + - Fujitsu Server PRIMERGY (@FujitsuPrimergy) + +options: + irmc_url: + description: IP address of the iRMC to be requested for data. + required: true + irmc_username: + description: iRMC user for basic authentication. + required: true + irmc_password: + description: Password for iRMC user for basic authentication. + required: true + validate_certs: + description: Evaluate SSL certificate (set to false for self-signed certificate). + required: false + default: true + command: + description: How to handle iRMC eLCM respository data. + required: false + default: get + choices: ['get', 'set'] + server: + description: Server where eLCM Update Repository is located. + Needs to be set together with 'catalog'. + required: false + catalog: + description: Path to eLCM Update Repository on server. + Needs to be set together with 'server'. + required: false + use_proxy: + description: Whether to use proxy to access eLCM Update Repository. + required: false + proxy_url: + description: Proxy server to access eLCM Update Repository. + required: false + proxy_port: + description: Proxy port to access eLCM Update Repository. + required: false + proxy_user: + description: Proxy user to access eLCM Update Repository. + required: false + proxy_password: + description: Proxy password to access eLCM Update Repository. + required: false + wait_for_finish: + description: Wait for session to finish. + required: false + default: true + +notes: + - See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf + - See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf +''' + +EXAMPLES = ''' +# Get eLCM repository data +- name: Get eLCM repository data + irmc_elcm_repository: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + delegate_to: localhost + +# Set eLCM repository data +- name: Set eLCM repository data + irmc_elcm_repository: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "set" + server: "{{ elcm_server }}" + catalog: "{{ elcm_catalog }}" + use_proxy: "{{ elcm_use_proxy }}" + proxy_url: "{{ elcm_proxy_url }}" + proxy_port: "{{ elcm_proxy_port }}" + proxy_user: "{{ elcm_proxy_user }}" + proxy_password: "{{ elcm_proxy_password }}" + wait_for_finish: true +''' + +RETURN = ''' +# eLCM data returned for command "get": + repository: + description: eLCM repository data + returned: always + type: dict +''' + + +import json +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.irmc import irmc_redfish_get, irmc_redfish_put, get_irmc_json, \ + waitForSessionToFinish, elcm_check_status + + +# Global +result = dict() +true_false = {False: "no", True: "yes"} + + +def irmc_elcm_repository(module): + # initialize result + result['changed'] = False + result['status'] = 0 + + if module.check_mode: + result['msg'] = "module was not run" + module.exit_json(**result) + + # check eLCM status + status, data, msg = elcm_check_status(module) + if status > 30 and status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status < 30 or status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + # preliminary parameter check + if module.params['command'] == "set": + none_count = 0 + plist = ("server", "catalog", "use_proxy", "proxy_url", "proxy_port", "proxy_user", "proxy_password") + for param in plist: + if module.params[param] is None: + none_count += 1 + if none_count == len(plist): + result['msg'] = "Command 'set' requires at least one parameter to be set!" + result['status'] = 10 + module.fail_json(**result) + if (module.params['proxy_url'] is None and module.params['proxy_port'] is not None) or \ + (module.params['proxy_url'] is not None and module.params['proxy_port'] is None): + result['msg'] = "'proxy_url' and 'proxy_port' need to be set together!" + result['status'] = 11 + module.fail_json(**result) + + # start doing the actual work + if module.params['command'] == "get": + get_elcm_data(module) + + if module.params['command'] == "set": + set_elcm_data(module) + result['changed'] = True + + module.exit_json(**result) + + +def get_elcm_data(module): + status, elcmdata, msg = irmc_redfish_get(module, "rest/v1/Oem/eLCM/Repository/Update") + if status < 100: + module.fail_json(msg=msg, status=status, exception=elcmdata) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + result['repository'] = {} + result['repository']['server'] = get_irmc_json(elcmdata.json(), ["Repository", "Server", "URL"]) + result['repository']['catalog'] = get_irmc_json(elcmdata.json(), ["Repository", "Server", "Catalog"]) + result['repository']['use_proxy'] = get_irmc_json(elcmdata.json(), ["Repository", "Server", "UseProxy"]) + result['repository']['proxy_url'] = get_irmc_json(elcmdata.json(), ["Repository", "Proxy", "URL"]) + result['repository']['proxy_port'] = get_irmc_json(elcmdata.json(), ["Repository", "Proxy", "Port"]) + result['repository']['proxy_user'] = get_irmc_json(elcmdata.json(), ["Repository", "Proxy", "User"]) + result['repository']['proxy_password'] = get_irmc_json(elcmdata.json(), ["Repository", "Proxy", "Password"]) + + +def set_elcm_data(module): + body = {'Repository': {}} + if module.params['server'] is not None or module.params['catalog'] is not None or \ + module.params['use_proxy'] is not None: + body['Repository']['Server'] = {} + if module.params['server'] is not None: + body['Repository']['Server']['URL'] = module.params['server'] + if module.params['catalog'] is not None: + body['Repository']['Server']['Catalog'] = module.params['catalog'] + if module.params['use_proxy'] is not None: + body['Repository']['Server']['UseProxy'] = true_false.get(module.params['use_proxy']) + if module.params['proxy_url'] is not None or module.params['proxy_port'] is not None or \ + module.params['proxy_user'] is not None or module.params['proxy_password'] is not None: + body['Repository']['Proxy'] = {} + if module.params['proxy_url'] is not None: + body['Repository']['Proxy']['URL'] = module.params['proxy_url'] + if module.params['proxy_port'] is not None: + body['Repository']['Proxy']['Port'] = module.params['proxy_port'] + if module.params['proxy_user'] is not None: + body['Repository']['Proxy']['User'] = module.params['proxy_user'] + if module.params['proxy_password'] is not None and module.params['proxy_password'] != "": + body['Repository']['Proxy']['Password'] = module.params['proxy_password'] + + status, elcmdata, msg = irmc_redfish_put(module, "rest/v1/Oem/eLCM/Repository/Update", json.dumps(body)) + if status < 100: + module.fail_json(msg=msg, status=status, exception=elcmdata) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + if module.params['wait_for_finish'] is True: + # check that current session is terminated + status, data, msg = waitForSessionToFinish(module, get_irmc_json(elcmdata.json(), ["Session", "Id"])) + if status > 30 and status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, log=data, status=status) + + +def main(): + # import pdb; pdb.set_trace() + module_args = dict( + irmc_url=dict(required=True, type="str"), + irmc_username=dict(required=True, type="str"), + irmc_password=dict(required=True, type="str", no_log=True), + validate_certs=dict(required=False, type="bool", default=True), + command=dict(required=False, type="str", default="get", choices=['get', 'set']), + server=dict(required=False, type="str"), + catalog=dict(required=False, type="str"), + use_proxy=dict(required=False, type="bool"), + proxy_url=dict(required=False, type="str"), + proxy_port=dict(required=False, type="str"), + proxy_user=dict(required=False, type="str"), + proxy_password=dict(required=False, type="str", no_log=True), + wait_for_finish=dict(required=False, type="bool", default=True), + ) + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=False + ) + + irmc_elcm_repository(module) + + +if __name__ == '__main__': + main() diff --git a/library/irmc_eventlog.py b/library/irmc_eventlog.py index a2c79ac..c0c4d07 100644 --- a/library/irmc_eventlog.py +++ b/library/irmc_eventlog.py @@ -22,13 +22,13 @@ description: - Ansible module to handle iRMC eventlogs via Restful API. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_facts.py b/library/irmc_facts.py index d9fdb12..ce2d15a 100644 --- a/library/irmc_facts.py +++ b/library/irmc_facts.py @@ -3,7 +3,7 @@ # FUJITSU LIMITED # Copyright 2018 FUJITSU LIMITED # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -from __future__ import (absolute_import, division) +from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -22,13 +22,13 @@ description: - Ansible module to get or set basic iRMC and PRIMERGY server data via iRMC RedFish interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" @@ -47,10 +47,11 @@ required: true validate_certs: description: Evaluate SSL certificate (set to false for self-signed certificate). + type: bool required: false default: true command: - description: Get or set server facts. + description: How to access server facts. required: false default: get choices: ['get', 'set'] @@ -103,7 +104,7 @@ ''' RETURN = ''' -# facts returned by command "get": +# facts returned by "get": hardware.ethernetinterfaces: description: dict with total number (count) @@ -286,13 +287,13 @@ def irmc_facts(module): module.exit_json(**result) # parameter check - if module.params['command'] == "set" and \ - module.params['asset_tag'] is None and module.params['description'] is None and \ - module.params['helpdesk_message'] is None and module.params['location'] is None and \ - module.params['contact'] is None: - result['msg'] = "Command 'set' requires at least one parameter to be set!" - result['status'] = 10 - module.fail_json(**result) + if module.params['command'] == "set": + if module.params['asset_tag'] is None and module.params['description'] is None and \ + module.params['helpdesk_message'] is None and module.params['location'] is None and \ + module.params['contact'] is None: + result['msg'] = "Command 'set' requires at least one parameter to be set!" + result['status'] = 10 + module.fail_json(**result) # Get iRMC OEM system data status, oemdata, msg = irmc_redfish_get(module, "redfish/v1/Systems/0/Oem/ts_fujitsu/System") diff --git a/library/irmc_fwbios_update.py b/library/irmc_fwbios_update.py index 897a2e5..9cd40b6 100644 --- a/library/irmc_fwbios_update.py +++ b/library/irmc_fwbios_update.py @@ -23,14 +23,13 @@ description: - Ansible module to get current iRMC update settings or update iRMC Firmware or BIOS via iRMC RedFish interface. - BIOS or firmware flash can be initiated from TFTP server or local file. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' - - python module 'requests_toolbelt' + - Python modules 'future', 'requests', 'urllib3', 'requests_toolbelt' version_added: "2.4" @@ -68,6 +67,10 @@ description: Whether to update iRMC FW or server BIOS. required: false choices: ['irmc', 'bios'] + timeout: + description: Timeout for BIOS/iRMC FW flash process in minutes. + required: false + default: 30 server_name: description: TFTP server name or IP. ignored if update_source is set to 'file' @@ -118,7 +121,7 @@ file_name: "{{ bios_filename }}" delegate_to: localhost -# Update iRMC FW +# Update iRMC FW via TFTP - name: Update iRMC FW via TFTP irmc_fwbios_update: irmc_url: "{{ inventory_hostname }}" @@ -220,7 +223,7 @@ from datetime import datetime from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.irmc import irmc_redfish_get, irmc_redfish_post, irmc_redfish_patch, get_irmc_json +from ansible.module_utils.irmc import irmc_redfish_get, irmc_redfish_patch, irmc_redfish_post, get_irmc_json from ansible.module_utils.irmc_upload_file import irmc_redfish_post_file @@ -322,9 +325,6 @@ def preliminary_parameter_check(module): module.exit_json(**result) -# irmc: A BIOS firmware update has been started. A system reboot is required to continue with the update. -# Status: FlashImageDownloadedSuccessfully (0% / 33%) [Running] -# Status: (0% / 0%) [Pending] def wait_for_update_to_finish(module, location, power_state): rebootDone = None start_time = time.time() @@ -333,8 +333,8 @@ def wait_for_update_to_finish(module, location, power_state): elapsed_time = time.time() - start_time # make sure the module does not get stuck if anything goes wrong now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - if elapsed_time > 30 * 60: - msg = "Timeout of 30 minutes exceeded. Abort." + if elapsed_time > module.params['timeout'] * 60: + msg = "Timeout of {0} minutes exceeded. Abort.".format(module.params['timeout']) module.fail_json(msg=msg, status=20) status, sdata, msg = irmc_redfish_get(module, "{0}".format(location[1:])) @@ -360,8 +360,6 @@ def wait_for_update_to_finish(module, location, power_state): if "Key" in get_irmc_json(sdata.json(), "error"): rebootDone = False oemstate = get_irmc_json(sdata.json(), ["Oem", "ts_fujitsu", "StatusOEM"]) - state_progress = get_irmc_json(sdata.json(), ["Oem", "ts_fujitsu", "StateProgressPercent"]) - overall_progress = get_irmc_json(sdata.json(), ["Oem", "ts_fujitsu", "TotalProgressPercent"]) state = get_irmc_json(sdata.json(), "TaskState") # make sure the process ran through if power_state == "On" and oemstate == "Pending": @@ -456,6 +454,7 @@ def main(): ignore_power_on=dict(required=False, type="bool", default=False), update_source=dict(required=False, type="str", choices=['tftp', 'file']), update_type=dict(required=False, type="str", choices=['irmc', 'bios']), + timeout=dict(required=False, type="int", default=30), server_name=dict(required=False, type="str"), file_name=dict(required=False, type="str"), irmc_flash_selector=dict(required=False, type="str", choices=['Auto', 'LowFWImage', 'HighFWImage']), diff --git a/library/irmc_getvm.py b/library/irmc_getvm.py index 013c7b1..de3932f 100644 --- a/library/irmc_getvm.py +++ b/library/irmc_getvm.py @@ -22,13 +22,13 @@ description: - Ansible module to get iRMC Virtual Media Data via iRMC RedFish interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_idled.py b/library/irmc_idled.py index a4f237d..066194f 100644 --- a/library/irmc_idled.py +++ b/library/irmc_idled.py @@ -22,13 +22,13 @@ description: - Ansible module to get or set server ID LED via iRMC RedFish interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_ldap.py b/library/irmc_ldap.py index 824f786..5133d8e 100644 --- a/library/irmc_ldap.py +++ b/library/irmc_ldap.py @@ -22,12 +22,12 @@ description: - Ansible module to manage iRMC LDAP settings via iRMC remote scripting interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_license.py b/library/irmc_license.py index 025bf6c..472669f 100644 --- a/library/irmc_license.py +++ b/library/irmc_license.py @@ -22,12 +22,12 @@ description: - Ansible module to manage iRMC user accounts via iRMC remote scripting interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_ntp.py b/library/irmc_ntp.py index 529c4af..655c346 100644 --- a/library/irmc_ntp.py +++ b/library/irmc_ntp.py @@ -22,12 +22,12 @@ description: - Ansible module to manage iRMC time options via iRMC remote scripting interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_powerstate.py b/library/irmc_powerstate.py index eb058a1..a4219fa 100644 --- a/library/irmc_powerstate.py +++ b/library/irmc_powerstate.py @@ -23,13 +23,13 @@ description: - Ansible module to get or set server power state via iRMC RedFish interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_profiles.py b/library/irmc_profiles.py index a8577c4..953df5e 100644 --- a/library/irmc_profiles.py +++ b/library/irmc_profiles.py @@ -24,14 +24,14 @@ - Ansible module to configure the BIOS boot oder via iRMC. - Using this module may force server into several reboots. - See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - The PRIMERGY server needs to be at least a M2 model. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" @@ -272,7 +272,7 @@ def list_profiles(module): result['profiles'] = {} for profile in get_irmc_json(sysdata.json(), ["Links", "profileStore"]): - for key, value in profile.items(): + for status, value in profile.items(): profile = {} profile['Name'] = value.replace("rest/v1/Oem/eLCM/ProfileManagement/", "") profile['Location'] = value diff --git a/library/irmc_raid.py b/library/irmc_raid.py new file mode 100644 index 0000000..a21e2db --- /dev/null +++ b/library/irmc_raid.py @@ -0,0 +1,497 @@ +#!/usr/bin/python + +# FUJITSU LIMITED +# Copyright 2018 FUJITSU LIMITED +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division) +__metaclass__ = type + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + + +DOCUMENTATION = ''' +--- +module: irmc_raid + +short_description: handle iRMC RAID + +description: + - Ansible module to configure a PRIMERGY server's RAID via iRMC. + - Using this module may force the server into several reboots. + - See specification [iRMC RESTful API](http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf). + - Module Version V1.2. + +requirements: + - The module needs to run locally. + - The PRIMERGY server needs to be at least a M2 model. + - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. + - Python >= 2.6 + - Python modules 'future', 'requests', 'urllib3' + +version_added: "2.4" + +author: + - Fujitsu Server PRIMERGY (@FujitsuPrimergy) + +options: + irmc_url: + description: IP address of the iRMC to be requested for data. + required: true + irmc_username: + description: iRMC user for basic authentication. + required: true + irmc_password: + description: Password for iRMC user for basic authentication. + required: true + validate_certs: + description: Evaluate SSL certificate (set to false for self-signed certificate). + required: false + default: true + command: + description: How to handle iRMC RAID. + required: false + default: get + choices: ['get', 'create', 'delete'] + adapter: + description: The logical number of the adapter to create/delete RAID arrays on/from. + required: false + array: + description: The logical number of the RAID array to delete. Use -1 for all arrays. Ignored for 'create'. + required: false + level: + description: Raid level of array to be created. Ignored for 'delete'. + required: false + name: + description: Name of the array to be created. Ignored for 'delete'. + required: false + wait_for_finish: + description: Wait for raid session to finish. + required: false + default: true + +notes: + - See http://manuals.ts.fujitsu.com/file/13371/irmc-restful-spec-en.pdf + - See http://manuals.ts.fujitsu.com/file/13372/irmc-redfish-wp-en.pdf +''' + +EXAMPLES = ''' +# Get RAID configuration +- name: Get RAID configuration + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "get" + register: raid + delegate_to: localhost +- name: Show RAID configuration + debug: + msg: "{{ raid.configuration }}" + +# Create RAID array +- name: Create RAID array + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "create" + adapter: "{{ adapter }}" + level: "{{ level }}" + name: "{{ name }}" + delegate_to: localhost + +# Delete RAID array +- name: Delete RAID array + irmc_raid: + irmc_url: "{{ inventory_hostname }}" + irmc_username: "{{ irmc_user }}" + irmc_password: "{{ irmc_password }}" + validate_certs: "{{ validate_certificate }}" + command: "delete" + adapter: "{{ adapter }}" + array: "{{ array }}" + delegate_to: localhost +''' + +RETURN = ''' +# data returned for command "get": + configuration: + description: list of available RAID adapters with attached logical and physical disks + returned: always + type: dict + sample: + [{ + "id": "RAIDAdapter0", + "logical_drives": [{ + "raid_level": "1", + "disks": [{ "slot": 0, "id": "0", "name": "WDC WD5003ABYX-", "size": "465 GB" }, + { "slot": 1, "id": "1", "name": "WDC WD5003ABYX-", "size": "465 GB" }], + "id": 0, + "name": "LogicalDrive_0" + }, { + "raid_level": "0", + "disks": [{ "slot": 2, "id": "2", "name": "WDC WD5003ABYX-", "size": "465 GB" }], + "id": 1, + "name": "LogicalDrive_1" + }], + "raid_level": "0,1,5,6,10,50,60", + "name": "RAIDAdapter0", + "unused_disks": [{ "slot": 3, "id": "3", "name": "WDC WD5003ABYX-", "size": "465 GB" }] + }] + +# For all commands: + log: + description: detailed log data of RAID session + returned: in case of error + type: dict + sample: + "SessionLog": { + "Entries": { + "Entry": [ + { "#text": "CreateSession: Session 'obtainProfile' created with id 6", "@date": "2018/11/09 09:39:19" }, + { "#text": "AttachWorkSequence: Attached work sequence 'obtainProfileParameters' to session 6", "@date": "2018/11/09 09:39:19" }, + { "#text": "ObtainProfileParameters: Finished processing of profile path 'Server/HWConfigurationIrmc/Adapters/RAIDAdapter' with status 'Error'", "@date": "2018/11/09 09:39:45" }, + { "#text": "TerminateSession: 'obtainProfileParameters' is being terminated", "@date": "2018/11/09 09:39:45" } + ] + }, + "Id": 6, + "Tag": "", + "WorkSequence": "obtainProfileParameters" + } +''' + + +import json +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.irmc import irmc_redfish_get, irmc_redfish_post, irmc_redfish_delete, get_irmc_json, \ + waitForSessionToFinish + + +# Global +result = dict() + + +def irmc_raid(module): + # initialize result + result['changed'] = False + result['status'] = 0 + + if module.check_mode: + result['msg'] = "module was not run" + module.exit_json(**result) + + # preliminary parameter check + preliminary_parameter_check(module) + + # get current RAID configuration + irmc_profile = get_raid_data(module) + raid_configuration = get_raid_configuration(module, irmc_profile) + + if module.params['command'] == "get": + result['configuration'] = raid_configuration + + if module.params['command'] == "create": + create_array(module, raid_configuration) + + if module.params['command'] == "delete": + delete_array(module, raid_configuration) + + module.exit_json(**result) + + +def preliminary_parameter_check(module): + if module.params['command'] != "get": + # Get server power state + status, sysdata, msg = irmc_redfish_get(module, "redfish/v1/Systems/0/") + if status < 100: + module.fail_json(msg=msg, status=status, exception=sysdata) + elif status != 200: + module.fail_json(msg=msg, status=status) + if get_irmc_json(sysdata.json(), "PowerState") == "On": + result['msg'] = "Server is powered on. Cannot continue." + result['status'] = 10 + module.fail_json(**result) + if module.params['command'] == "create" and \ + module.params['adapter'] is None and module.params['level'] is None: + result['msg'] = "Command 'create' requires 'adapter' and 'level' to be set." + result['status'] = 10 + module.fail_json(**result) + if module.params['command'] == "delete" and module.params['adapter'] is None and module.params['array'] is None: + result['msg'] = "Command 'delete' requires 'adapter' and 'array' to be set." + result['status'] = 11 + module.fail_json(**result) + + +def create_array(module, raid_configuration): + for adapter in raid_configuration: + if adapter["id"].replace("RAIDAdapter", "") != module.params['adapter']: + continue + else: + disk_data = adapter['unused_disks'] + if not disk_data: + result['msg'] = "No un-used disks available on controller {0}".format(module.params['adapter']) + result['status'] = 41 + module.fail_json(**result) + + if module.params['level'] not in adapter['level']: + result['msg'] = "Adapter {0} does not support RAID level {1}. Supported: {2}". \ + format(module.params['adapter'], module.params['level'], adapter['level']) + result['status'] = 42 + module.fail_json(**result) + + if module.params['name'] is not None: + raid_array = {"@Action": "Create", "Name": module.params['name'], + "RaidLevel": module.params['level']} + else: + raid_array = {"@Action": "Create", "RaidLevel": module.params['level']} + + body = { + "Server": { + "HWConfigurationIrmc": { + "@Processing": "execute", + "Adapters": { + "RAIDAdapter": [{ + "@AdapterId": adapter["id"], + "@ConfigurationType": "Addressing", + "LogicalDrives": { + "LogicalDrive": [raid_array] + }, + }] + }, + "@Version": "1.00" + }, + "@Version": "1.01" + } + } + + # Set new configuration + apply_raid_configuration(module, body) + return + + result['msg'] = "Specified adapter {0} does not exist.".format(module.params['adapter']) + result['status'] = 40 + module.fail_json(**result) + + +def delete_array(module, raid_configuration): + for adapter in raid_configuration: + if adapter["id"].replace("RAIDAdapter", "") != module.params['adapter']: + continue + else: + logical_drives = adapter['logical_drives'] + + if not logical_drives: + result['msg'] = "There are no logical drives on adapter {0}.".format(module.params['adapter']) + if module.params['array'] == "-1": + result['skipped'] = True + module.exit_json(**result) + else: + result['status'] = 51 + module.fail_json(**result) + + lds = [] + for array in logical_drives: + if module.params['array'] != "-1" and "{0}".format(array["id"]) != module.params['array']: + ld = {"@Number": array['id'], "@Action": "None"} + continue + else: + ld = {"@Number": array['id'], "@Action": "Delete"} + lds.append(ld) + + if not lds: + result['msg'] = "Specified array {0} does not exist.".format(module.params['array']) + result['status'] = 52 + module.fail_json(**result) + + body = { + "Server": { + "HWConfigurationIrmc": { + "@Processing": "execute", + "Adapters": { + "RAIDAdapter": [{ + "@AdapterId": adapter["id"], + "@ConfigurationType": "Addressing", + "LogicalDrives": { + "LogicalDrive": lds + }, + }] + }, + "@Version": "1.00" + }, + "@Version": "1.01" + } + } + + # Set new configuration + apply_raid_configuration(module, body) + return + + result['msg'] = "Specified adapter {0} does not exist.".format(module.params['adapter']) + result['status'] = 40 + module.fail_json(**result) + + +def apply_raid_configuration(module, body): + status, sysdata, msg = irmc_redfish_post(module, "rest/v1/Oem/eLCM/ProfileManagement/set", json.dumps(body)) + if status < 100: + module.fail_json(msg=msg, status=status, exception=sysdata) + elif status == 406: + result['msg'] = "Raid Configuration cannot be {0}d.".format(module.params['command']) + module.fail_json(msg=msg, status=status) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + if module.params['wait_for_finish'] is True: + # check that current session is terminated + status, data, msg = waitForSessionToFinish(module, get_irmc_json(sysdata.json(), ["Session", "Id"])) + if status > 30 and status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, log=data, status=status) + + result['changed'] = True + + +def get_raid_data(module): + # make sure RAIDAdapter profile is up-to-date + status, sysdata, msg = irmc_redfish_delete(module, "/rest/v1/Oem/eLCM/ProfileManagement/RAIDAdapter") + if status < 100: + module.fail_json(msg=msg, status=status, exception=sysdata) + elif status not in (200, 202, 204, 404): + module.fail_json(msg=msg, status=status) + + url = "rest/v1/Oem/eLCM/ProfileManagement/get?PARAM_PATH=Server/HWConfigurationIrmc/Adapters/RAIDAdapter" + status, sysdata, msg = irmc_redfish_post(module, url, "") + if status < 100: + module.fail_json(msg=msg, status=status, exception=sysdata) + elif status == 404: + result['msg'] = "Requested profile 'HWConfigurationIrmc/Adapters/RAIDAdapter' cannot be created." + module.fail_json(msg=msg, status=status) + elif status == 409: + result['msg'] = "Requested profile 'HWConfigurationIrmc/Adapters/RAIDAdapter' already exists." + module.fail_json(msg=msg, status=status) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + if module.params['wait_for_finish'] is True: + # check that current session is terminated + status, data, msg = waitForSessionToFinish(module, get_irmc_json(sysdata.json(), ["Session", "Id"])) + if status > 30 and status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, log=data, status=status) + + status, sysdata, msg = irmc_redfish_get(module, "/rest/v1/Oem/eLCM/ProfileManagement/RAIDAdapter") + if status < 100: + module.fail_json(msg=msg, status=status, exception=sysdata) + elif status == 404: + module.fail_json(msg="Requested profile 'HWConfigurationIrmc/Adapters/RAIDAdapter' does not exist.", + status=status) + elif status != 200: + module.fail_json(msg=msg, status=status) + + return sysdata.json() + + +def get_raid_configuration(module, irmc_profile): + raid_configuration = [] + for adapter in get_irmc_json(irmc_profile, ["Server", "HWConfigurationIrmc", "Adapters", "RAIDAdapter"]): + adapter_list = get_adapter(module, adapter) + disk_data = get_irmc_json(adapter, ["PhysicalDisks", "PhysicalDisk"]) + ld_data = get_irmc_json(adapter, ["LogicalDrives", "LogicalDrive"]) + for pd in disk_data: + disk_list = get_disk(pd) + adapter_list['unused_disks'].append(disk_list) + if "Key" not in ld_data: + for ld in ld_data: + array_list = get_logicaldrive(ld) + for ref in get_irmc_json(ld, ["ArrayRefs", "ArrayRef"]): + for array in get_irmc_json(adapter, ["Arrays", "Array"]): + if get_irmc_json(ref, ["@Number"]) == get_irmc_json(array, ["@Number"]): + for disk in get_irmc_json(array, ["PhysicalDiskRefs", "PhysicalDiskRef"]): + for pd in disk_data: + if get_irmc_json(disk, ["@Number"]) == get_irmc_json(pd, ["@Number"]): + break + disk_list = get_disk(pd) + array_list['disks'].append(disk_list) + adapter_list['unused_disks'].remove(disk_list) + adapter_list['logical_drives'].append(array_list) + raid_configuration.append(adapter_list) + return raid_configuration + + +def get_adapter(module, adapter): + ctrl = {} + ctrl['id'] = get_irmc_json(adapter, ["@AdapterId"]) + ctrl['name'] = ctrl['id'] + ctrl['level'] = get_irmc_json(adapter, ["Features", "RaidLevel"]) + ctrl['logical_drives'] = [] + ctrl['unused_disks'] = [] + status, hwdata, msg = irmc_redfish_get(module, "redfish/v1/Systems/0/Storage?$expand=Members") + if status < 100: + module.fail_json(msg=msg, status=status, exception=hwdata) + elif status != 200: + module.fail_json(msg=msg, status=status) + for member in get_irmc_json(hwdata.json(), "Members"): + # iRMC has each StroageController with its own Storage + for sc in get_irmc_json(member, "StorageControllers"): + if get_irmc_json(adapter, ["@AdapterId"]).replace("RAIDAdapter", "") == get_irmc_json(sc, ["MemberId"]): + ctrl['name'] = get_irmc_json(sc, ["Model"]) + ctrl['firmware'] = get_irmc_json(sc, ["FirmwareVersion"]) + ctrl['drives'] = get_irmc_json(sc, ["Oem", "ts_fujitsu", "DriveCount"]) + ctrl['volumes'] = get_irmc_json(sc, ["Oem", "ts_fujitsu", "VolumeCount"]) + break + return(ctrl) + + +def get_logicaldrive(ld): + logicaldrive = {} + logicaldrive['id'] = get_irmc_json(ld, ["@Number"]) + logicaldrive['level'] = get_irmc_json(ld, ["RaidLevel"]) + logicaldrive['name'] = get_irmc_json(ld, ["Name"]) + logicaldrive['disks'] = [] + return(logicaldrive) + + +def get_disk(pd): + disk = {} + disk['id'] = get_irmc_json(pd, ["@Number"]) + disk['slot'] = get_irmc_json(pd, ["Slot"]) + disk['name'] = get_irmc_json(pd, ["Product"]) + disk['size'] = "{0} {1}".format(get_irmc_json(pd, ["Size", "#text"]), get_irmc_json(pd, ["Size", "@Unit"])) + return(disk) + + +def main(): + # import pdb; pdb.set_trace() + module_args = dict( + irmc_url=dict(required=True, type="str"), + irmc_username=dict(required=True, type="str"), + irmc_password=dict(required=True, type="str", no_log=True), + validate_certs=dict(required=False, type="bool", default=True), + command=dict(required=False, type="str", default="list", + choices=['get', 'create', 'delete']), + adapter=dict(required=False, type="str"), + array=dict(required=False, type="str"), + level=dict(required=False, type="str"), + name=dict(required=False, type="str"), + wait_for_finish=dict(required=False, type="bool", default=True), + ) + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=False + ) + + irmc_raid(module) + + +if __name__ == '__main__': + main() diff --git a/library/irmc_scci.py b/library/irmc_scci.py index d2b76c2..842f4b5 100644 --- a/library/irmc_scci.py +++ b/library/irmc_scci.py @@ -22,12 +22,12 @@ description: - Ansible module to execute iRMC Remote Scripting (SCCI) commands. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_session.py b/library/irmc_session.py index b158a04..380bd9a 100644 --- a/library/irmc_session.py +++ b/library/irmc_session.py @@ -22,13 +22,13 @@ description: - Ansible module to handle iRMC sessions via Restful API. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" @@ -169,15 +169,13 @@ def irmc_session(module): module.exit_json(**result) # preliminary parameter check - if (module.params['command'] in ("get", "remove", "terminate")) and module.params['id'] is None: - result['msg'] = "Command '{0}' requires 'id' parameter to be set!".format(module.params['command']) - result['status'] = 10 - module.fail_json(**result) + preliminary_parameter_check(module) sessions = get_irmc_sessions(module) if module.params['command'] == "list": result['sessions'] = {} id_found = 0 + for key, session in sessions.items(): for item in session: if module.params['command'] == "clearall": @@ -211,6 +209,13 @@ def irmc_session(module): module.exit_json(**result) +def preliminary_parameter_check(module): + if (module.params['command'] in ("get", "remove", "terminate")) and module.params['id'] is None: + result['msg'] = "Command '{0}' requires 'id' parameter to be set!".format(module.params['command']) + result['status'] = 10 + module.fail_json(**result) + + def get_irmc_sessions(module): status, sessiondata, msg = irmc_redfish_get(module, "sessionInformation") if status < 100: diff --git a/library/irmc_setnextboot.py b/library/irmc_setnextboot.py index d35a419..843aa2a 100644 --- a/library/irmc_setnextboot.py +++ b/library/irmc_setnextboot.py @@ -22,13 +22,13 @@ description: - Ansible module to configure iRMC to force next boot to specified option. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_setvm.py b/library/irmc_setvm.py index be993f0..049f84a 100644 --- a/library/irmc_setvm.py +++ b/library/irmc_setvm.py @@ -22,13 +22,13 @@ description: - Ansible module to set iRMC Virtual Media Data via iRMC RedFish interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" @@ -125,7 +125,7 @@ def irmc_setvirtualmedia(module): result['msg'] = "module was not run" module.exit_json(**result) - vmparams, count = setup_datadict(module) + vmparams, status = setup_datadict(module) # Get iRMC Virtual Media data status, vmdata, msg = irmc_redfish_get(module, "redfish/v1/Systems/0/Oem/ts_fujitsu/VirtualMedia/") diff --git a/library/irmc_task.py b/library/irmc_task.py index b6ea704..0086d0b 100644 --- a/library/irmc_task.py +++ b/library/irmc_task.py @@ -22,13 +22,13 @@ description: - Ansible module to handle iRMC tasks via Restful API. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - iRMC S4 needs FW >= 9.04, iRMC S5 needs FW >= 1.25. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" diff --git a/library/irmc_user.py b/library/irmc_user.py index 27c0cac..51031ba 100644 --- a/library/irmc_user.py +++ b/library/irmc_user.py @@ -22,12 +22,12 @@ description: - Ansible module to manage iRMC user accounts via iRMC remote scripting interface. - - Module Version V1.1. + - Module Version V1.2. requirements: - The module needs to run locally. - Python >= 2.6 - - Python module 'future' + - Python modules 'future', 'requests', 'urllib3' version_added: "2.4" @@ -498,11 +498,14 @@ ] +# Global +result = dict() + + def irmc_user(module): - result = dict( - changed=False, - status=0 - ) + # initialize result + result['changed'] = False + result['status'] = 0 if module.check_mode: result['msg'] = "module was not run" @@ -510,7 +513,47 @@ def irmc_user(module): userdata, setparam_count = setup_datadict(module) - # preliminary parameter checks + preliminary_parameter_check(module, setparam_count) + + # determine user ID (free or otherwise) + userdata['id'] = determine_userid(module) + + # set up command list + if module.params['command'] == "get": + body = setup_user_commandlist(userdata, "GET", param_scci_map, userdata['id']) + elif module.params['command'] == "change": + body = setup_user_commandlist(userdata, "SET", param_scci_map, userdata['id']) + elif module.params['command'] == "create": + userdata = set_default(userdata) + body = setup_user_commandlist(userdata, "CREATE", param_scci_map, userdata['id']) + elif module.params['command'] == "delete": + body = scci_body_start + body += add_scci_command("DELETE", param_scci_map, "ConfBMCAcctUserName", userdata['id'], "") + body += add_scci_command("DELETE", param_scci_map, "ConfBMCAcctUserDescription", userdata['id'], "") + body += scci_body_end + + # send command list to scripting interface + status, data, msg = irmc_scci_post(module, body) + if status < 100: + module.fail_json(msg=msg, status=status, exception=data) + elif status not in (200, 202, 204): + module.fail_json(msg=msg, status=status) + + # evaluate result list + userdata, scciresult, sccicontext = get_scciresultlist(data.content, userdata, param_scci_map) + if scciresult != 0: + module.fail_json(msg=sccicontext, status=scciresult) + + userdata['name'] = module.params['name'] + if module.params['command'] == "get": + result['user'] = setup_resultdata(userdata) + else: + result['changed'] = True + + module.exit_json(**result) + + +def preliminary_parameter_check(module, setparam_count): if module.params['command'] == "change": if setparam_count <= 1: result['msg'] = "Command 'change' requires at least one parameter to be changed!" @@ -526,9 +569,10 @@ def irmc_user(module): result['status'] = 12 module.fail_json(**result) - # determine user ID (free or otherwise) + +def determine_userid(module): usernumber = newuser = 0 - userdata['id'] = None + userid = None while True: body = scci_body_start + add_scci_command("GET", param_scci_map, "ConfBMCAcctUserName", usernumber, "") + \ scci_body_end @@ -538,28 +582,27 @@ def irmc_user(module): elif status not in (200, 202, 204): module.fail_json(msg=msg, status=status) - username, sccistatus, sccicontext = get_scciresult(data.content, 0x1451) + username, sccistatus, msg = get_scciresult(data.content, 0x1451) if (sccistatus != 0 or username == "" or username is None) and newuser == 0: newuser = usernumber if username == module.params['name']: - userdata['id'] = usernumber + userid = usernumber break usernumber += 1 if usernumber >= 15: break - # more parameter checks if module.params['command'] == "create": - if userdata['id'] is not None: + if userid is not None: result['skipped'] = True result['msg'] = "User '{0}' already exists.".format(module.params['name']) module.exit_json(**result) else: - userdata['id'] = newuser + userid = newuser else: - if userdata['id'] is None: + if userid is None: if module.params['command'] == "delete": result['skipped'] = True result['msg'] = "User '{0}' does not exist.".format(module.params['name']) @@ -569,39 +612,7 @@ def irmc_user(module): result['status'] = 20 module.fail_json(**result) - # set up command list - if module.params['command'] == "get": - body = setup_user_commandlist(userdata, "GET", param_scci_map, userdata['id']) - elif module.params['command'] == "change": - body = setup_user_commandlist(userdata, "SET", param_scci_map, userdata['id']) - elif module.params['command'] == "create": - userdata = set_default(userdata) - body = setup_user_commandlist(userdata, "CREATE", param_scci_map, userdata['id']) - elif module.params['command'] == "delete": - body = scci_body_start - body += add_scci_command("DELETE", param_scci_map, "ConfBMCAcctUserName", userdata['id'], "") - body += add_scci_command("DELETE", param_scci_map, "ConfBMCAcctUserDescription", userdata['id'], "") - body += scci_body_end - - # send command list to scripting interface - status, data, msg = irmc_scci_post(module, body) - if status < 100: - module.fail_json(msg=msg, status=status, exception=data) - elif status not in (200, 202, 204): - module.fail_json(msg=msg, status=status) - - # evaluate result list - userdata, scciresult, sccicontext = get_scciresultlist(data.content, userdata, param_scci_map) - if scciresult != 0: - module.fail_json(msg=sccicontext, status=scciresult) - - userdata['name'] = module.params['name'] - if module.params['command'] == "get": - result['user'] = setup_resultdata(userdata) - else: - result['changed'] = True - - module.exit_json(**result) + return userid def setup_user_commandlist(cmdlist, ctype, scci_map, user_id): @@ -627,7 +638,7 @@ def set_default(data): def setup_resultdata(data): - result = { + resultdata = { 'name': data['name'], 'id': data['id'], # 'password': data['password'], @@ -668,7 +679,7 @@ def setup_resultdata(data): 'alert_memory': alerts.get(data['alert_memory']), 'alert_others': alerts.get(data['alert_others']), } - return result + return resultdata def main(): diff --git a/module_utils/irmc.py b/module_utils/irmc.py index cbf4b54..6ac1775 100644 --- a/module_utils/irmc.py +++ b/module_utils/irmc.py @@ -1,26 +1,30 @@ -#!/usr/bin/python - # FUJITSU LIMITED # Copyright 2018 FUJITSU LIMITED # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division) __metaclass__ = type -from builtins import str - import time import traceback import json -import requests -from requests.auth import HTTPBasicAuth -from requests.adapters import HTTPAdapter -import urllib3 -from urllib3.util.retry import Retry -from urllib3.exceptions import InsecureRequestWarning -urllib3.disable_warnings(InsecureRequestWarning) + +try: + import requests + from requests.auth import HTTPBasicAuth + from requests.adapters import HTTPAdapter + import urllib3 + from urllib3.util.retry import Retry + from urllib3.exceptions import InsecureRequestWarning + urllib3.disable_warnings(InsecureRequestWarning) + HAS_REQUESTS = True +except: + HAS_REQUESTS = False def irmc_redfish_get(module, uri): + if not HAS_REQUESTS: + return 90, "Python 'requests' module not found.", "iRMC module requires 'requests' Module" + headers = { "Accept": "application/json", "Content-Type": "application/json" @@ -41,9 +45,10 @@ def irmc_redfish_get(module, uri): status = data.status_code if status != 200: try: - msg = "GET request was not successful ({0}): {1}".format(url, data.json()['error']['message']) + msg = "GET request was not successful ({0}): status {1}, '{2}'". \ + format(url, status, data.json()['error']['message']) except Exception: - msg = "GET request was not successful ({0}).".format(url) + msg = "GET request was not successful ({0}), status {1}.".format(url, status) except Exception as e: status = 99 @@ -54,6 +59,9 @@ def irmc_redfish_get(module, uri): def irmc_redfish_patch(module, uri, body, etag): + if not HAS_REQUESTS: + return 90, "Python 'requests' module not found.", "iRMC access requires 'requests' Module" + etag = str(etag) if not etag.isdigit(): msg = "etag is no number: {0}".format(etag) @@ -89,9 +97,10 @@ def irmc_redfish_patch(module, uri, body, etag): status = data.status_code if status != 200: try: - msg = "PATCH request was not successful ({0}): {1}".format(url, data.json()['error']['message']) + msg = "PATCH request was not successful ({0}): status {1}, '{2}'". \ + format(url, status, data.json()['error']['message']) except Exception: - msg = "PATCH request was not successful ({0}).".format(url) + msg = "PATCH request was not successful ({0}), status {1}.".format(url, status) except Exception as e: status = 99 @@ -102,6 +111,9 @@ def irmc_redfish_patch(module, uri, body, etag): def irmc_redfish_post(module, uri, body): + if not HAS_REQUESTS: + return 90, "Python 'requests' module not found.", "iRMC module requires 'requests' Module" + if body != "": try: json.loads(body) @@ -142,7 +154,54 @@ def irmc_redfish_post(module, uri, body): return status, data, msg +def irmc_redfish_put(module, uri, body): + if not HAS_REQUESTS: + return 90, "Python 'requests' module not found.", "iRMC module requires 'requests' Module" + + if body != "": + try: + json.loads(body) + except ValueError as e: + data = traceback.format_exc() + msg = "POST request got invalid JSON body: {0}".format(body) + return 98, data, msg + + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + url = "https://{0}/{1}".format(module.params['irmc_url'], uri) + + session = requests.Session() + retries = Retry(total=5, backoff_factor=0.1) + session.mount('http://', HTTPAdapter(max_retries=retries)) + session.mount('https://', HTTPAdapter(max_retries=retries)) + + msg = "OK" + try: + data = session.put(url, headers=headers, data=body, verify=module.params['validate_certs'], + auth=HTTPBasicAuth(module.params['irmc_username'], module.params['irmc_password'])) + data.connection.close() + + status = data.status_code + if status not in (200, 202, 204): + try: + msg = "PUT request was not successful ({0}): {1}".format(url, data.json()['error']['message']) + except Exception: + msg = "PUT request was not successful ({0}).".format(url) + + except Exception as e: + status = 99 + data = traceback.format_exc() + msg = "PUT request encountered exception ({0}): {1}".format(url, str(e)) + + return status, data, msg + + def irmc_redfish_delete(module, uri): + if not HAS_REQUESTS: + return 90, "Python 'requests' module not found.", "iRMC module requires 'requests' Module" + headers = { "Accept": "application/json", "Content-Type": "application/json", @@ -223,3 +282,18 @@ def waitForSessionToFinish(module, sessionId): status = 29 break return status, sdata, msg + + +def elcm_check_status(module): + status, data, msg = irmc_redfish_get(module, "rest/v1/Oem/eLCM/eLCMStatus") + if status < 100 or (status not in (200, 202, 204)): + return status, data, msg + + if get_irmc_json(data.json(), ["eLCMStatus", "EnabledAndLicenced"]) == "false": + msg = "eLCM functionality can onlybe used when iRMC is supplied with a valid eLCM license!" + status = 20 + if get_irmc_json(data.json(), ["eLCMStatus", "SDCardMounted"]) == "false": + msg = "eLCM requires iRMC to be supplied with a eLCM SD card!" + status = 21 + + return status, data, msg diff --git a/module_utils/irmc_scci_utils.py b/module_utils/irmc_scci_utils.py index 7db2653..0638f44 100644 --- a/module_utils/irmc_scci_utils.py +++ b/module_utils/irmc_scci_utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - # FUJITSU LIMITED # Copyright 2018 FUJITSU LIMITED # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -10,10 +8,15 @@ import traceback from xml.etree import cElementTree as ElementTree -import requests -from requests.auth import HTTPBasicAuth -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry + +try: + import requests + from requests.auth import HTTPBasicAuth + from requests.adapters import HTTPAdapter + from urllib3.util.retry import Retry + HAS_REQUESTS = True +except: + HAS_REQUESTS = False scci_body_start = """\n""" @@ -167,6 +170,9 @@ def get_scciresultlist(resultlist, sccidata, scci_map): def irmc_scci_post(module, body): ''' Post a SCCI command at iRMC config URI. ''' + if not HAS_REQUESTS: + return 90, "Python 'requests' module not found.", "iRMC module requires 'requests' Module" + try: ElementTree.fromstring(body) except Exception as e: @@ -204,6 +210,9 @@ def irmc_scci_post(module, body): def irmc_scci_update(module, update_url): + if not HAS_REQUESTS: + return 90, "Python 'requests' module not found.", "iRMC module requires 'requests' Module" + session = requests.Session() retries = Retry(total=5, backoff_factor=0.1) session.mount('http://', HTTPAdapter(max_retries=retries)) diff --git a/module_utils/irmc_upload_file.py b/module_utils/irmc_upload_file.py index 90b2962..d2345b7 100644 --- a/module_utils/irmc_upload_file.py +++ b/module_utils/irmc_upload_file.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - # FUJITSU LIMITED # Copyright 2018 FUJITSU LIMITED # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -10,17 +8,31 @@ import ntpath import traceback -import requests -from requests.auth import HTTPBasicAuth -from requests.adapters import HTTPAdapter -import urllib3 -from urllib3.util.retry import Retry -from urllib3.exceptions import InsecureRequestWarning -from requests_toolbelt import MultipartEncoder -urllib3.disable_warnings(InsecureRequestWarning) + +try: + import requests + from requests.auth import HTTPBasicAuth + from requests.adapters import HTTPAdapter + import urllib3 + from urllib3.util.retry import Retry + from urllib3.exceptions import InsecureRequestWarning + urllib3.disable_warnings(InsecureRequestWarning) + HAS_REQUESTS = True +except: + HAS_REQUESTS = False +try: + from requests_toolbelt import MultipartEncoder + HAS_REQUESTS_TOOLBELT = True +except: + HAS_REQUESTS_TOOLBELT = False def irmc_redfish_post_file(module, uri, filename): + if not HAS_REQUESTS: + return 90, "Python 'requests' module not found.", "iRMC module requires 'requests' module" + if not HAS_REQUESTS_TOOLBELT: + return 91, "Python 'requests_toolbelt' module not found.", "iRMC module requires 'requests_toolbelt' module" + try: filedata = open(filename, 'rb') except Exception as e: diff --git a/module_utils/irmc_utils.py b/module_utils/irmc_utils.py index 384155e..c0d6ca1 100644 --- a/module_utils/irmc_utils.py +++ b/module_utils/irmc_utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - # FUJITSU LIMITED # Copyright 2018 FUJITSU LIMITED # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) diff --git a/tests/test_irmc.py b/tests/test_irmc.py index 20e567d..656cd6c 100644 --- a/tests/test_irmc.py +++ b/tests/test_irmc.py @@ -70,7 +70,7 @@ def test__irmc_redfish_get__bad_status_without_reason(self, get): status, data, msg = irmc.irmc_redfish_get(self.mod, "redfish_path") self.assertEqual(self.mockdata.status_code, status) self.assertEqual(self.mockdata.json.return_value, data.json.return_value) - self.assertEqual("GET request was not successful (" + self.url + ").", msg) + self.assertEqual("GET request was not successful (" + self.url + "), status " + str(status) + ".", msg) @patch.object(requests.Session, 'get') def test__irmc_redfish_get__bad_status_with_reason(self, get): @@ -80,7 +80,8 @@ def test__irmc_redfish_get__bad_status_with_reason(self, get): status, data, msg = irmc.irmc_redfish_get(self.mod, "redfish_path") self.assertEqual(self.mockdata.status_code, status) self.assertEqual(self.mockdata.json.return_value, data.json.return_value) - self.assertEqual("GET request was not successful (" + self.url + "): " + self.mockdata.reason, msg) + self.assertEqual("GET request was not successful (" + self.url + "): status " + str(status) + \ + ", '" + self.mockdata.reason + "'", msg) @patch.object(requests.Session, 'get') def test__irmc_redfish_get__exception(self, get): @@ -132,7 +133,7 @@ def test__irmc_redfish_patch__bad_status_without_reason(self, patch): status, data, msg = irmc.irmc_redfish_patch(self.mod, "redfish_path", json.dumps({'Patch': 'mockpatch'}), 12345) self.assertEqual(self.mockdata.status_code, status) self.assertEqual(self.mockdata.json.return_value, data.json.return_value) - self.assertEqual("PATCH request was not successful (" + self.url + ").", msg) + self.assertEqual("PATCH request was not successful (" + self.url + "), status " + str(status) + ".", msg) @patch.object(requests.Session, 'patch') def test__irmc_redfish_patch__bad_status_with_reason(self, patch): @@ -142,7 +143,8 @@ def test__irmc_redfish_patch__bad_status_with_reason(self, patch): status, data, msg = irmc.irmc_redfish_patch(self.mod, "redfish_path", json.dumps({'Patch': 'mockpatch'}), 12345) self.assertEqual(self.mockdata.status_code, status) self.assertEqual(self.mockdata.json.return_value, data.json.return_value) - self.assertEqual("PATCH request was not successful (" + self.url + "): " + self.mockdata.reason, msg) + self.assertEqual("PATCH request was not successful (" + self.url + "): status " + str(status) + \ + ", '" + self.mockdata.reason + "'", msg) @patch.object(requests.Session, 'patch') def test__irmc_redfish_patch__bad_body(self, patch): @@ -242,7 +244,7 @@ def test__waitForSessionToFinish__session_error(self, get): requests.Session.get.return_value = self.mockdata self.mockdata.json.return_value = {'Session': {'Status': 'terminated with error'}} status, data, msg = irmc.waitForSessionToFinish(self.mod, 1) - self.assertEqual(self.mockdata.status_code, status) + self.assertEqual(29, status) self.assertEqual(self.mockdata.json.return_value, data) self.assertEqual("Session result: terminated with error", msg) diff --git a/tests/test_irmc_scci_utils.py b/tests/test_irmc_scci_utils.py index 1b2f4ff..197f64a 100644 --- a/tests/test_irmc_scci_utils.py +++ b/tests/test_irmc_scci_utils.py @@ -272,7 +272,7 @@ def test__get_scciresultlist__bad_scci_status_1455_1457(self): sccidata, scciresult, sccicontext = \ irmc_scci_utils.get_scciresultlist(sccireturndata, self.userdata, self.param_scci_map) self.assertEqual(status * 2, scciresult) - self.assertIn("OpCodeExt 0x{0}: {1} ({2})\n".format(format(opcode1, 'X'), datastr, status) + + self.assertIn("OpCodeExt 0x{0}: {1} ({2})\n".format(format(opcode1, 'X'), datastr, status) + \ "OpCodeExt 0x{0}: {1} ({2})".format(format(opcode2, 'X'), dataint, status), sccicontext) self.assertEqual(datastr, sccidata['description']) self.assertEqual(dataint, sccidata['enabled'])