From cd24d57636eaa0b08e78506e15139d28ad0c3124 Mon Sep 17 00:00:00 2001 From: Simon Dodsley Date: Fri, 30 Aug 2019 01:12:39 -0400 Subject: [PATCH] Add IOPs bandwidth support to volumes (#61577) --- .../61577-support-iops-in-purefa_volume.yml | 2 + .../storage/purestorage/purefa_volume.py | 299 +++++++++++------- 2 files changed, 194 insertions(+), 107 deletions(-) create mode 100644 changelogs/fragments/61577-support-iops-in-purefa_volume.yml diff --git a/changelogs/fragments/61577-support-iops-in-purefa_volume.yml b/changelogs/fragments/61577-support-iops-in-purefa_volume.yml new file mode 100644 index 00000000000..fd3f1f965ab --- /dev/null +++ b/changelogs/fragments/61577-support-iops-in-purefa_volume.yml @@ -0,0 +1,2 @@ +minor_changes: + - purefa_volume - Change I(qos) parameter to I(bw_iops), but retain I(qos) as an alias for backwards compatability (https://github.com/ansible/ansible/pull/61577). diff --git a/lib/ansible/modules/storage/purestorage/purefa_volume.py b/lib/ansible/modules/storage/purestorage/purefa_volume.py index 8030951b146..2bb5cf87aef 100644 --- a/lib/ansible/modules/storage/purestorage/purefa_volume.py +++ b/lib/ansible/modules/storage/purestorage/purefa_volume.py @@ -50,14 +50,23 @@ options: description: - Volume size in M, G, T or P units. type: str - qos: + bw_qos: description: - Bandwidth limit for volume in M or G units. M will set MB/s G will set GB/s - To clear an existing Qos setting using 0 (zero) + To clear an existing QoS setting use 0 (zero) version_added: '2.8' type: str + aliases: [ qos ] + iops_qos: + description: + - IOPs limit for volume - use value or K or M + K will mean 1000 + M will mean 1000000 + To clear an existing IOPs setting use 0 (zero) + version_added: '2.9' + type: str extends_documentation_fragment: - purestorage.fa ''' @@ -67,7 +76,8 @@ EXAMPLES = r''' purefa_volume: name: foo size: 1T - qos: 58M + bw_qos: 58M + iops_qos: 23K fa_url: 10.10.10.2 api_token: e31060a7-21fc-e277-6240-25983c6c4592 state: present @@ -108,7 +118,8 @@ EXAMPLES = r''' - name: Clear volume QoS from volume foo purefa_volume: name: foo - qos: 0 + bw_qos: 0 + iops_qos: 0 fa_url: 10.10.10.2 api_token: e31060a7-21fc-e277-6240-25983c6c4592 state: present @@ -138,6 +149,12 @@ volume: size: description: Volume size in bytes type: int + bandwidth_limit: + description: Volume bandwidth limit in bytes/sec + type: int + iops_limit: + description: Volume IOPs limit + type: int ''' from ansible.module_utils.basic import AnsibleModule @@ -147,6 +164,7 @@ from ansible.module_utils.pure import get_system, purefa_argument_spec QOS_API_VERSION = "1.14" VGROUPS_API_VERSION = "1.13" POD_API_VERSION = "1.13" +IOPS_API_VERSION = "1.17" def human_to_bytes(size): @@ -166,6 +184,8 @@ def human_to_bytes(size): bytes *= 1073741824 elif unit == 'M': bytes *= 1048576 + elif unit == 'K': + bytes *= 1024 else: bytes = 0 else: @@ -173,6 +193,26 @@ def human_to_bytes(size): return bytes +def human_to_real(iops): + """Given a human-readable IOPs string (e.g. 2K, 30M), + return the real number. Will return 0 if the argument has + unexpected form. + """ + digit = iops[:-1] + unit = iops[-1] + if digit.isdigit(): + digit = int(digit) + if unit == 'M': + digit *= 1000000 + elif unit == 'K': + digit *= 1000 + else: + digit = 0 + else: + digit = 0 + return digit + + def get_volume(module, array): """Return Volume or None""" try: @@ -184,10 +224,7 @@ def get_volume(module, array): def get_destroyed_volume(module, array): """Return Destroyed Volume or None""" try: - if array.get_volume(module.params['name'], pending=True)['time_remaining'] != '': - return True - else: - return False + return bool(array.get_volume(module.params['name'], pending=True)['time_remaining'] != '') except Exception: return False @@ -240,127 +277,173 @@ def check_pod(module, array): def create_volume(module, array): """Create Volume""" - changed = False - if "/" in module.params['name'] and not check_vgroup(module, array): - module.fail_json(msg="Failed to create volume {0}. Volume Group does not exist.".format(module.params["name"])) - if "::" in module.params['name'] and not check_pod(module, array): - module.fail_json(msg="Failed to create volume {0}. Poid does not exist".format(module.params["name"])) - volfact = [] - api_version = array._list_available_rest_versions() - if module.params['qos'] and QOS_API_VERSION in api_version: - if 549755813888 >= int(human_to_bytes(module.params['qos'])) >= 1048576: + changed = True + if not module.check_mode: + if "/" in module.params['name'] and not check_vgroup(module, array): + module.fail_json(msg="Failed to create volume {0}. Volume Group does not exist.".format(module.params["name"])) + if "::" in module.params['name'] and not check_pod(module, array): + module.fail_json(msg="Failed to create volume {0}. Poid does not exist".format(module.params["name"])) + volfact = [] + api_version = array._list_available_rest_versions() + if module.params['bw_qos'] or module.params['iops_qos']: + if module.params['bw_qos'] and QOS_API_VERSION in api_version or module.params['iops_qos'] and IOPS_API_VERSION in api_version: + if module.params['bw_qos'] and not module.params['iops_qos']: + if 549755813888 >= int(human_to_bytes(module.params['bw_qos'])) >= 1048576: + try: + volfact = array.create_volume(module.params['name'], + module.params['size'], + bandwidth_limit=module.params['bw_qos']) + except Exception: + module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name'])) + else: + module.fail_json(msg='Bandwidth QoS value {0} out of range.'.format(module.params['bw_qos'])) + elif module.params['iops_qos'] and not module.params['bw_qos']: + if 100000000 >= int(human_to_real(module.params['iops_qos'])) >= 100: + try: + volfact = array.create_volume(module.params['name'], + module.params['size'], + iops_limit=module.params['iops_qos']) + except Exception: + module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name'])) + else: + module.fail_json(msg='IOPs QoS value {0} out of range.'.format(module.params['iops_qos'])) + else: + bw_qos_size = int(human_to_bytes(module.params['bw_qos'])) + if 100000000 >= int(human_to_real(module.params['iops_qos'])) >= 100 and 549755813888 >= bw_qos_size >= 1048576: + try: + volfact = array.create_volume(module.params['name'], + module.params['size'], + iops_limit=module.params['iops_qos'], + bandwidth_limit=module.params['bw_qos']) + except Exception: + module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name'])) + else: + module.fail_json(msg='IOPs or Bandwidth QoS value out of range.') + + else: try: - volfact = array.create_volume(module.params['name'], - module.params['size'], - bandwidth_limit=module.params['qos']) - changed = True + volfact = array.create_volume(module.params['name'], module.params['size']) except Exception: module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name'])) - else: - module.fail_json(msg='QoS value {0} out of range.'.format(module.params['qos'])) - else: - try: - volfact = array.create_volume(module.params['name'], module.params['size']) - changed = True - except Exception: - module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name'])) module.exit_json(changed=changed, volume=volfact) def copy_from_volume(module, array): """Create Volume Clone""" - changed = False - volfact = [] - tgt = get_target(module, array) + changed = True + if not module.check_mode: + volfact = [] + tgt = get_target(module, array) - if tgt is None: - try: - volfact = array.copy_volume(module.params['name'], - module.params['target']) - changed = True - except Exception: - module.fail_json(msg='Copy volume {0} to volume {1} failed.'.format(module.params['name'], - module.params['target'])) - elif tgt is not None and module.params['overwrite']: - try: - volfact = array.copy_volume(module.params['name'], - module.params['target'], - overwrite=module.params['overwrite']) - changed = True - except Exception: - module.fail_json(msg='Copy volume {0} to volume {1} failed.'.format(module.params['name'], - module.params['target'])) + if tgt is None: + try: + volfact = array.copy_volume(module.params['name'], + module.params['target']) + except Exception: + module.fail_json(msg='Copy volume {0} to volume {1} failed.'.format(module.params['name'], + module.params['target'])) + elif tgt is not None and module.params['overwrite']: + try: + volfact = array.copy_volume(module.params['name'], + module.params['target'], + overwrite=module.params['overwrite']) + except Exception: + module.fail_json(msg='Copy volume {0} to volume {1} failed.'.format(module.params['name'], + module.params['target'])) module.exit_json(changed=changed, volume=volfact) def update_volume(module, array): """Update Volume size and/or QoS""" - changed = False - volfact = [] - api_version = array._list_available_rest_versions() - vol = array.get_volume(module.params['name']) - if QOS_API_VERSION in api_version: + changed = True + if not module.check_mode: + change = False + volfact = [] + api_version = array._list_available_rest_versions() + vol = array.get_volume(module.params['name']) vol_qos = array.get_volume(module.params['name'], qos=True) - if vol_qos['bandwidth_limit'] is None: - vol_qos['bandwidth_limit'] = 0 - if module.params['size']: - if human_to_bytes(module.params['size']) != vol['size']: - if human_to_bytes(module.params['size']) > vol['size']: - try: - volfact = array.extend_volume(module.params['name'], module.params['size']) - changed = True - except Exception: - module.fail_json(msg='Volume {0} resize failed.'.format(module.params['name'])) - if module.params['qos'] and QOS_API_VERSION in api_version: - if human_to_bytes(module.params['qos']) != vol_qos['bandwidth_limit']: - if module.params['qos'] == '0': - try: - volfact = array.set_volume(module.params['name'], bandwidth_limit='') - changed = True - except Exception: - module.fail_json(msg='Volume {0} QoS removal failed.'.format(module.params['name'])) - elif 549755813888 >= int(human_to_bytes(module.params['qos'])) >= 1048576: - try: - volfact = array.set_volume(module.params['name'], - bandwidth_limit=module.params['qos']) - changed = True - except Exception: - module.fail_json(msg='Volume {0} QoS change failed.'.format(module.params['name'])) - else: - module.fail_json(msg='QoS value {0} out of range. Check documentation.'.format(module.params['qos'])) - - module.exit_json(changed=changed, volume=volfact) + if QOS_API_VERSION in api_version: + if vol_qos['bandwidth_limit'] is None: + vol_qos['bandwidth_limit'] = 0 + if IOPS_API_VERSION in api_version: + if vol_qos['iops_limit'] is None: + vol_qos['iops_limit'] = 0 + if module.params['size']: + if human_to_bytes(module.params['size']) != vol['size']: + if human_to_bytes(module.params['size']) > vol['size']: + try: + volfact = array.extend_volume(module.params['name'], module.params['size']) + change = True + except Exception: + module.fail_json(msg='Volume {0} resize failed.'.format(module.params['name'])) + if module.params['bw_qos'] and QOS_API_VERSION in api_version: + if human_to_bytes(module.params['bw_qos']) != vol_qos['bandwidth_limit']: + if module.params['bw_qos'] == '0': + try: + volfact = array.set_volume(module.params['name'], bandwidth_limit='') + change = True + except Exception: + module.fail_json(msg='Volume {0} Bandwidth QoS removal failed.'.format(module.params['name'])) + elif 549755813888 >= int(human_to_bytes(module.params['bw_qos'])) >= 1048576: + try: + volfact = array.set_volume(module.params['name'], + bandwidth_limit=module.params['bw_qos']) + change = True + except Exception: + module.fail_json(msg='Volume {0} Bandwidth QoS change failed.'.format(module.params['name'])) + else: + module.fail_json(msg='Bandwidth QoS value {0} out of range.'.format(module.params['bw_qos'])) + if module.params['iops_qos'] and IOPS_API_VERSION in api_version: + if human_to_real(module.params['iops_qos']) != vol_qos['iops_limit']: + if module.params['iops_qos'] == '0': + try: + volfact = array.set_volume(module.params['name'], iops_limit='') + change = True + except Exception: + module.fail_json(msg='Volume {0} IOPs QoS removal failed.'.format(module.params['name'])) + elif 100000000 >= int(human_to_real(module.params['iops_qos'])) >= 100: + try: + volfact = array.set_volume(module.params['name'], + iops_limit=module.params['iops_qos']) + except Exception: + module.fail_json(msg='Volume {0} IOPs QoS change failed.'.format(module.params['name'])) + else: + module.fail_json(msg='Bandwidth QoS value {0} out of range.'.format(module.params['bw_qos'])) + + module.exit_json(changed=change, volume=volfact) + + module.exit_json(changed=changed) def delete_volume(module, array): """ Delete Volume""" - changed = False - volfact = [] - try: - array.destroy_volume(module.params['name']) - if module.params['eradicate']: - try: - volfact = array.eradicate_volume(module.params['name']) - except Exception: - module.fail_json(msg='Eradicate volume {0} failed.'.format(module.params['name'])) - changed = True - except Exception: - module.fail_json(msg='Delete volume {0} failed.'.format(module.params['name'])) + changed = True + if not module.check_mode: + volfact = [] + try: + array.destroy_volume(module.params['name']) + if module.params['eradicate']: + try: + volfact = array.eradicate_volume(module.params['name']) + except Exception: + module.fail_json(msg='Eradicate volume {0} failed.'.format(module.params['name'])) + except Exception: + module.fail_json(msg='Delete volume {0} failed.'.format(module.params['name'])) module.exit_json(changed=changed, volume=volfact) def eradicate_volume(module, array): """ Eradicate Deleted Volume""" - changed = False - volfact = [] - if module.params['eradicate']: - try: - array.eradicate_volume(module.params['name']) - changed = True - except Exception: - module.fail_json(msg='Eradication of volume {0} failed'.format(module.params['name'])) + changed = True + if not module.check_mode: + volfact = [] + if module.params['eradicate']: + try: + array.eradicate_volume(module.params['name']) + except Exception: + module.fail_json(msg='Eradication of volume {0} failed'.format(module.params['name'])) module.exit_json(changed=changed, volume=volfact) @@ -372,7 +455,8 @@ def main(): overwrite=dict(type='bool', default=False), eradicate=dict(type='bool', default=False), state=dict(type='str', default='present', choices=['absent', 'present']), - qos=dict(type='str'), + bw_qos=dict(type='str', aliases=['qos']), + iops_qos=dict(type='str'), size=dict(type='str'), )) @@ -380,10 +464,11 @@ def main(): module = AnsibleModule(argument_spec, mutually_exclusive=mutually_exclusive, - supports_check_mode=False) + supports_check_mode=True) size = module.params['size'] - qos = module.params['qos'] + bw_qos = module.params['bw_qos'] + iops_qos = module.params['iops_qos'] state = module.params['state'] array = get_system(module) volume = get_volume(module, array) @@ -393,7 +478,7 @@ def main(): if state == 'present' and not volume and size: create_volume(module, array) - elif state == 'present' and volume and (size or qos): + elif state == 'present' and volume and (size or bw_qos or iops_qos): update_volume(module, array) elif state == 'present' and volume and target: copy_from_volume(module, array)