Dre4m Shell
Server IP : 85.214.239.14  /  Your IP : 3.15.198.69
Web Server : Apache/2.4.62 (Debian)
System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64
User : www-data ( 33)
PHP Version : 7.4.18
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
MySQL : OFF  |  cURL : OFF  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : OFF
Directory :  /lib/python3/dist-packages/ansible_collections/purestorage/fusion/plugins/modules/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME SHELL ]     

Current File : /lib/python3/dist-packages/ansible_collections/purestorage/fusion/plugins/modules//fusion_volume.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

# (c) 2023, Simon Dodsley (simon@purestorage.com), Jan Kodera (jkodera@purestorage.com)
# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function

__metaclass__ = type

DOCUMENTATION = r"""
---
module: fusion_volume
version_added: '1.0.0'
short_description:  Manage volumes in Pure Storage Fusion
description:
- Create, update or delete a volume in Pure Storage Fusion.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
notes:
- Supports C(check mode).
options:
  name:
    description:
    - The name of the volume.
    type: str
    required: true
  display_name:
    description:
    - The human name of the volume.
    - If not provided, defaults to I(name).
    type: str
  state:
    description:
    - Define whether the volume should exist or not.
    type: str
    default: present
    choices: [ absent, present ]
  tenant:
    description:
    - The name of the tenant.
    type: str
    required: true
  tenant_space:
    description:
    - The name of the tenant space.
    type: str
    required: true
  eradicate:
    description:
    - "Wipes the volume instead of a soft delete if true. Must be used with `state: absent`."
    type: bool
    default: false
  size:
    description:
    - Volume size in M, G, T or P units.
    type: str
  storage_class:
    description:
    - The name of the storage class.
    type: str
  placement_group:
    description:
    - The name of the placement group.
    type: str
  protection_policy:
    description:
    - The name of the protection policy.
    type: str
  host_access_policies:
    description:
    - 'A list of host access policies to connect the volume to.
        To clear, assign empty list: host_access_policies: []'
    type: list
    elements: str
  rename:
    description:
    - New name for volume.
    type: str
extends_documentation_fragment:
- purestorage.fusion.purestorage.fusion
"""

EXAMPLES = r"""
- name: Create new volume named foo in storage_class fred
  purestorage.fusion.fusion_volume:
    name: foo
    storage_class: fred
    size: 1T
    tenant: test
    tenant_space: space_1
    state: present
    issuer_id: key_name
    private_key_file: "az-admin-private-key.pem"

- name: Extend the size of an existing volume named foo
  purestorage.fusion.fusion_volume:
    name: foo
    size: 2T
    tenant: test
    tenant_space: space_1
    state: present
    issuer_id: key_name
    private_key_file: "az-admin-private-key.pem"

- name: Delete volume named foo
  purestorage.fusion.fusion_volume:
    name: foo
    tenant: test
    tenant_space: space_1
    state: absent
    issuer_id: key_name
    private_key_file: "az-admin-private-key.pem"
"""

RETURN = r"""
"""

try:
    import fusion as purefusion
except ImportError:
    pass

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
    fusion_argument_spec,
)
from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import (
    parse_number_with_metric_suffix,
)
from ansible_collections.purestorage.fusion.plugins.module_utils.startup import (
    setup_fusion,
)
from ansible_collections.purestorage.fusion.plugins.module_utils.operations import (
    await_operation,
)


def get_volume(module, fusion):
    """Return Volume or None"""
    volume_api_instance = purefusion.VolumesApi(fusion)
    try:
        return volume_api_instance.get_volume(
            tenant_name=module.params["tenant"],
            tenant_space_name=module.params["tenant_space"],
            volume_name=module.params["name"],
        )
    except purefusion.rest.ApiException:
        return None


def get_wanted_haps(module):
    """Return set of host access policies to assign"""
    if not module.params["host_access_policies"]:
        return set()
    # looks like yaml parsing can leave in some spaces if coma-delimited .so strip() the names
    return set([hap.strip() for hap in module.params["host_access_policies"]])


def extract_current_haps(volume):
    """Return set of host access policies that volume currently has"""
    if not volume.host_access_policies:
        return set()
    return set([hap.name for hap in volume.host_access_policies])


