diff --git a/docs/source/bibliography.rst b/docs/source/bibliography.rst index 465b036c..590900c7 100644 --- a/docs/source/bibliography.rst +++ b/docs/source/bibliography.rst @@ -48,3 +48,6 @@ Resources HMC API 2.15.0 `IBM SC27-2638, IBM Z Hardware Management Console Web Services API (Version 2.15.0) `_ (covers both GA1 and GA2) + + Capacity on Demand User's Guide + `IBM SC28-6985, IBM Z Capacity on Demand User's Guide `_ diff --git a/docs/source/modules.rst b/docs/source/modules.rst index 69688326..fad3fd73 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -57,6 +57,7 @@ Modules supported with CPCs in any operational mode: modules/zhmc_cpc modules/zhmc_cpc_list + modules/zhmc_cpc_capacity Modules supported only with CPCs in DPM operational mode: diff --git a/docs/source/modules/zhmc_cpc_capacity.rst b/docs/source/modules/zhmc_cpc_capacity.rst new file mode 100644 index 00000000..3c02edd9 --- /dev/null +++ b/docs/source/modules/zhmc_cpc_capacity.rst @@ -0,0 +1,429 @@ + +:github_url: https://github.com/ansible-collections/ibm_zos_core/blob/dev/plugins/modules/zhmc_cpc_capacity.py + +.. _zhmc_cpc_capacity_module: + + +zhmc_cpc_capacity -- Manage temporary processor capacity +======================================================== + + + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Gather facts about the processor capacity of a CPC (Z system). +- Update the processor capacity of a CPC (Z system) via adding or removing temporary capacity (On/Off CoD). +- For details on processor capacity on demand, see the :term:\`Capacity on Demand User's Guide\`. + + +Requirements +------------ + +- The HMC userid must have these task permissions: 'Perform Model Conversion'. +- The HMC userid must have object-access permissions to these objects: Target CPCs. +- The CPC must be enabled for On-Off Capacity-On-Demand. + + + + +Parameters +---------- + + +hmc_host + The hostnames or IP addresses of a single HMC or of a list of redundant HMCs. A single HMC can be specified as a string type or as an HMC list with one item. An HMC list can be specified as a list type or as a string type containing a Python list representation. + + The first available HMC of a list of redundant HMCs is used for the entire execution of the module. + + | **required**: True + | **type**: raw + + +hmc_auth + The authentication credentials for the HMC. + + | **required**: True + | **type**: dict + + + userid + The userid (username) for authenticating with the HMC. This is mutually exclusive with providing \ :literal:`session\_id`\ . + + | **required**: False + | **type**: str + + + password + The password for authenticating with the HMC. This is mutually exclusive with providing \ :literal:`session\_id`\ . + + | **required**: False + | **type**: str + + + session_id + HMC session ID to be used. This is mutually exclusive with providing \ :literal:`userid`\ and \ :literal:`password`\ and can be created as described in :ref:\`zhmc\_session\_module\`. + + | **required**: False + | **type**: str + + + ca_certs + Path name of certificate file or certificate directory to be used for verifying the HMC certificate. If null (default), the path name in the 'REQUESTS\_CA\_BUNDLE' environment variable or the path name in the 'CURL\_CA\_BUNDLE' environment variable is used, or if neither of these variables is set, the certificates in the Mozilla CA Certificate List provided by the 'certifi' Python package are used for verifying the HMC certificate. + + | **required**: False + | **type**: str + + + verify + If True (default), verify the HMC certificate as specified in the \ :literal:`ca\_certs`\ parameter. If False, ignore what is specified in the \ :literal:`ca\_certs`\ parameter and do not verify the HMC certificate. + + | **required**: False + | **type**: bool + | **default**: True + + + +name + The name of the target CPC. + + | **required**: True + | **type**: str + + +state + The desired state for the operation: + + \* \ :literal:`set`\ : Ensures that the CPC has the specified specialty processor capacity and the specified software model, and returns the resulting processor capacity of the CPC. + + \* \ :literal:`facts`\ : Does not change anything on the CPC and returns the current processor capacity of the CPC. + + | **required**: True + | **type**: str + | **choices**: set, facts + + +record_id + The ID of the capacity record to be used for any updates of the processor capacity. + + Required for state \ :literal:`set`\ . + + | **required**: False + | **type**: str + + +software_model + The target software model to be active. This value must be one of the software models defined within the specified capacity record. The software model implies the number of general purpose processors that will be active. + + If null or not provided, the software model and the number of general purpose processors of the CPC will remain unchanged. + + | **required**: False + | **type**: str + + +software_model_direction + Indicates the direction of the capacity change for general purpose processors in \ :literal:`software\_model`\ , relative to the current software model: + + \* \ :literal:`increase`\ : The specified software model defines more general purpose processors than the current software model. + + \* \ :literal:`decrease`\ : The specified software model defines less general purpose processors than the current software model. + + Ignored when \ :literal:`software\_model`\ is null, not provided, or specifies the current software model. Otherwise required. + + | **required**: False + | **type**: str + | **choices**: increase, decrease + + +specialty_processors + The target number of specialty processors to be active. Processor types not provided will not be changed. Target numbers of general purpose processors can be set via the \ :literal:`software\_model`\ parameter. + + Each item in the dictionary identifies the target number of processors of one type of specialty processor. The key identifies the type of specialty processor ('aap', 'icf', 'ifl', 'iip', 'sap'), and the value is the target number of processors of that type. Note that the target number is the number of permanently activated processors plus the number of temporarily activated processors. + + The target number for each processor type may be larger, equal or lower than the current number, but it must not be lower than the number of permanent processors of that type. + + If the target number of processors is not installed in the CPC, the \ :literal:`force`\ parameter controls what happens. + + If null, empty or not provided, the specialty processor capacity will remain unchanged. + + | **required**: False + | **type**: dict + + +test_activation + Indicates that test resources instead of real resources from the capacity record should be activated. Test resources are automatically deactivated after 24h. This is mainly used for Capacity Backup Upgrade (CBU) test activations. For details, see the :term:\`Capacity on Demand User's Guide\`. + + | **required**: False + | **type**: bool + + +force + Indicates that an increase of capacity should be performed even if the necessary processors are not currently installed in the CPC. + + | **required**: False + | **type**: bool + + +log_file + File path of a log file to which the logic flow of this module as well as interactions with the HMC are logged. If null, logging will be propagated to the Python root logger. + + | **required**: False + | **type**: str + + + + +Examples +-------- + +.. code-block:: yaml+jinja + + + --- + # Note: The following examples assume that some variables named 'my_*' are set. + + - name: Gather facts about the CPC processor capacity + zhmc_cpc_capacity: + hmc_host: "{{ my_hmc_host }}" + hmc_auth: "{{ my_hmc_auth }}" + name: "{{ my_cpc_name }}" + state: facts + register: cap1 + + - name: Ensure the CPC has a certain general purpose processor capacity active + zhmc_cpc_capacity: + hmc_host: "{{ my_hmc_host }}" + hmc_auth: "{{ my_hmc_auth }}" + name: "{{ my_cpc_name }}" + state: set + record_id: R1234 + software_model: "710" + register: cap1 + + - name: Ensure the CPC has a certain IFL processor capacity active + zhmc_cpc_capacity: + hmc_host: "{{ my_hmc_host }}" + hmc_auth: "{{ my_hmc_auth }}" + name: "{{ my_cpc_name }}" + state: set + record_id: R1234 + specialty_processors: + ifl: 20 + register: cap1 + + + + + + + + + + +Return Values +------------- + + +changed + Indicates if any change has been made by the module. For \ :literal:`state=facts`\ , always will be false. + + | **returned**: always + | **type**: bool + +msg + An error message that describes the failure. + + | **returned**: failure + | **type**: str + +cpc + A dictionary with capacity related properties of the CPC. + + | **returned**: success + | **type**: dict + + name + CPC name + + | **type**: str + + has_temporary_capacity_change_allowed + Boolean indicating whether API applications are allowed to make changes to temporary capacity. + + | **type**: bool + + is_on_off_cod_enabled + Boolean indicating whether the On/Off Capacity on Demand feature is enabled for the CPC. + + | **type**: bool + + is_on_off_cod_installed + Boolean indicating whether an On/Off Capacity on Demand record is installed on the CPC. + + | **type**: bool + + is_on_off_cod_activated + Boolean indicating whether an On/Off Capacity on Demand record is installed and active on the CPC. + + | **type**: bool + + on_off_cod_activation_date + Timestamp when the On/Off Capacity on Demand record was activated on the CPC. + + | **type**: int + + software_model_purchased + The software model based on the purchased processors. + + | **type**: str + + software_model_permanent + The software model based on the permanent processors. + + | **type**: str + + software_model_permanent_plus_billable + The software model based on the permanent plus billable temporary processors. + + | **type**: str + + software_model_permanent_plus_temporary + The software model based on the permanent plus all temporary processors. + + | **type**: str + + msu_purchased + The MSU value associated with the software model based on the purchased processors. + + | **type**: int + + msu_permanent + The MSU value associated with the software model based on the permanent processors. + + | **type**: int + + msu_permanent_plus_billable + The MSU value associated with the software model based on the permanent plus billable temporary processors. + + | **type**: int + + msu_permanent_plus_temporary + The MSU value associated with the software model based on the permanent plus all temporary processors. + + | **type**: int + + processor_count_general_purpose + The count of active general purpose processors. + + | **type**: int + + processor_count_ifl + The count of active Integrated Facility for Linux (IFL) processors. + + | **type**: int + + processor_count_icf + The count of active Internal Coupling Facility (ICF) processors. + + | **type**: int + + processor_count_aap + The count of active IBM zEnterprise Application Assist Processor (zAAP) processors. + + | **type**: int + + processor_count_iip + The count of active IBM z Integrated Information Processor (zIIP) processors. + + | **type**: int + + processor_count_service_assist + The count of active service assist processors. + + | **type**: int + + processor_count_spare + The count of spare processors, across all processor types. + + | **type**: int + + processor_count_defective + The count of defective processors, across all processor types. + + | **type**: int + + processor_count_pending_general_purpose + The number of general purpose processors that will become active, when more processors are made available by adding new hardware or by deactivating capacity records. + + | **type**: int + + processor_count_pending_ifl + The number of Integrated Facility for Linux processors that will become active, when more processors are made available by adding new hardware or by deactivating capacity records. + + | **type**: int + + processor_count_pending_icf + The number of Integrated Coupling Facility processors that will become active, when more processors are made available by adding new hardware or by deactivating capacity records. + + | **type**: int + + processor_count_pending_aap + The number of Application Assist processors that will become active, when more processors are made available by adding new hardware or by deactivating capacity records. + + | **type**: int + + processor_count_pending_iip + The number of z Integrated Information Processors that will become active, when more processors are made available by adding new hardware or by deactivating capacity records. + + | **type**: int + + processor_count_pending_service_assist + The number of service assist processors that will become active, when more processors are made available by adding new hardware or by deactivating capacity records. + + | **type**: int + + processor_count_permanent_ifl + The number of Integrated Facility for Linux processors that are permanent. + + | **type**: int + + processor_count_permanent_icf + The number of Integrated Coupling Facility processors that are permanent. + + | **type**: int + + processor_count_permanent_iip + The number of z Integrated Information Processors that are permanent. + + | **type**: int + + processor_count_permanent_service_assist + The number of service assist processors that are permanent. + + | **type**: int + + processor_count_unassigned_ifl + The number of Integrated Facility for Linux processors that are unassigned. + + | **type**: int + + processor_count_unassigned_icf + The number of Integrated Coupling Facility processors that are unassigned. + + | **type**: int + + processor_count_unassigned_iip + The number of z Integrated Information Processors that are unassigned. + + | **type**: int + + processor_count_unassigned_service_assist + The number of service assist processors that are unassigned. + + | **type**: int + + diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst index 2cff96cd..c8323213 100644 --- a/docs/source/release_notes.rst +++ b/docs/source/release_notes.rst @@ -48,6 +48,9 @@ Availability: `AutomationHub`_, `Galaxy`_, `GitHub`_ **Enhancements:** +* Added a new Ansible module 'zhmc_cpc_capacity' for managing the temporary + processor capacity of a CPC. (issue #243) + **Cleanup:** * Increased versions of GitHub Actions plugins to increase node.js runtime diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index 473bce30..c7b4d4ee 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -1102,6 +1102,41 @@ def process_normal_property( return create_props, update_props, deactivate +def ensure_hyphens(props): + """ + Return a dict that is a deep copy of the input dict, with underscores in + key (property) names replaced by hyphens. + + This is used when property names specified in Ansible that have underscores + in the name need to be converted to property names for HMC operations, + where they need to have hyphens in the name. + """ + hyphen_props = dict() + for pname, pvalue in props.items(): + hpname = pname.replace('_', '-') + if isinstance(pvalue, dict): + pvalue = ensure_hyphens(pvalue) + hyphen_props[hpname] = pvalue + return hyphen_props + + +def ensure_underscores(props): + """ + Return a dict that is a deep copy of the input dict, with hyphens in + key (property) names replaced by underscores. + + This is used when property names returned from the HMC need to be shown + in an Ansible module result with underscores. + """ + underscore_props = dict() + for pname, pvalue in props.items(): + hpname = pname.replace('-', '_') + if isinstance(pvalue, dict): + pvalue = ensure_underscores(pvalue) + underscore_props[hpname] = pvalue + return underscore_props + + def log_init(logger_name, log_file=None): """ Set up logging for the loggers of the current Ansible module, and for the diff --git a/plugins/modules/zhmc_cpc_capacity.py b/plugins/modules/zhmc_cpc_capacity.py new file mode 100644 index 00000000..c2d0026d --- /dev/null +++ b/plugins/modules/zhmc_cpc_capacity.py @@ -0,0 +1,859 @@ +#!/usr/bin/python +# Copyright 2020 IBM Corp. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# For information on the format of the ANSIBLE_METADATA, DOCUMENTATION, +# EXAMPLES, and RETURN strings, see +# http://docs.ansible.com/ansible/dev_guide/developing_modules_documenting.html + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['stableinterface'], + 'supported_by': 'community', + 'shipped_by': 'other', + 'other_repo_url': 'https://github.com/zhmcclient/zhmc-ansible-modules' +} + +DOCUMENTATION = """ +--- +module: zhmc_cpc_capacity +version_added: "2.9.0" +short_description: Manage temporary processor capacity +description: + - Gather facts about the processor capacity of a CPC (Z system). + - Update the processor capacity of a CPC (Z system) via adding or removing + temporary capacity (On/Off CoD). + - For details on processor capacity on demand, see the + :term:`Capacity on Demand User's Guide`. +author: + - Andreas Maier (@andy-maier) +requirements: + - "The HMC userid must have these task permissions: + 'Perform Model Conversion'." + - "The HMC userid must have object-access permissions to these objects: + Target CPCs." + - "The CPC must be enabled for On-Off Capacity-On-Demand." +options: + hmc_host: + description: + - The hostnames or IP addresses of a single HMC or of a list of redundant + HMCs. A single HMC can be specified as a string type or as an HMC list + with one item. An HMC list can be specified as a list type or as a + string type containing a Python list representation. + - The first available HMC of a list of redundant HMCs is used for the + entire execution of the module. + type: raw + required: true + hmc_auth: + description: + - The authentication credentials for the HMC. + type: dict + required: true + suboptions: + userid: + description: + - The userid (username) for authenticating with the HMC. + This is mutually exclusive with providing C(session_id). + type: str + required: false + default: null + password: + description: + - The password for authenticating with the HMC. + This is mutually exclusive with providing C(session_id). + type: str + required: false + default: null + session_id: + description: + - HMC session ID to be used. + This is mutually exclusive with providing C(userid) and C(password) + and can be created as described in :ref:`zhmc_session_module`. + type: str + required: false + default: null + ca_certs: + description: + - Path name of certificate file or certificate directory to be used + for verifying the HMC certificate. If null (default), the path name + in the 'REQUESTS_CA_BUNDLE' environment variable or the path name + in the 'CURL_CA_BUNDLE' environment variable is used, or if neither + of these variables is set, the certificates in the Mozilla CA + Certificate List provided by the 'certifi' Python package are used + for verifying the HMC certificate. + type: str + required: false + default: null + verify: + description: + - If True (default), verify the HMC certificate as specified in the + C(ca_certs) parameter. If False, ignore what is specified in the + C(ca_certs) parameter and do not verify the HMC certificate. + type: bool + required: false + default: true + name: + description: + - The name of the target CPC. + type: str + required: true + state: + description: + - "The desired state for the operation:" + - "* C(set): Ensures that the CPC has the specified specialty processor + capacity and the specified software model, and returns the resulting + processor capacity of the CPC." + - "* C(facts): Does not change anything on the CPC and returns the + current processor capacity of the CPC." + type: str + required: true + choices: ['set', 'facts'] + record_id: + description: + - "The ID of the capacity record to be used for any updates of the + processor capacity." + - "Required for state C(set)." + type: str + required: false + default: null + software_model: + description: + - "The target software model to be active. This value must be one of + the software models defined within the specified capacity record. + The software model implies the number of general purpose processors + that will be active." + - "If null or not provided, the software model and the number of + general purpose processors of the CPC will remain unchanged." + type: str + required: false + default: null + software_model_direction: + description: + - "Indicates the direction of the capacity change for general purpose + processors in C(software_model), relative to the current software + model:" + - "* C(increase): The specified software model defines more general + purpose processors than the current software model." + - "* C(decrease): The specified software model defines less general + purpose processors than the current software model." + - "Ignored when C(software_model) is null, not provided, or specifies + the current software model. Otherwise required." + type: str + choices: ['increase', 'decrease'] + required: false + default: null + specialty_processors: + description: + - "The target number of specialty processors to be active. Processor + types not provided will not be changed. Target numbers of general + purpose processors can be set via the C(software_model) parameter." + - "Each item in the dictionary identifies the target number of processors + of one type of specialty processor. The key identifies the type + of specialty processor ('aap', 'icf', 'ifl', 'iip', 'sap'), + and the value is the target number of processors of that type. Note + that the target number is the number of permanently activated + processors plus the number of temporarily activated processors." + - "The target number for each processor type may be larger, equal or + lower than the current number, but it must not be lower than the + number of permanent processors of that type." + - "If the target number of processors is not installed in the CPC, + the C(force) parameter controls what happens." + - "If null, empty or not provided, the specialty processor capacity will + remain unchanged." + type: dict + required: false + default: null + test_activation: + description: + - "Indicates that test resources instead of real resources from the + capacity record should be activated. Test resources are automatically + deactivated after 24h. This is mainly used for Capacity Backup Upgrade + (CBU) test activations. For details, see the + :term:`Capacity on Demand User's Guide`." + type: bool + required: false + default: false + force: + description: + - "Indicates that an increase of capacity should be performed even if the + necessary processors are not currently installed in the CPC." + type: bool + required: false + default: false + log_file: + description: + - "File path of a log file to which the logic flow of this module as well + as interactions with the HMC are logged. If null, logging will be + propagated to the Python root logger." + type: str + required: false + default: null + _faked_session: + description: + - "An internal parameter used for testing the module." + required: false + type: raw + default: null +""" + +EXAMPLES = """ +--- +# Note: The following examples assume that some variables named 'my_*' are set. + +- name: Gather facts about the CPC processor capacity + zhmc_cpc_capacity: + hmc_host: "{{ my_hmc_host }}" + hmc_auth: "{{ my_hmc_auth }}" + name: "{{ my_cpc_name }}" + state: facts + register: cap1 + +- name: Ensure the CPC has a certain general purpose processor capacity active + zhmc_cpc_capacity: + hmc_host: "{{ my_hmc_host }}" + hmc_auth: "{{ my_hmc_auth }}" + name: "{{ my_cpc_name }}" + state: set + record_id: R1234 + software_model: "710" + register: cap1 + +- name: Ensure the CPC has a certain IFL processor capacity active + zhmc_cpc_capacity: + hmc_host: "{{ my_hmc_host }}" + hmc_auth: "{{ my_hmc_auth }}" + name: "{{ my_cpc_name }}" + state: set + record_id: R1234 + specialty_processors: + ifl: 20 + register: cap1 +""" + +RETURN = """ +changed: + description: Indicates if any change has been made by the module. + For C(state=facts), always will be false. + returned: always + type: bool +msg: + description: An error message that describes the failure. + returned: failure + type: str +cpc: + description: "A dictionary with capacity related properties of the CPC." + returned: success + type: dict + contains: + name: + description: "CPC name" + type: str + has_temporary_capacity_change_allowed: + description: + - "Boolean indicating whether API applications are allowed to make + changes to temporary capacity." + type: bool + is_on_off_cod_enabled: + description: + - "Boolean indicating whether the On/Off Capacity on Demand feature + is enabled for the CPC." + type: bool + is_on_off_cod_installed: + description: + - "Boolean indicating whether an On/Off Capacity on Demand record is + installed on the CPC." + type: bool + is_on_off_cod_activated: + description: + - "Boolean indicating whether an On/Off Capacity on Demand record is + installed and active on the CPC." + type: bool + on_off_cod_activation_date: + description: + - "Timestamp when the On/Off Capacity on Demand record was activated + on the CPC." + type: int + + software_model_purchased: + description: + - "The software model based on the purchased processors." + type: str + software_model_permanent: + description: + - "The software model based on the permanent processors." + type: str + software_model_permanent_plus_billable: + description: + - "The software model based on the permanent plus billable temporary + processors." + type: str + software_model_permanent_plus_temporary: + description: + - "The software model based on the permanent plus all temporary + processors." + type: str + + msu_purchased: + description: + - "The MSU value associated with the software model based on the + purchased processors." + type: int + msu_permanent: + description: + - "The MSU value associated with the software model based on the + permanent processors." + type: int + msu_permanent_plus_billable: + description: + - "The MSU value associated with the software model based on the + permanent plus billable temporary processors." + type: int + msu_permanent_plus_temporary: + description: + - "The MSU value associated with the software model based on the + permanent plus all temporary processors." + type: int + + processor_count_general_purpose: + description: + - "The count of active general purpose processors." + type: int + processor_count_ifl: + description: + - "The count of active Integrated Facility for Linux (IFL) processors." + type: int + processor_count_icf: + description: + - "The count of active Internal Coupling Facility (ICF) processors." + type: int + processor_count_aap: + description: + - "The count of active IBM zEnterprise Application Assist Processor + (zAAP) processors." + type: int + processor_count_iip: + description: + - "The count of active IBM z Integrated Information Processor (zIIP) + processors." + type: int + processor_count_service_assist: + description: + - "The count of active service assist processors." + type: int + processor_count_spare: + description: + - "The count of spare processors, across all processor types." + type: int + processor_count_defective: + description: + - "The count of defective processors, across all processor types." + type: int + + processor_count_pending_general_purpose: + description: + - "The number of general purpose processors that will become active, + when more processors are made available by adding new hardware or by + deactivating capacity records." + type: int + processor_count_pending_ifl: + description: + - "The number of Integrated Facility for Linux processors that will + become active, when more processors are made available by adding new + hardware or by deactivating capacity records." + type: int + processor_count_pending_icf: + description: + - "The number of Integrated Coupling Facility processors that will + become active, when more processors are made available by adding new + hardware or by deactivating capacity records." + type: int + processor_count_pending_aap: + description: + - "The number of Application Assist processors that will become active, + when more processors are made available by adding new hardware or by + deactivating capacity records." + type: int + processor_count_pending_iip: + description: + - "The number of z Integrated Information Processors that will become + active, when more processors are made available by adding new + hardware or by deactivating capacity records." + type: int + processor_count_pending_service_assist: + description: + - "The number of service assist processors that will become active, + when more processors are made available by adding new hardware or by + deactivating capacity records." + type: int + + processor_count_permanent_ifl: + description: + - "The number of Integrated Facility for Linux processors that are + permanent." + type: int + processor_count_permanent_icf: + description: + - "The number of Integrated Coupling Facility processors that are + permanent." + type: int + processor_count_permanent_iip: + description: + - "The number of z Integrated Information Processors that are + permanent." + type: int + processor_count_permanent_service_assist: + description: + - "The number of service assist processors that are permanent." + type: int + + processor_count_unassigned_ifl: + description: + - "The number of Integrated Facility for Linux processors that are + unassigned." + type: int + processor_count_unassigned_icf: + description: + - "The number of Integrated Coupling Facility processors that are + unassigned." + type: int + processor_count_unassigned_iip: + description: + - "The number of z Integrated Information Processors that are + unassigned." + type: int + processor_count_unassigned_service_assist: + description: + - "The number of service assist processors that are unassigned." + type: int +""" + +import logging # noqa: E402 +import traceback # noqa: E402 +from ansible.module_utils.basic import AnsibleModule # noqa: E402 + +from ..module_utils.common import log_init, open_session, close_session, \ + hmc_auth_parameter, Error, missing_required_lib, \ + common_fail_on_import_errors, parse_hmc_host, \ + ensure_underscores # noqa: E402 + +try: + import requests.packages.urllib3 + IMP_URLLIB3_ERR = None +except ImportError: + IMP_URLLIB3_ERR = traceback.format_exc() + +try: + import zhmcclient + IMP_ZHMCCLIENT_ERR = None +except ImportError: + IMP_ZHMCCLIENT_ERR = traceback.format_exc() + +# Python logger name for this module +LOGGER_NAME = 'zhmc_cpc_capacity' + +LOGGER = logging.getLogger(LOGGER_NAME) + + +# Capacity-related CPC properties for result, in HMC notation (hyphens) +# The display order is automatically sorted by Ansible. +CPC_CAPACITY_PROPERTIES = [ + 'name', + 'has-temporary-capacity-change-allowed', + 'is-on-off-cod-enabled', + 'is-on-off-cod-installed', + 'is-on-off-cod-activated', + 'on-off-cod-activation-date', + 'software-model-purchased', + 'software-model-permanent', + 'software-model-permanent-plus-billable', + 'software-model-permanent-plus-temporary', + 'msu-purchased', + 'msu-permanent', + 'msu-permanent-plus-billable', + 'msu-permanent-plus-temporary', + 'processor-count-general-purpose', + 'processor-count-ifl', + 'processor-count-icf', + 'processor-count-aap', + 'processor-count-iip', + 'processor-count-service-assist', + 'processor-count-spare', + 'processor-count-defective', + 'processor-count-pending-general-purpose', + 'processor-count-pending-ifl', + 'processor-count-pending-icf', + 'processor-count-pending-aap', + 'processor-count-pending-iip', + 'processor-count-pending-service-assist', + 'processor-count-permanent-ifl', + 'processor-count-permanent-icf', + 'processor-count-permanent-iip', + 'processor-count-permanent-service-assist', + 'processor-count-unassigned-ifl', + 'processor-count-unassigned-icf', + 'processor-count-unassigned-iip', + 'processor-count-unassigned-service-assist', +] + + +def split_target_processors(target_processors, current_processors): + """ + Split the target processor dict into processors to add and processors to + remove, relative to the current processors. + + I all parameters and return values, dict key is the processor type and dict + value is the number of processors. + + Parameters: + target_processor_dict(dict): Dict with target numbers of the specialty + processors. + current_processors(dict): Dict with current numbers of the specialty + processors. + + Returns: + tuple(add_info, remove_info): Tuple of dict with processor numbers to + add and dict with processor numbers to remove. + """ + add_processors = {} + remove_processors = {} + for proc_key, target_number in target_processors.items(): + current_number = current_processors[proc_key] + if target_number > current_number: + add_processors[proc_key] = target_number - current_number + elif target_number < current_number: + remove_processors[proc_key] = current_number - target_number + return add_processors, remove_processors + + +def get_current_processor_dict(cpc): + """ + Get the current speciality processors of the CPC as a processor dict. + """ + processor_dict = { + 'ifl': cpc.get_property('processor-count-ifl'), + 'icf': cpc.get_property('processor-count-icf'), + 'aap': cpc.get_property('processor-count-aap'), + 'iip': cpc.get_property('processor-count-iip'), + 'sap': cpc.get_property('processor-count-service-assist'), + } + return processor_dict + + +def cpc_result_properties(cpc): + """ + Convert CPC properties (with hyphens) into result properties + (with underscores) and reduce to exact set defined. + """ + result_props = {} + for hmc_name in CPC_CAPACITY_PROPERTIES: + value = cpc.get_property(hmc_name) + result_props[hmc_name] = value + return ensure_underscores(result_props) + + +def add_temporary_capacity_check_mode( + cpc, record_id, software_model, processor_info): + """ + Simulate the addition of capacity in check mode, by updating the CPC + properties locally. + """ + update_properties = {} + + if software_model: + current_model = cpc.get_property( + 'software-model-permanent-plus-temporary') + if software_model != current_model: + update_properties['software-model-permanent-plus-temporary'] = \ + software_model + cp_delta = 1 # TODO: Can we find out from the software model? + update_properties['processor-count-general-purpose'] = \ + cpc.get_property('processor-count-general-purpose') + cp_delta + + if processor_info: + for proc_key, delta in processor_info.items(): + if proc_key == 'ifl': + update_properties['processor-count-ifl'] = \ + cpc.get_property('processor-count-ifl') + delta + elif proc_key == 'icf': + update_properties['processor-count-icf'] = \ + cpc.get_property('processor-count-icf') + delta + elif proc_key == 'aap': + update_properties['processor-count-aap'] = \ + cpc.get_property('processor-count-aap') + delta + elif proc_key == 'iip': + update_properties['processor-count-iip'] = \ + cpc.get_property('processor-count-iip') + delta + elif proc_key == 'sap': + update_properties['processor-count-service-assist'] = \ + cpc.get_property('processor-count-service-assist') + delta + + if update_properties: + cpc.update_properties_local(update_properties) + + +def remove_temporary_capacity_check_mode( + cpc, record_id, software_model, processor_info): + """ + Simulate the removal of capacity in check mode, by updating the CPC + properties locally. + """ + update_properties = {} + + if software_model: + current_model = cpc.get_property( + 'software-model-permanent-plus-temporary') + if software_model != current_model: + update_properties['software-model-permanent-plus-temporary'] = \ + software_model + cp_delta = 1 # TODO: Can we find out from the software model? + update_properties['processor-count-general-purpose'] = \ + cpc.get_property('processor-count-general-purpose') - cp_delta + + if processor_info: + for proc_key, delta in processor_info.items(): + if proc_key == 'ifl': + update_properties['processor-count-ifl'] = \ + cpc.get_property('processor-count-ifl') - delta + elif proc_key == 'icf': + update_properties['processor-count-icf'] = \ + cpc.get_property('processor-count-icf') - delta + elif proc_key == 'aap': + update_properties['processor-count-aap'] = \ + cpc.get_property('processor-count-aap') - delta + elif proc_key == 'iip': + update_properties['processor-count-iip'] = \ + cpc.get_property('processor-count-iip') - delta + elif proc_key == 'sap': + update_properties['processor-count-service-assist'] = \ + cpc.get_property('processor-count-service-assist') - delta + + if update_properties: + cpc.update_properties_local(update_properties) + + +def ensure_set(module): + """ + Identify the target CPC and ensure that the specified capacity is set on + the target CPC. + + Raises: + ParameterError: An issue with the module parameters. + Error: Other errors during processing. + zhmcclient.Error: Any zhmcclient exception can happen. + """ + + module.fail_on_missing_params(['record_id']) + cpc_name = module.params['name'] + record_id = module.params['record_id'] + software_model = module.params['software_model'] + software_model_direction = module.params['software_model_direction'] + specialty_processors = module.params['specialty_processors'] + if specialty_processors is None: + specialty_processors = {} + test_activation = module.params['test_activation'] + force = module.params['force'] + + changed = False + need_pull = False + + session, logoff = open_session(module.params) + try: + client = zhmcclient.Client(session) + cpc = client.cpcs.find(name=cpc_name) + # The default exception handling is sufficient for the above. + + cpc.pull_properties(CPC_CAPACITY_PROPERTIES) + + add_software_model = None + remove_software_model = None + if software_model: + current_model = cpc.get_property( + 'software-model-permanent-plus-temporary') + if software_model != current_model: + module.fail_on_missing_params(['software_model_direction']) + if software_model_direction == 'increase': + add_software_model = software_model + elif software_model_direction == 'decrease': + remove_software_model = software_model + + add_processors = None + remove_processors = None + if specialty_processors: + current_processors = get_current_processor_dict(cpc) + add_processors, remove_processors = split_target_processors( + specialty_processors, current_processors) + + if add_processors or add_software_model: + LOGGER.debug( + "Adding temporary capacity to cpc %r: software_model=%r, " + "processors=%r", + cpc_name, add_software_model, add_processors) + if not module.check_mode: + cpc.add_temporary_capacity( + record_id, software_model=add_software_model, + processor_info=add_processors, + test=test_activation, + force=force) + need_pull = True + else: + add_temporary_capacity_check_mode( + cpc, record_id, software_model=add_software_model, + processor_info=add_processors) + changed = True + + if remove_processors or remove_software_model: + LOGGER.debug( + "Removing temporary capacity to cpc %r: software_model=%r, " + "processor_info=%r", + cpc_name, remove_software_model, remove_processors) + if not module.check_mode: + cpc.remove_temporary_capacity( + record_id, software_model=remove_software_model, + processor_info=remove_processors, + test=test_activation, + force=force) + need_pull = True + else: + remove_temporary_capacity_check_mode( + cpc, record_id, software_model=remove_software_model, + processor_info=remove_processors) + changed = True + + if need_pull: + cpc.pull_properties(CPC_CAPACITY_PROPERTIES) + + result = cpc_result_properties(cpc) + return changed, result + + finally: + close_session(session, logoff) + + +def facts(module): + """ + Identify the target CPC and return facts about the target CPC and its + child resources. + + Raises: + ParameterError: An issue with the module parameters. + zhmcclient.Error: Any zhmcclient exception can happen. + """ + + cpc_name = module.params['name'] + + session, logoff = open_session(module.params) + try: + client = zhmcclient.Client(session) + cpc = client.cpcs.find(name=cpc_name) + # The default exception handling is sufficient for the above. + + cpc.pull_properties(CPC_CAPACITY_PROPERTIES) + + result = cpc_result_properties(cpc) + return False, result + + finally: + close_session(session, logoff) + + +def perform_task(module): + """ + Perform the task for this module, dependent on the 'state' module + parameter. + + If check_mode is True, check whether changes would occur, but don't + actually perform any changes. + + Raises: + ParameterError: An issue with the module parameters. + zhmcclient.Error: Any zhmcclient exception can happen. + """ + actions = { + "set": ensure_set, + "facts": facts, + } + return actions[module.params['state']](module) + + +def main(): + + # The following definition of module input parameters must match the + # description of the options in the DOCUMENTATION string. + argument_spec = dict( + hmc_host=dict(required=True, type='raw'), + hmc_auth=hmc_auth_parameter(), + name=dict(required=True, type='str'), + state=dict(required=True, type='str', choices=['set', 'facts']), + record_id=dict(required=False, type='str', default=None), + software_model=dict(required=False, type='str', default=None), + software_model_direction=dict( + required=False, type='str', choices=['increase', 'decrease'], + default=None), + specialty_processors=dict(required=False, type='dict', default=None), + test_activation=dict(required=False, type='bool', default=False), + force=dict(required=False, type='bool', default=False), + log_file=dict(required=False, type='str', default=None), + _faked_session=dict(required=False, type='raw'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True) + + if IMP_URLLIB3_ERR is not None: + module.fail_json(msg=missing_required_lib("requests"), + exception=IMP_URLLIB3_ERR) + + requests.packages.urllib3.disable_warnings() + + if IMP_ZHMCCLIENT_ERR is not None: + module.fail_json(msg=missing_required_lib("zhmcclient"), + exception=IMP_ZHMCCLIENT_ERR) + + common_fail_on_import_errors(module) + + log_file = module.params['log_file'] + log_init(LOGGER_NAME, log_file) + + module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) + + _params = dict(module.params) + del _params['hmc_auth'] + LOGGER.debug("Module entry: params: %r", _params) + + try: + + changed, result = perform_task(module) + + except (Error, zhmcclient.Error) as exc: + # These exceptions are considered errors in the environment or in user + # input. They have a proper message that stands on its own, so we + # simply pass that message on and will not need a traceback. + msg = "{0}: {1}".format(exc.__class__.__name__, exc) + LOGGER.debug("Module exit (failure): msg: %r", msg) + module.fail_json(msg=msg) + # Other exceptions are considered module errors and are handled by Ansible + # by showing the traceback. + + LOGGER.debug("Module exit (success): changed: %s, cpc: %r", + changed, result) + module.exit_json( + changed=changed, cpc=result) + + +if __name__ == '__main__': + main() diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index 8a873507..cd8bd26e 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -1,6 +1,7 @@ plugins/modules/zhmc_adapter_list.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_adapter.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_console.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 +plugins/modules/zhmc_cpc_capacity.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_cpc_list.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_cpc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_crypto_attachment.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index 8a873507..cd8bd26e 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -1,6 +1,7 @@ plugins/modules/zhmc_adapter_list.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_adapter.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_console.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 +plugins/modules/zhmc_cpc_capacity.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_cpc_list.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_cpc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_crypto_attachment.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 8a873507..cd8bd26e 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -1,6 +1,7 @@ plugins/modules/zhmc_adapter_list.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_adapter.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_console.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 +plugins/modules/zhmc_cpc_capacity.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_cpc_list.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_cpc.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zhmc_crypto_attachment.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 diff --git a/tests/unit/test_common.py b/tests/unit/test_common.py index 5050add5..5ecef585 100644 --- a/tests/unit/test_common.py +++ b/tests/unit/test_common.py @@ -143,3 +143,161 @@ def test_common_parse_hmc_host( hmc_host = common.parse_hmc_host(in_hmc_host) assert hmc_host == exp_hmc_host + + +TESTCASES_COMMON_ENSURE_HYPHENS = [ + # Testcases for test_common_ensure_hyphens() + # The list items are tuples with the following items: + # - input_dict (dict): Input dict for the function + # - exp_dict (dict): Expected result dict + + ( + {}, + {}, + ), + ( + { + 'a': 1, + }, + { + 'a': 1, + }, + ), + ( + { + 'a_b': '1_2', + }, + { + 'a-b': '1_2', + }, + ), + ( + { + 'a_b_c': 1, + }, + { + 'a-b-c': 1, + }, + ), + ( + { + 'a_b_c': { + 'd_e': { + 'f_g': 1, + }, + }, + }, + { + 'a-b-c': { + 'd-e': { + 'f-g': 1, + }, + }, + }, + ), + ( + { + 'a_b_c': [ + '1_2', + ], + }, + { + 'a-b-c': [ + '1_2', + ], + }, + ), +] + + +@pytest.mark.parametrize( + "input_dict, exp_dict", + TESTCASES_COMMON_ENSURE_HYPHENS) +def test_common_ensure_hyphens(input_dict, exp_dict): + """ + Test the ensure_hyphens() function. + """ + + # The code to be tested + result_dict = common.ensure_hyphens(input_dict) + + assert result_dict == exp_dict + + +TESTCASES_COMMON_ENSURE_UNDERSCORES = [ + # Testcases for test_common_ensure_underscores() + # The list items are tuples with the following items: + # - input_dict (dict): Input dict for the function + # - exp_dict (dict): Expected result dict + + ( + {}, + {}, + ), + ( + { + 'a': 1, + }, + { + 'a': 1, + }, + ), + ( + { + 'a-b': '1-2', + }, + { + 'a_b': '1-2', + }, + ), + ( + { + 'a-b-c': 1, + }, + { + 'a_b_c': 1, + }, + ), + ( + { + 'a-b-c': { + 'd-e': { + 'f-g': 1, + }, + }, + }, + { + 'a_b_c': { + 'd_e': { + 'f_g': 1, + }, + }, + }, + ), + ( + { + 'a-b-c': [ + '1-2', + ], + }, + { + 'a_b_c': [ + '1-2', + ], + }, + ), +] + + +@pytest.mark.parametrize( + "input_dict, exp_dict", + TESTCASES_COMMON_ENSURE_UNDERSCORES) +def test_common_ensure_underscores(input_dict, exp_dict): + """ + Test the ensure_underscores() function. + """ + + # The code to be tested + result_dict = common.ensure_underscores(input_dict) + + assert result_dict == exp_dict