From 67c6a4d1dccb62159b9d9b2dea4e2f487446e276 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 26 Apr 2024 06:05:56 +0200 Subject: Adding upstream version 9.5.1+dfsg. Signed-off-by: Daniel Baumann --- .../plugins/modules/infini_infinimetrics.py | 358 +++++++++++++++++++++ .../plugins/modules/infini_notification_rule.py | 41 ++- .../infinibox/plugins/modules/infini_pool.py | 68 +++- 3 files changed, 446 insertions(+), 21 deletions(-) create mode 100644 ansible_collections/infinidat/infinibox/plugins/modules/infini_infinimetrics.py (limited to 'ansible_collections/infinidat/infinibox/plugins/modules') diff --git a/ansible_collections/infinidat/infinibox/plugins/modules/infini_infinimetrics.py b/ansible_collections/infinidat/infinibox/plugins/modules/infini_infinimetrics.py new file mode 100644 index 000000000..4c9baac5b --- /dev/null +++ b/ansible_collections/infinidat/infinibox/plugins/modules/infini_infinimetrics.py @@ -0,0 +1,358 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# pylint: disable=invalid-name,use-dict-literal,line-too-long,wrong-import-position,too-many-locals + +# Copyright: (c) 2024, Infinidat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""This module creates or modifies Infinibox registrations on Infinimetrics.""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: infini_infinimetrics +version_added: 2.16.0 +short_description: Create (present state) or remove (absent state) an Infinibox registration on an Infinimetrics. +description: + - Create (present state) or remove (absent state) an Infinibox registration on an Infinimetrics. +author: David Ohlemacher (@ohlemacher) +options: + ibox_serial: + description: + - Infinibox serial number. + type: str + required: true + ibox_url: + description: Infinibox DNS resolvable hostname or IPv4 address. + type: str + required: false + ibox_readonly_user: + description: + - Read only Infinibox user name. + type: str + required: false + ibox_readonly_password: + description: + - Read only Infinibox user password. + type: str + required: false + imx_system: + description: + - Infinimetrics hostname or IPv4 Address. + type: str + required: true + imx_user: + description: + - Infinimetrics user name. + type: str + required: true + imx_password: + description: + - Infinimetrics user password. + type: str + required: true + state: + description: + - Registers the Infinibox with Infinimetrics, when using state present. + - For state absent, the Infinibox is disabled on Infinimetrics and will no longer appear on the Infinimetrics UI. + - Existing Infinibox data is not purged from Infinimetrics. + - Purging may be executed using the Infinimetrics CLI tool. + type: str + required: false + default: present + choices: [ "present", "absent" ] +extends_documentation_fragment: + - infinibox +""" + +EXAMPLES = r""" +- name: Register IBOX with Infinimetrics + infini_infinimetrics: + infinimetrics_system: infinimetrics + state: present + user: admin + password: secret + system: ibox001 + +- name: Deregister IBOX from Infinimetrics + infini_infinimetrics: + infinimetrics_system: infinimetrics + state: absent + user: admin + password: secret + system: ibox001 +""" + +# RETURN = r''' # ''' + +import re +import traceback + +from ansible.module_utils.basic import missing_required_lib + +try: + import requests +except ImportError: + HAS_REQUESTS = False + HAS_REQUESTS_IMPORT_ERROR = traceback.format_exc() +else: + HAS_REQUESTS = True + HAS_REQUESTS_IMPORT_ERROR = None + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.infinidat.infinibox.plugins.module_utils.infinibox import ( + api_wrapper, + get_system, + infinibox_argument_spec, +) + + +def find_csrfmiddleware_token(response): + """Search for csrfmiddlewaretoken in the response lines. Return token or None.""" + token = None + for line_bytes in response.iter_lines(): + line = str(line_bytes) + # Example of line searched for: + # ' + result = re.search(r'"csrfmiddlewaretoken" value="(\w+)"', line) + if result: + token = result.group(1) + break + return token + + +@api_wrapper +def imx_login(module, imx_session): + """ Log into an IMX (GET and POST) using credentials. """ + ibox_url = module.params.get('ibox_url') + imx_system = module.params.get('imx_system') + path = f"https://{imx_system}/auth/login/" + + # Use GET to get a token + payload = { + 'username': module.params.get('imx_user', None), + 'password': module.params.get('imx_password', None), + } + get_response = imx_session.get(path, data=payload, verify=False) + status_code = get_response.status_code + if status_code not in [200]: + text = get_response.text + msg = f"Cannot add Infinibox {ibox_url} to Infinimetrics {imx_system}. Status code: {status_code}. Text returned: {text}" + module.fail_json(msg=msg) + + token = find_csrfmiddleware_token(get_response) + + # Use POST provide token + data = { + 'csrfmiddlewaretoken': token, + 'password': module.params.get('imx_password', None), + 'username': module.params.get('imx_user', None), + } + headers = { + 'referer': f'https://{module.params.get("imx_system")}', + } + response = imx_session.post(path, headers=headers, data=data, verify=False) + if response.status_code not in [200, 201]: + msg = f"Cannot log into Infinimetrics {imx_system}. Status code: {response.status_code}. Text returned: {response.text}" + module.fail_json(msg=msg) + + +@api_wrapper +def imx_system_add(module, imx_session): + """ Add an Infinibox to an Infinimetrics using an imx_session """ + imx_system = module.params.get('imx_system') + ibox_readonly_user = module.params.get('ibox_readonly_user') + ibox_readonly_password = module.params.get('ibox_readonly_password') + ibox_serial = module.params.get('ibox_serial') + ibox_url = module.params.get('ibox_url') + path = f"https://{imx_system}/system/add/" + headers = { + 'referer': f'https://{imx_system}/', + } + + # Use GET to get a token + get_response = imx_session.get(path, headers=headers, verify=False) + status_code = get_response.status_code + if status_code not in [200]: + text = get_response.text + msg = f"Cannot add Infinibox {ibox_url} to Infinimetrics {imx_system}. Status code: {status_code}. Text returned: {text}" + module.fail_json(msg=msg) + + get_token = find_csrfmiddleware_token(get_response) + + # Use POST provide token + data = { + 'api_url': ibox_url, + 'api_username': ibox_readonly_user, + 'api_password': ibox_readonly_password, + 'csrfmiddlewaretoken': get_token, + } + response = imx_session.post(path, headers=headers, data=data, verify=False) + status_code = response.status_code + text = response.text + if status_code not in [200, 201]: + msg = f"Cannot add Infinibox {ibox_url} to Infinimetrics {imx_system}. Status code: {status_code}. Text returned: {text}" + module.fail_json(msg=msg) + + # Check that the IBOX was added or was previously added. + # Search for one of: + # - 'The system is already monitored' + # - add_progress url + if ("The system is already" not in response.text or "monitored" not in response.text) \ + and (f"/system/{ibox_serial}/add_progress" not in response.text): + msg = f"Cannot add Infinibox {ibox_url} to Infinimetrics {imx_system}. Status code: {status_code}. Text returned: {text}" + module.fail_json(msg=msg) + + if "add_progress" in response.text: + return True # Just added now + return False # Previously added + + +@api_wrapper +def imx_system_delete(module, imx_session): + """ Remove an Infinibox from an Infinimetrics using an imx_session """ + imx_system = module.params.get('imx_system') + serial = module.params.get('ibox_serial') + ibox_url = module.params.get('ibox_url') + path = f"https://{imx_system}/system/{serial}/edit/" + headers = { + 'referer': f'https://{imx_system}/', + } + + # Use GET to get a token + get_response = imx_session.get(path, headers=headers, verify=False) + status_code = get_response.status_code + if status_code not in [200]: + text = get_response.text + msg = f"Cannot remove Infinibox {ibox_url} from Infinimetrics {imx_system}. Status code: {status_code}. Text returned: {text}" + module.fail_json(msg=msg) + + get_token = find_csrfmiddleware_token(get_response) + + path = f"https://{imx_system}/system/{serial}/remove/" + headers = { + 'X-CSRFToken': get_token, + 'referer': f'https://{imx_system}/', + } + response = imx_session.delete(path, headers=headers, verify=False) + + # Check that the IBOX was removed or was previously removed + # In response.return_code, search for 200 + status_code = response.status_code + if status_code not in [200, 201]: + text = response.text + msg = f"Cannot remove Infinibox {serial} from infinimetrics {imx_system}. Status code: {status_code}, Text returned: {text}" + module.fail_json(msg=msg) + + +def handle_present(module): + """ Handle the present state parameter """ + imx_system = module.params.get('imx_system') + ibox_url = module.params.get('ibox_url') + + imx_session = requests.session() + imx_login(module, imx_session) + is_newly_added = imx_system_add(module, imx_session) + + if is_newly_added: + msg = f"Infinibox {ibox_url} added to Infinimetrics {imx_system}" + changed = True + else: + msg = f"Infinibox {ibox_url} previously added to Infinimetrics {imx_system}" + changed = False + + result = dict(changed=changed, msg=msg) + module.exit_json(**result) + + +def handle_absent(module): + """ Handle the absent state parameter. """ + imx_system = module.params.get('imx_system') + serial = module.params.get('ibox_serial') + + imx_session = requests.session() + imx_login(module, imx_session) + imx_system_delete(module, imx_session) + result = dict( + changed=True, + msg=f"Infinibox serial {serial} has been removed from Infinimetrics {imx_system}" + ) + module.exit_json(**result) + + +def execute_state(module): + """Handle states""" + state = module.params["state"] + try: + if state == "present": + handle_present(module) + elif state == "absent": + handle_absent(module) + else: + module.fail_json(msg=f"Internal handler error. Invalid state: {state}") + finally: + system = get_system(module) + system.logout() + + +def verify_params(module, req_params): + """ Verify that required params are provided """ + missing_req_params = [] + for req_param in req_params: + if not module.params[req_param]: + missing_req_params.append(req_param) + if missing_req_params: + state = module.params["state"] + msg = f"Cannot handle state {state} due to missing parameters: {missing_req_params}" + module.fail_json(msg=msg) + + +def check_options(module): # pylint: disable=too-many-branches + """ Check option logic """ + state = module.params['state'] + + if state == 'present': + req_params = ["ibox_url", "ibox_readonly_user", "ibox_readonly_password"] + verify_params(module, req_params) + elif state == 'absent': + pass + else: + module.fail_json(msg=f'Internal handler error. Invalid state: {state}') + + +def main(): + """ Main """ + argument_spec = infinibox_argument_spec() + argument_spec.update( + dict( + ibox_serial=dict(required=True), + ibox_url=dict(required=False, default=None), + ibox_readonly_user=dict(required=False, default=None), + ibox_readonly_password=dict(required=False, no_log=True, default=None), + imx_system=dict(required=True), + imx_user=dict(required=True), + imx_password=dict(required=True, no_log=True), + state=dict(default="present", choices=["present", "absent"]), + ) + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True) + + if not HAS_REQUESTS: + module.fail_json( + msg=missing_required_lib('requests'), + exception=HAS_REQUESTS_IMPORT_ERROR, + ) + + check_options(module) + + execute_state(module) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/infinidat/infinibox/plugins/modules/infini_notification_rule.py b/ansible_collections/infinidat/infinibox/plugins/modules/infini_notification_rule.py index 1916cdb49..c6e30af5d 100644 --- a/ansible_collections/infinidat/infinibox/plugins/modules/infini_notification_rule.py +++ b/ansible_collections/infinidat/infinibox/plugins/modules/infini_notification_rule.py @@ -56,6 +56,14 @@ options: elements: str required: false default: [] + recipient_target_name: + description: + - When creating a rule using recipients, a notification target is required. + - Usually, this target is named "customer-smtp". + - If this is not the case, use this variable to specifiy another name. + type: str + required: false + default: customer-smtp target: description: - Notification target @@ -117,6 +125,23 @@ except ModuleNotFoundError: ) +@api_wrapper +def find_recipient_target_name_id(module, system): + """ Find the ID of the target by name """ + target = module.params["recipient_target_name"] + path = f"notifications/targets?name={target}&fields=id" + api_result = system.api.get( + path=path + ) + if len(api_result.get_json()['result']) > 0: + result = api_result.get_json()['result'][0] + target_id = result['id'] + else: + msg = f"Cannot find an ID for recipient_target_name {target}" + module.fail_json(msg=msg) + return target_id + + @api_wrapper def find_target_id(module, system): """ Find the ID of the target by name """ @@ -190,16 +215,14 @@ def create_rule(module): target_parameters = { "recipients": recipients } - target_id = 3 # Target ID for sending to recipients + json_data["target_id"] = find_recipient_target_name_id(module, system) # Target ID for sending to recipients json_data["target_parameters"] = target_parameters elif target: - target_id = find_target_id(module, system) + json_data["target_id"] = find_target_id(module, system) else: msg = "Neither recipients nor target parameters specified" module.fail_json(msg=msg) - json_data["target_id"] = target_id - system.api.post(path=path, data=json_data) @@ -215,6 +238,8 @@ def update_rule(module): exclude_events = module.params["exclude_events"] recipients = module.params["recipients"] target = module.params["target"] + rule_id = find_rule_id(module, system) + path = f"notifications/rules/{rule_id}" json_data = { "name": name, @@ -227,17 +252,14 @@ def update_rule(module): target_parameters = { "recipients": recipients } - target_id = 3 # Target ID for sending to recipients + json_data["target_id"] = find_recipient_target_name_id(module, system) # Target ID for sending to recipients json_data["target_parameters"] = target_parameters elif target: - target_id = find_target_id(module, system) + json_data["target_id"] = find_target_id(module, system) else: msg = "Neither recipients nor target parameters specified" module.fail_json(msg=msg) - json_data["target_id"] = target_id - rule_id = find_rule_id(module, system) - path = f"notifications/rules/{rule_id}" system.api.put(path=path, data=json_data) @@ -342,6 +364,7 @@ def main(): "include_events": {"required": False, "default": [], "type": "list", "elements": "str"}, "exclude_events": {"required": False, "default": [], "type": "list", "elements": "str"}, "recipients": {"required": False, "default": [], "type": "list", "elements": "str"}, + "recipient_target_name": {"required": False, "default": "customer-smtp", "type": "str"}, "target": {"required": False, "type": "str", "default": None}, "state": {"default": "present", "choices": ["stat", "present", "absent"]}, } diff --git a/ansible_collections/infinidat/infinibox/plugins/modules/infini_pool.py b/ansible_collections/infinidat/infinibox/plugins/modules/infini_pool.py index 43daa71be..97366a817 100644 --- a/ansible_collections/infinidat/infinibox/plugins/modules/infini_pool.py +++ b/ansible_collections/infinidat/infinibox/plugins/modules/infini_pool.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# pylint: disable=invalid-name,use-dict-literal,line-too-long,wrong-import-position +# pylint: disable=invalid-name,use-dict-literal,line-too-long,wrong-import-position,too-many-branches """This module creates, deletes or modifies pools on Infinibox.""" @@ -59,6 +59,18 @@ options: required: false default: yes type: bool + physical_capacity_warning: + description: + - Capacity, in percent, for a warning notification. + required: false + type: int + default: 80 + physical_capacity_critical: + description: + - Capacity, in percent, for a critical notification. + required: false + type: int + default: 90 notes: - Infinibox Admin level access is required for pool modifications @@ -123,16 +135,22 @@ def create_pool(module, system): vsize = module.params['vsize'] ssd_cache = module.params['ssd_cache'] compression = module.params['compression'] + physical_capacity_warning = module.params['physical_capacity_warning'] + physical_capacity_critical = module.params['physical_capacity_critical'] if not module.check_mode: if not size and not vsize: - pool = system.pools.create(name=name, physical_capacity=Capacity('1TB'), virtual_capacity=Capacity('1TB')) + pool = system.pools.create(name=name, physical_capacity=Capacity('1TB'), virtual_capacity=Capacity('1TB'), + physical_capacity_warning=physical_capacity_warning, physical_capacity_critical=physical_capacity_critical) elif size and not vsize: - pool = system.pools.create(name=name, physical_capacity=Capacity(size), virtual_capacity=Capacity(size)) + pool = system.pools.create(name=name, physical_capacity=Capacity(size), virtual_capacity=Capacity(size), + physical_capacity_warning=physical_capacity_warning, physical_capacity_critical=physical_capacity_critical) elif not size and vsize: - pool = system.pools.create(name=name, physical_capacity=Capacity('1TB'), virtual_capacity=Capacity(vsize)) + pool = system.pools.create(name=name, physical_capacity=Capacity('1TB'), virtual_capacity=Capacity(vsize), + physical_capacity_warning=physical_capacity_warning, physical_capacity_critical=physical_capacity_critical) else: - pool = system.pools.create(name=name, physical_capacity=Capacity(size), virtual_capacity=Capacity(vsize)) + pool = system.pools.create(name=name, physical_capacity=Capacity(size), virtual_capacity=Capacity(vsize), + physical_capacity_warning=physical_capacity_warning, physical_capacity_critical=physical_capacity_critical) # Default value of ssd_cache is True. Disable ssd caching if False if not ssd_cache: pool.update_ssd_enabled(ssd_cache) @@ -150,7 +168,7 @@ def update_pool(module, pool): size = module.params['size'] vsize = module.params['vsize'] - # ssd_cache = module.params['ssd_cache'] + ssd_cache = module.params['ssd_cache'] compression = module.params['compression'] # Roundup the capacity to mimic Infinibox behaviour @@ -168,15 +186,29 @@ def update_pool(module, pool): pool.update_virtual_capacity(virtual_capacity) changed = True - # if pool.is_ssd_enabled() != ssd_cache: - # if not module.check_mode: - # pool.update_ssd_enabled(ssd_cache) - # changed = True + if pool.is_ssd_enabled() != ssd_cache: + if not module.check_mode: + pool.update_ssd_enabled(ssd_cache) + changed = True if pool.is_compression_enabled() != compression: if not module.check_mode: pool.update_compression_enabled(compression) - changed = True + changed = True + + physical_capacity_critical = module.params.get('physical_capacity_critical') + existing_physical_capacity_critical = pool.get_physical_capacity_critical() + if physical_capacity_critical != existing_physical_capacity_critical: + if not module.check_mode: + pool.update_physical_capacity_critical(physical_capacity_critical) + changed = True + + physical_capacity_warning = module.params.get('physical_capacity_warning') + existing_physical_capacity_warning = pool.get_physical_capacity_warning() + if physical_capacity_warning != existing_physical_capacity_warning: + if not module.check_mode: + pool.update_physical_capacity_warning(physical_capacity_warning) + changed = True if changed: msg = 'Pool updated' @@ -206,12 +238,22 @@ def handle_stat(module): # print('fields: {0}'.format(fields)) free_physical_capacity = fields.get('free_physical_capacity', None) pool_id = fields.get('id', None) + physical_capacity_warning = pool.get_physical_capacity_warning() + physical_capacity_critical = pool.get_physical_capacity_critical() + physical_capacity = pool.get_physical_capacity() + virtual_capacity = pool.get_virtual_capacity() result = dict( changed=False, free_physical_capacity=str(free_physical_capacity), + physical_capacity_warning=physical_capacity_warning, + physical_capacity_critical=physical_capacity_critical, + physical_capacity=str(physical_capacity), + virtual_capacity=str(virtual_capacity), + ssd_cache=pool.is_ssd_enabled(), + compression_enabled=pool.is_compression_enabled(), id=pool_id, - msg='Pool stat found' + msg='Pool stat found', ) module.exit_json(**result) @@ -267,6 +309,8 @@ def main(): vsize=dict(), ssd_cache=dict(type='bool', default=True), compression=dict(type='bool', default=True), + physical_capacity_warning=dict(type='int', default=80), + physical_capacity_critical=dict(type='int', default=90), ) ) -- cgit v1.2.3