#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2017, Kairo Araujo <kairo@kairo.eti.br>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = r'''
---
author:
  - Kairo Araujo (@kairoaraujo)
module: aix_lvg
short_description: Manage LVM volume groups on AIX
description:
  - This module creates, removes or resize volume groups on AIX LVM.
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  force:
    description:
    - Force volume group creation.
    type: bool
    default: false
  pp_size:
    description:
    - The size of the physical partition in megabytes.
    type: int
  pvs:
    description:
    - List of comma-separated devices to use as physical devices in this volume group.
    - Required when creating or extending (V(present) state) the volume group.
    - If not informed reducing (V(absent) state) the volume group will be removed.
    type: list
    elements: str
  state:
    description:
    - Control if the volume group exists and volume group AIX state varyonvg V(varyon) or varyoffvg V(varyoff).
    type: str
    choices: [ absent, present, varyoff, varyon ]
    default: present
  vg:
    description:
    - The name of the volume group.
    type: str
    required: true
  vg_type:
    description:
    - The type of the volume group.
    type: str
    choices: [ big, normal, scalable ]
    default: normal
notes:
- AIX will permit remove VG only if all LV/Filesystems are not busy.
- Module does not modify PP size for already present volume group.
'''

EXAMPLES = r'''
- name: Create a volume group datavg
  community.general.aix_lvg:
    vg: datavg
    pp_size: 128
    vg_type: scalable
    state: present

- name: Removing a volume group datavg
  community.general.aix_lvg:
    vg: datavg
    state: absent

- name: Extending rootvg
  community.general.aix_lvg:
    vg: rootvg
    pvs: hdisk1
    state: present

- name: Reducing rootvg
  community.general.aix_lvg:
    vg: rootvg
    pvs: hdisk1
    state: absent
'''

RETURN = r''' # '''

from ansible.module_utils.basic import AnsibleModule


def _validate_pv(module, vg, pvs):
    """
    Function to validate if the physical volume (PV) is not already in use by
    another volume group or Oracle ASM.

    :param module: Ansible module argument spec.
    :param vg: Volume group name.
    :param pvs: Physical volume list.
    :return: [bool, message] or module.fail_json for errors.
    """

    lspv_cmd = module.get_bin_path('lspv', True)
    rc, current_lspv, stderr = module.run_command([lspv_cmd])
    if rc != 0:
        module.fail_json(msg="Failed executing 'lspv' command.", rc=rc, stdout=current_lspv, stderr=stderr)

    for pv in pvs:
        # Get pv list.
        lspv_list = {}
        for line in current_lspv.splitlines():
            pv_data = line.split()
            lspv_list[pv_data[0]] = pv_data[2]

        # Check if pv exists and is free.
        if pv not in lspv_list.keys():
            module.fail_json(msg="Physical volume '%s' doesn't exist." % pv)

        if lspv_list[pv] == 'None':
            # Disk None, looks free.
            # Check if PV is not already in use by Oracle ASM.
            lquerypv_cmd = module.get_bin_path('lquerypv', True)
            rc, current_lquerypv, stderr = module.run_command([lquerypv_cmd, "-h", "/dev/%s" % pv, "20", "10"])
            if rc != 0:
                module.fail_json(msg="Failed executing lquerypv command.", rc=rc, stdout=current_lquerypv, stderr=stderr)

            if 'ORCLDISK' in current_lquerypv:
                module.fail_json("Physical volume '%s' is already used by Oracle ASM." % pv)

            msg = "Physical volume '%s' is ok to be used." % pv
            return True, msg

        # Check if PV is already in use for the same vg.
        elif vg != lspv_list[pv]:
            module.fail_json(msg="Physical volume '%s' is in use by another volume group '%s'." % (pv, lspv_list[pv]))

        msg = "Physical volume '%s' is already used by volume group '%s'." % (pv, lspv_list[pv])
        return False, msg


def _validate_vg(module, vg):
    """
    Check the current state of volume group.

    :param module: Ansible module argument spec.
    :param vg: Volume Group name.
    :return: True (VG in varyon state) or False (VG in varyoff state) or
             None (VG does not exist), message.
    """
    lsvg_cmd = module.get_bin_path('lsvg', True)
    rc, current_active_vgs, err = module.run_command([lsvg_cmd, "-o"])
    if rc != 0:
        module.fail_json(msg="Failed executing '%s' command." % lsvg_cmd)

    rc, current_all_vgs, err = module.run_command([lsvg_cmd])
    if rc != 0:
        module.fail_json(msg="Failed executing '%s' command." % lsvg_cmd)

    if vg in current_all_vgs and vg not in current_active_vgs:
        msg = "Volume group '%s' is in varyoff state." % vg
        return False, msg

    if vg in current_active_vgs:
        msg = "Volume group '%s' is in varyon state." % vg
        return True, msg

    msg = "Volume group '%s' does not exist." % vg
    return None, msg