def create_volume(module, fusion):
    """Create Volume"""

    size = parse_number_with_metric_suffix(module, module.params["size"])

    if not module.check_mode:
        display_name = module.params["display_name"] or module.params["name"]
        volume_api_instance = purefusion.VolumesApi(fusion)
        volume = purefusion.VolumePost(
            size=size,
            storage_class=module.params["storage_class"],
            placement_group=module.params["placement_group"],
            name=module.params["name"],
            display_name=display_name,
            protection_policy=module.params["protection_policy"],
        )
        op = volume_api_instance.create_volume(
            volume,
            tenant_name=module.params["tenant"],
            tenant_space_name=module.params["tenant_space"],
        )
        await_operation(fusion, op)

    return True


def update_host_access_policies(module, current, patches):
    wanted = module.params
    # 'wanted[...] is not None' to differentiate between empty list and no list
    if wanted["host_access_policies"] is not None:
        current_haps = extract_current_haps(current)
        wanted_haps = get_wanted_haps(module)
        if wanted_haps != current_haps:
            patch = purefusion.VolumePatch(
                host_access_policies=purefusion.NullableString(",".join(wanted_haps))
            )
            patches.append(patch)


def update_destroyed(module, current, patches):
    wanted = module.params
    destroyed = wanted["state"] != "present"
    if destroyed != current.destroyed:
        patch = purefusion.VolumePatch(destroyed=purefusion.NullableBoolean(destroyed))
        patches.append(patch)
        if destroyed and not module.params["eradicate"]:
            module.warn(
                (
                    "Volume '{0}' is being soft deleted to prevent data loss, "
                    "if you want to wipe it immediately to reclaim used space, add 'eradicate: true'"
                ).format(current.name)
            )


def update_display_name(module, current, patches):
    wanted = module.params
    if wanted["display_name"] and wanted["display_name"] != current.display_name:
        patch = purefusion.VolumePatch(
            display_name=purefusion.NullableString(wanted["display_name"])
        )
        patches.append(patch)


def update_storage_class(module, current, patches):
    wanted = module.params
    if (
        wanted["storage_class"]
        and wanted["storage_class"] != current.storage_class.name
    ):
        patch = purefusion.VolumePatch(
            storage_class=purefusion.NullableString(wanted["storage_class"])
        )
        patches.append(patch)


def update_placement_group(module, current, patches):
    wanted = module.params
    if (
        wanted["placement_group"]
        and wanted["placement_group"] != current.placement_group.name
    ):
        patch = purefusion.VolumePatch(
            placement_group=purefusion.NullableString(wanted["placement_group"])
        )
        patches.append(patch)


def update_size(module, current, patches):
    wanted = module.params
    if wanted["size"]:
        wanted_size = parse_number_with_metric_suffix(module, wanted["size"])
        if wanted_size != current.size:
            patch = purefusion.VolumePatch(size=purefusion.NullableSize(wanted_size))
            patches.append(patch)


def update_protection_policy(module, current, patches):
    wanted = module.params
    current_policy = current.protection_policy.name if current.protection_policy else ""
    if (
        wanted["protection_policy"] is not None
        and wanted["protection_policy"] != current_policy
    ):
        patch = purefusion.VolumePatch(
            protection_policy=purefusion.NullableString(wanted["protection_policy"])
        )
        patches.append(patch)


def apply_patches(module, fusion, patches):
    volume_api_instance = purefusion.VolumesApi(fusion)
    for patch in patches:
        op = volume_api_instance.update_volume(
            patch,
            volume_name=module.params["name"],
            tenant_name=module.params["tenant"],
            tenant_space_name=module.params["tenant_space"],
        )
        await_operation(fusion, op)