def create_extend_vg(module, vg, pvs, pp_size, vg_type, force, vg_validation):
    """ Creates or extend a volume group. """

    # Command option parameters.
    force_opt = {
        True: '-f',
        False: ''
    }

    vg_opt = {
        'normal': '',
        'big': '-B',
        'scalable': '-S',
    }

    # Validate if PV are not already in use.
    pv_state, msg = _validate_pv(module, vg, pvs)
    if not pv_state:
        changed = False
        return changed, msg

    vg_state, msg = vg_validation
    if vg_state is False:
        changed = False
        return changed, msg

    elif vg_state is True:
        # Volume group extension.
        changed = True
        msg = ""

        if not module.check_mode:
            extendvg_cmd = module.get_bin_path('extendvg', True)
            rc, output, err = module.run_command([extendvg_cmd, vg] + pvs)
            if rc != 0:
                changed = False
                msg = "Extending volume group '%s' has failed." % vg
                return changed, msg

        msg = "Volume group '%s' extended." % vg
        return changed, msg

    elif vg_state is None:
        # Volume group creation.
        changed = True
        msg = ''

        if not module.check_mode:
            mkvg_cmd = module.get_bin_path('mkvg', True)
            rc, output, err = module.run_command([mkvg_cmd, vg_opt[vg_type], pp_size, force_opt[force], "-y", vg] + pvs)
            if rc != 0:
                changed = False
                msg = "Creating volume group '%s' failed." % vg
                return changed, msg

        msg = "Volume group '%s' created." % vg
        return changed, msg


def reduce_vg(module, vg, pvs, vg_validation):
    vg_state, msg = vg_validation

    if vg_state is False:
        changed = False
        return changed, msg

    elif vg_state is None:
        changed = False
        return changed, msg

    # Define pvs_to_remove (list of physical volumes to be removed).
    if pvs is None:
        # Remove VG if pvs are note informed.
        # Remark: AIX will permit remove only if the VG has not LVs.
        lsvg_cmd = module.get_bin_path('lsvg', True)
        rc, current_pvs, err = module.run_command([lsvg_cmd, "-p", vg])
        if rc != 0:
            module.fail_json(msg="Failing to execute '%s' command." % lsvg_cmd)

        pvs_to_remove = []
        for line in current_pvs.splitlines()[2:]:
            pvs_to_remove.append(line.split()[0])

        reduce_msg = "Volume group '%s' removed." % vg
    else:
        pvs_to_remove = pvs
        reduce_msg = ("Physical volume(s) '%s' removed from Volume group '%s'." % (' '.join(pvs_to_remove), vg))

    # Reduce volume group.
    if len(pvs_to_remove) <= 0:
        changed = False
        msg = "No physical volumes to remove."
        return changed, msg

    changed = True
    msg = ''

    if not module.check_mode:
        reducevg_cmd = module.get_bin_path('reducevg', True)
        rc, stdout, stderr = module.run_command([reducevg_cmd, "-df", vg] + pvs_to_remove)
        if rc != 0:
            module.fail_json(msg="Unable to remove '%s'." % vg, rc=rc, stdout=stdout, stderr=stderr)

    msg = reduce_msg
    return changed, msg


def state_vg(module, vg, state, vg_validation):
    vg_state, msg = vg_validation

    if vg_state is None:
        module.fail_json(msg=msg)

    if state == 'varyon':
        if vg_state is True:
            changed = False
            return changed, msg

        changed = True
        msg = ''
        if not module.check_mode:
            varyonvg_cmd = module.get_bin_path('varyonvg', True)
            rc, varyonvg_out, err = module.run_command([varyonvg_cmd, vg])
            if rc != 0:
                module.fail_json(msg="Command 'varyonvg' failed.", rc=rc, err=err)

        msg = "Varyon volume group %s completed." % vg
        return changed, msg

    elif state == 'varyoff':
        if vg_state is False:
            changed = False
            return changed, msg

        changed = True
        msg = ''

        if not module.check_mode:
            varyonvg_cmd = module.get_bin_path('varyoffvg', True)
            rc, varyonvg_out, stderr = module.run_command([varyonvg_cmd, vg])
            if rc != 0:
                module.fail_json(msg="Command 'varyoffvg' failed.", rc=rc, stdout=varyonvg_out, stderr=stderr)

        msg = "Varyoff volume group %s completed." % vg
        return changed, msg


def main():
    module = AnsibleModule(
        argument_spec=dict(
            force=dict(type='bool', default=False),
            pp_size=dict(type='int'),
            pvs=dict(type='list', elements='str'),
            state=dict(type='str', default='present', choices=['absent', 'present', 'varyoff', 'varyon']),
            vg=dict(type='str', required=True),
            vg_type=dict(type='str', default='normal', choices=['big', 'normal', 'scalable'])
        ),
        supports_check_mode=True,
    )

    force = module.params['force']
    pp_size = module.params['pp_size']
    pvs = module.params['pvs']
    state = module.params['state']
    vg = module.params['vg']
    vg_type = module.params['vg_type']

    if pp_size is None:
        pp_size = ''
    else:
        pp_size = "-s %s" % pp_size

    vg_validation = _validate_vg(module, vg)

    if state == 'present':
        if not pvs:
            changed = False
            msg = "pvs is required to state 'present'."
            module.fail_json(msg=msg)
        else:
            changed, msg = create_extend_vg(module, vg, pvs, pp_size, vg_type, force, vg_validation)

    elif state == 'absent':
        changed, msg = reduce_vg(module, vg, pvs, vg_validation)

    elif state == 'varyon' or state == 'varyoff':
        changed, msg = state_vg(module, vg, state, vg_validation)

    else:
        changed = False
        msg = "Unexpected state"

    module.exit_json(changed=changed, msg=msg, state=state)


if __name__ == '__main__':
    main()