def update_volume(module, fusion):
    """Update Volume size, placement group, protection policy, storage class, HAPs"""
    current = get_volume(module, fusion)
    patches = []

    if not current:
        # cannot update nonexistent volume
        # Note for check mode: the reasons this codepath is ran in check mode
        # is to catch any argument errors and to compute 'changed'. Basically
        # all argument checks are kept in validate_arguments() to filter the
        # first part. The second part MAY diverge flow from the real run here if
        # create_volume() created the volume and update was then run to update
        # its properties. HOWEVER we don't really care in that case because
        # create_volume() already sets 'changed' to true, so any 'changed'
        # result from update_volume() would not change it.
        return False

    # volumes with 'destroyed' flag are kinda special because we can't change
    # most of their properties while in this state, so we need to set it last
    # and unset it first if changed, respectively
    if module.params["state"] == "present":
        update_destroyed(module, current, patches)
        update_size(module, current, patches)
        update_protection_policy(module, current, patches)
        update_display_name(module, current, patches)
        update_storage_class(module, current, patches)
        update_placement_group(module, current, patches)
        update_host_access_policies(module, current, patches)
    elif module.params["state"] == "absent" and not current.destroyed:
        update_size(module, current, patches)
        update_protection_policy(module, current, patches)
        update_display_name(module, current, patches)
        update_storage_class(module, current, patches)
        update_placement_group(module, current, patches)
        update_host_access_policies(module, current, patches)
        update_destroyed(module, current, patches)

    if not module.check_mode:
        apply_patches(module, fusion, patches)

    changed = len(patches) != 0
    return changed


def eradicate_volume(module, fusion):
    """Eradicate Volume"""
    current = get_volume(module, fusion)
    if module.check_mode:
        return current or module.params["state"] == "present"
    if not current:
        return False

    # update_volume() should be called before eradicate=True and it should
    # ensure the volume is destroyed and HAPs are unassigned
    if not current.destroyed or current.host_access_policies:
        module.fail_json(
            msg="BUG: inconsistent state, eradicate_volume() cannot be called with current.destroyed=False or any host_access_policies"
        )

    volume_api_instance = purefusion.VolumesApi(fusion)
    op = volume_api_instance.delete_volume(
        volume_name=module.params["name"],
        tenant_name=module.params["tenant"],
        tenant_space_name=module.params["tenant_space"],
    )
    await_operation(fusion, op)

    return True


def validate_arguments(module, volume):
    """Validates most argument conditions and possible unacceptable argument combinations"""
    state = module.params["state"]

    if state == "present" and not volume:
        module.fail_on_missing_params(["placement_group", "storage_class", "size"])

    if module.params["state"] == "absent" and (
        module.params["host_access_policies"]
        or (volume and volume.host_access_policies)
    ):
        module.fail_json(
            msg=(
                "Volume must have no host access policies when destroyed, either revert the delete "
                "by setting 'state: present' or remove all HAPs by 'host_access_policies: []'"
            )
        )

    if state == "present" and module.params["eradicate"]:
        module.fail_json(
            msg="'eradicate: true' cannot be used together with 'state: present'"
        )

    if module.params["size"]:
        size = parse_number_with_metric_suffix(module, module.params["size"])
        if size < 1048576 or size > 4503599627370496:  # 1MB to 4PB
            module.fail_json(
                msg="Size is not within the required range, size must be between 1MB and 4PB"
            )


def main():
    """Main code"""
    argument_spec = fusion_argument_spec()
    deprecated_hosts = dict(
        name="hosts", date="2023-07-26", collection_name="purefusion.fusion"
    )
    argument_spec.update(
        dict(
            name=dict(type="str", required=True),
            display_name=dict(type="str"),
            rename=dict(
                type="str",
                removed_at_date="2023-07-26",
                removed_from_collection="purestorage.fusion",
            ),
            tenant=dict(type="str", required=True),
            tenant_space=dict(type="str", required=True),
            placement_group=dict(type="str"),
            storage_class=dict(type="str"),
            protection_policy=dict(type="str"),
            host_access_policies=dict(
                type="list", elements="str", deprecated_aliases=[deprecated_hosts]
            ),
            eradicate=dict(type="bool", default=False),
            state=dict(type="str", default="present", choices=["absent", "present"]),
            size=dict(type="str"),
        )
    )

    required_by = {
        "placement_group": "storage_class",
    }

    module = AnsibleModule(
        argument_spec,
        required_by=required_by,
        supports_check_mode=True,
    )
    fusion = setup_fusion(module)

    state = module.params["state"]

    volume = get_volume(module, fusion)

    validate_arguments(module, volume)

    if state == "absent" and not volume:
        module.exit_json(changed=False)

    changed = False
    if state == "present" and not volume:
        changed = changed | create_volume(module, fusion)
    # volume might exist even if soft-deleted, so we still have to update it
    changed = changed | update_volume(module, fusion)
    if module.params["eradicate"]:
        changed = changed | eradicate_volume(module, fusion)

    module.exit_json(changed=changed)


if __name__ == "__main__":
    main()

Anon7 - 2022
AnonSec Team