Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
ansible / purestorage / flasharray / plugins / modules / purefa_host.py
Size: Mime:
#!/usr/bin/python
# -*- coding: utf-8 -*-

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

from __future__ import absolute_import, division, print_function

__metaclass__ = type

ANSIBLE_METADATA = {
    "metadata_version": "1.1",
    "status": ["preview"],
    "supported_by": "community",
}

DOCUMENTATION = r"""
---
module: purefa_host
version_added: '1.0.0'
short_description: Manage hosts on Pure Storage FlashArrays
description:
- Create, delete or modify hosts on Pure Storage FlashArrays.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
notes:
- If specifying C(lun) option ensure host support requested value
options:
  name:
    description:
    - The name of the host.
    - Note that hostnames are case-sensitive however FlashArray hostnames are unique
      and ignore case - you cannot have I(hosta) and I(hostA)
    - Multi-host support available from Purity//FA 6.0.0
      B(***NOTE***) Manual deletion of individual hosts created
      using multi-host will cause idempotency to fail
    - Multi-host support only exists for host creation
    type: str
    required: true
    aliases: [ host ]
  protocol:
    description:
    - Defines the host connection protocol for volumes.
    - DEPRECATED No longer a necessary parameter
    type: str
    choices: [ fc, iscsi, nvme, mixed ]
  rename:
    description:
    - The name to rename to.
    - Note that hostnames are case-sensitive however FlashArray hostnames are unique
      and ignore case - you cannot have I(hosta) and I(hostA)
    type: str
  state:
    description:
    - Define whether the host should exist or not.
    - When removing host all connected volumes will be disconnected.
    type: str
    default: present
    choices: [ absent, present ]
  wwns:
    type: list
    elements: str
    description:
    - List of wwns of the host.
  iqn:
    type: list
    elements: str
    description:
    - List of IQNs of the host.
  nqn:
    type: list
    elements: str
    description:
    - List of NQNs of the host.
  volume:
    type: str
    description:
    - Volume name to map to the host.
  lun:
    description:
    - LUN ID to assign to volume for host. Must be unique.
    - If not provided the ID will be automatically assigned.
    - Range for LUN ID is 1 to 4095.
    type: int
  count:
    description:
    - Number of hosts to be created in a multiple host creation
    - Only supported from Purity//FA v6.0.0 and higher
    type: int
  start:
    description:
    - Number at which to start the multiple host creation index
    - Only supported from Purity//FA v6.0.0 and higher
    type: int
    default: 0
  digits:
    description:
    - Number of digits to use for multiple host count. This
      will pad the index number with zeros where necessary
    - Only supported from Purity//FA v6.0.0 and higher
    - Range is between 1 and 10
    type: int
    default: 1
  suffix:
    description:
    - Suffix string, if required, for multiple host create
    - Host names will be formed as I(<name>#<suffix>), where
      I(#) is a placeholder for the host index
      See associated descriptions
    - Suffix string is optional
    - Only supported from Purity//FA v6.0.0 and higher
    type: str
  personality:
    type: str
    description:
    - Define which operating system the host is. Recommended for
      ActiveCluster integration.
    default: ''
    choices: ['hpux', 'vms', 'aix', 'esxi', 'solaris', 'hitachi-vsp', 'oracle-vm-server', 'delete', '']
  preferred_array:
    type: list
    elements: str
    description:
    - List of preferred arrays in an ActiveCluster environment.
    - To remove existing preferred arrays from the host, specify I(delete).
  target_user:
    type: str
    description:
    - Sets the target user name for CHAP authentication
    - Required with I(target_password)
    - To clear the username/password pair use I(clear) as the password
  target_password:
    type: str
    description:
    - Sets the target password for CHAP authentication
    - Password length between 12 and 255 characters
    - To clear the username/password pair use I(clear) as the password
    - SETTING A PASSWORD IS NON-IDEMPOTENT
  host_user:
    type: str
    description:
    - Sets the host user name for CHAP authentication
    - Required with I(host_password)
    - To clear the username/password pair use I(clear) as the password
  host_password:
    type: str
    description:
    - Sets the host password for CHAP authentication
    - Password length between 12 and 255 characters
    - To clear the username/password pair use I(clear) as the password
    - SETTING A PASSWORD IS NON-IDEMPOTENT
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""

EXAMPLES = r"""
- name: Create new AIX host
  purestorage.flasharray.purefa_host:
    name: foo
    personality: aix
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Create 10 hosts with index starting at 10 but padded with 3 digits
  purestorage.flasharray.purefa_host:
    name: foo
    personality: vms
    suffix: bar
    count: 10
    start: 10
    digits: 3
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592
    state: present

- name: Rename host foo to bar
  purestorage.flasharray.purefa_host:
    name: foo
    rename: bar
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Delete host
  purestorage.flasharray.purefa_host:
    name: foo
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592
    state: absent

- name: Make host bar with wwn ports
  purestorage.flasharray.purefa_host:
    name: bar
    wwns:
    - 00:00:00:00:00:00:00
    - 11:11:11:11:11:11:11
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Make host bar with iSCSI ports
  purestorage.flasharray.purefa_host:
    name: bar
    iqn:
    - iqn.1994-05.com.redhat:7d366003913
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Make host bar with NVMe ports
  purestorage.flasharray.purefa_host:
    name: bar
    nqn:
    - nqn.2014-08.com.vendor:nvme:nvm-subsystem-sn-d78432
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Make mixed protocol host
  purestorage.flasharray.purefa_host:
    name: bar
    nqn:
    - nqn.2014-08.com.vendor:nvme:nvm-subsystem-sn-d78432
    iqn:
    - iqn.1994-05.com.redhat:7d366003914
    wwns:
    - 00:00:00:00:00:00:01
    - 11:11:11:11:11:11:12
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Map host foo to volume bar as LUN ID 12
  purestorage.flasharray.purefa_host:
    name: foo
    volume: bar
    lun: 12
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Disconnect volume bar from host foo
  purestorage.flasharray.purefa_host:
    name: foo
    volume: bar
    state: absent
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Add preferred arrays to host foo
  purestorage.flasharray.purefa_host:
    name: foo
    preferred_array:
    - array1
    - array2
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Delete preferred arrays from host foo
  purestorage.flasharray.purefa_host:
    name: foo
    preferred_array: delete
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Delete exisitng WWNs from host foo (does not delete host object)
  purestorage.flasharray.purefa_host:
    name: foo
    wwns: ""
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Set CHAP target and host username/password pairs
  purestorage.flasharray.purefa_host:
    name: foo
    target_user: user1
    target_password: passwrodpassword
    host_user: user2
    host_password: passwrodpassword
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592

- name: Delete CHAP target and host username/password pairs
  purestorage.flasharray.purefa_host:
    name: foo
    target_user: user
    target_password: clear
    host_user: user
    host_password: clear
    fa_url: 10.10.10.2
    api_token: e31060a7-21fc-e277-6240-25983c6c4592
"""

RETURN = r"""
"""

HAS_PURESTORAGE = True
try:
    from pypureclient import flasharray
except ImportError:
    HAS_PURESTORAGE = False

import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
    get_array,
    get_system,
    purefa_argument_spec,
)


AC_REQUIRED_API_VERSION = "1.14"
PREFERRED_ARRAY_API_VERSION = "1.15"
NVME_API_VERSION = "1.16"
MULTI_HOST_VERSION = "2.2"


def _is_cbs(array, is_cbs=False):
    """Is the selected array a Cloud Block Store"""
    model = array.get(controllers=True)[0]["model"]
    is_cbs = bool("CBS" in model)
    return is_cbs


def _set_host_initiators(module, array):
    """Set host initiators."""
    if module.params["nqn"]:
        try:
            array.set_host(module.params["name"], nqnlist=module.params["nqn"])
        except Exception:
            module.fail_json(msg="Setting of NVMe NQN failed.")
    if module.params["iqn"]:
        try:
            array.set_host(module.params["name"], iqnlist=module.params["iqn"])
        except Exception:
            module.fail_json(msg="Setting of iSCSI IQN failed.")
    if module.params["wwns"]:
        try:
            array.set_host(module.params["name"], wwnlist=module.params["wwns"])
        except Exception:
            module.fail_json(msg="Setting of FC WWNs failed.")


def _update_host_initiators(module, array, answer=False):
    """Change host initiator if iscsi or nvme or add new FC WWNs"""
    if module.params["nqn"]:
        current_nqn = array.get_host(module.params["name"])["nqn"]
        if module.params["nqn"] != [""]:
            if current_nqn != module.params["nqn"]:
                answer = True
                if not module.check_mode:
                    try:
                        array.set_host(
                            module.params["name"], nqnlist=module.params["nqn"]
                        )
                    except Exception:
                        module.fail_json(msg="Change of NVMe NQN failed.")
        elif current_nqn:
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(module.params["name"], remnqnlist=current_nqn)
                except Exception:
                    module.fail_json(msg="Removal of NVMe NQN failed.")
    if module.params["iqn"]:
        current_iqn = array.get_host(module.params["name"])["iqn"]
        if module.params["iqn"] != [""]:
            if current_iqn != module.params["iqn"]:
                answer = True
                if not module.check_mode:
                    try:
                        array.set_host(
                            module.params["name"], iqnlist=module.params["iqn"]
                        )
                    except Exception:
                        module.fail_json(msg="Change of iSCSI IQN failed.")
        elif current_iqn:
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(module.params["name"], remiqnlist=current_iqn)
                except Exception:
                    module.fail_json(msg="Removal of iSCSI IQN failed.")
    if module.params["wwns"]:
        module.params["wwns"] = [wwn.replace(":", "") for wwn in module.params["wwns"]]
        module.params["wwns"] = [wwn.upper() for wwn in module.params["wwns"]]
        current_wwn = array.get_host(module.params["name"])["wwn"]
        if module.params["wwns"] != [""]:
            if current_wwn != module.params["wwns"]:
                answer = True
                if not module.check_mode:
                    try:
                        array.set_host(
                            module.params["name"], wwnlist=module.params["wwns"]
                        )
                    except Exception:
                        module.fail_json(msg="FC WWN change failed.")
        elif current_wwn:
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(module.params["name"], remwwnlist=current_wwn)
                except Exception:
                    module.fail_json(msg="Removal of all FC WWNs failed.")
    return answer


def _connect_new_volume(module, array, answer=False):
    """Connect volume to host"""
    api_version = array._list_available_rest_versions()
    if AC_REQUIRED_API_VERSION in api_version and module.params["lun"]:
        answer = True
        if not module.check_mode:
            try:
                array.connect_host(
                    module.params["name"],
                    module.params["volume"],
                    lun=module.params["lun"],
                )
            except Exception:
                module.fail_json(
                    msg="LUN ID {0} invalid. Check for duplicate LUN IDs.".format(
                        module.params["lun"]
                    )
                )
    else:
        answer = True
        if not module.check_mode:
            array.connect_host(module.params["name"], module.params["volume"])
    return answer


def _disconnect_volume(module, array, answer=False):
    """Disconnect volume from host"""
    answer = True
    if not module.check_mode:
        try:
            array.disconnect_host(module.params["name"], module.params["volume"])
        except Exception:
            module.fail_json(
                msg="Failed to disconnect volume {0}".format(module.params["volume"])
            )
    return answer


def _set_host_personality(module, array):
    """Set host personality. Only called when supported"""
    if module.params["personality"] != "delete":
        array.set_host(module.params["name"], personality=module.params["personality"])
    else:
        array.set_host(module.params["name"], personality="")


def _set_preferred_array(module, array):
    """Set preferred array list. Only called when supported"""
    if module.params["preferred_array"] != ["delete"]:
        array.set_host(
            module.params["name"], preferred_array=module.params["preferred_array"]
        )
    else:
        array.set_host(module.params["name"], personality="")


def _set_chap_security(module, array):
    """Set CHAP usernames and passwords"""
    pattern = re.compile("[^ ]{12,255}")
    if module.params["host_user"]:
        if not pattern.match(module.params["host_password"]):
            module.fail_json(
                msg="host_password must contain a minimum of 12 and a maximum of 255 characters"
            )
        try:
            array.set_host(
                module.params["name"],
                host_user=module.params["host_user"],
                host_password=module.params["host_password"],
            )
        except Exception:
            module.params(msg="Failed to set CHAP host username and password")
    if module.params["target_user"]:
        if not pattern.match(module.params["target_password"]):
            module.fail_json(
                msg="target_password must contain a minimum of 12 and a maximum of 255 characters"
            )
        try:
            array.set_host(
                module.params["name"],
                target_user=module.params["target_user"],
                target_password=module.params["target_password"],
            )
        except Exception:
            module.params(msg="Failed to set CHAP target username and password")


def _update_chap_security(module, array, answer=False):
    """Change CHAP usernames and passwords"""
    pattern = re.compile("[^ ]{12,255}")
    chap = array.get_host(module.params["name"], chap=True)
    if module.params["host_user"]:
        if module.params["host_password"] == "clear":
            if chap["host_user"]:
                answer = True
                if not module.check_mode:
                    try:
                        array.set_host(module.params["name"], host_user="")
                    except Exception:
                        module.params(
                            msg="Failed to clear CHAP host username and password"
                        )
        else:
            if not pattern.match(module.params["host_password"]):
                module.fail_json(
                    msg="host_password must contain a minimum of 12 and a maximum of 255 characters"
                )
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(
                        module.params["name"],
                        host_user=module.params["host_user"],
                        host_password=module.params["host_password"],
                    )
                except Exception:
                    module.params(msg="Failed to set CHAP host username and password")
    if module.params["target_user"]:
        if module.params["target_password"] == "clear":
            if chap["target_user"]:
                answer = True
                if not module.check_mode:
                    try:
                        array.set_host(module.params["name"], target_user="")
                    except Exception:
                        module.params(
                            msg="Failed to clear CHAP target username and password"
                        )
        else:
            if not pattern.match(module.params["target_password"]):
                module.fail_json(
                    msg="target_password must contain a minimum of 12 and a maximum of 255 characters"
                )
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(
                        module.params["name"],
                        target_user=module.params["target_user"],
                        target_password=module.params["target_password"],
                    )
                except Exception:
                    module.params(msg="Failed to set CHAP target username and password")
    return answer


def _update_host_personality(module, array, answer=False):
    """Change host personality. Only called when supported"""
    personality = array.get_host(module.params["name"], personality=True)["personality"]
    if personality is None and module.params["personality"] != "delete":
        answer = True
        if not module.check_mode:
            try:
                array.set_host(
                    module.params["name"], personality=module.params["personality"]
                )
            except Exception:
                module.fail_json(msg="Personality setting failed.")
    if personality is not None:
        if module.params["personality"] == "delete":
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(module.params["name"], personality="")
                except Exception:
                    module.fail_json(msg="Personality deletion failed.")
        elif personality != module.params["personality"]:
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(
                        module.params["name"], personality=module.params["personality"]
                    )
                except Exception:
                    module.fail_json(msg="Personality change failed.")
    return answer


def _update_preferred_array(module, array, answer=False):
    """Update existing preferred array list. Only called when supported"""
    preferred_array = array.get_host(module.params["name"], preferred_array=True)[
        "preferred_array"
    ]
    if preferred_array == [] and module.params["preferred_array"] != ["delete"]:
        answer = True
        if not module.check_mode:
            try:
                array.set_host(
                    module.params["name"],
                    preferred_array=module.params["preferred_array"],
                )
            except Exception:
                module.fail_json(
                    msg="Preferred array list creation failed for {0}.".format(
                        module.params["name"]
                    )
                )
    elif preferred_array != []:
        if module.params["preferred_array"] == ["delete"]:
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(module.params["name"], preferred_array=[])
                except Exception:
                    module.fail_json(
                        msg="Preferred array list deletion failed for {0}.".format(
                            module.params["name"]
                        )
                    )
        elif preferred_array != module.params["preferred_array"]:
            answer = True
            if not module.check_mode:
                try:
                    array.set_host(
                        module.params["name"],
                        preferred_array=module.params["preferred_array"],
                    )
                except Exception:
                    module.fail_json(
                        msg="Preferred array list change failed for {0}.".format(
                            module.params["name"]
                        )
                    )
    return answer


def get_multi_hosts(module):
    """Return True is all hosts exist"""
    hosts = []
    array = get_array(module)
    for host_num in range(
        module.params["start"], module.params["count"] + module.params["start"]
    ):
        if module.params["suffix"]:
            hosts.append(
                module.params["name"]
                + str(host_num).zfill(module.params["digits"])
                + module.params["suffix"]
            )
        else:
            hosts.append(
                module.params["name"] + str(host_num).zfill(module.params["digits"])
            )
    return bool(array.get_hosts(names=hosts).status_code == 200)


def get_host(module, array):
    """Return host or None"""
    host = None
    for hst in array.list_hosts():
        if hst["name"].lower() == module.params["name"].lower():
            module.params["name"] = hst["name"]
            host = hst
            break
    return host


def rename_exists(module, array):
    """Determine if rename target already exists"""
    exists = False
    for hst in array.list_hosts():
        if hst["name"].lower() == module.params["rename"].lower():
            exists = True
            break
    return exists


def make_multi_hosts(module):
    """Create multiple hosts"""
    changed = True
    if not module.check_mode:
        hosts = []
        array = get_array(module)
        for host_num in range(
            module.params["start"], module.params["count"] + module.params["start"]
        ):
            if module.params["suffix"]:
                hosts.append(
                    module.params["name"]
                    + str(host_num).zfill(module.params["digits"])
                    + module.params["suffix"]
                )
            else:
                hosts.append(
                    module.params["name"] + str(host_num).zfill(module.params["digits"])
                )
        if module.params["personality"]:
            host = flasharray.HostPost(personality=module.params["personality"])
        else:
            host = flasharray.HostPost()
        res = array.post_hosts(names=hosts, host=host)
        if res.status_code != 200:
            module.fail_json(
                msg="Multi-Host {0}#{1} creation failed: {2}".format(
                    module.params["name"],
                    module.params["suffix"],
                    res.errors[0].message,
                )
            )
    module.exit_json(changed=changed)


def make_host(module, array):
    """Create a new host"""
    changed = True
    if not module.check_mode:
        try:
            array.create_host(module.params["name"])
        except Exception:
            module.fail_json(
                msg="Host {0} creation failed.".format(module.params["name"])
            )
        try:
            _set_host_initiators(module, array)
            api_version = array._list_available_rest_versions()
            if AC_REQUIRED_API_VERSION in api_version and module.params["personality"]:
                _set_host_personality(module, array)
            if (
                PREFERRED_ARRAY_API_VERSION in api_version
                and module.params["preferred_array"]
            ):
                _set_preferred_array(module, array)
            if module.params["host_user"] or module.params["target_user"]:
                _set_chap_security(module, array)
            if module.params["volume"]:
                if module.params["lun"]:
                    array.connect_host(
                        module.params["name"],
                        module.params["volume"],
                        lun=module.params["lun"],
                    )
                else:
                    array.connect_host(module.params["name"], module.params["volume"])
        except Exception:
            module.fail_json(
                msg="Host {0} configuration failed.".format(module.params["name"])
            )
    module.exit_json(changed=changed)


def update_host(module, array):
    """Modify a host"""
    changed = False
    renamed = False
    if module.params["state"] == "present":
        if module.params["rename"]:
            if not rename_exists(module, array):
                changed = True
                if not module.check_mode:
                    try:
                        array.rename_host(
                            module.params["name"], module.params["rename"]
                        )
                        module.params["name"] = module.params["rename"]
                        renamed = True
                    except Exception:
                        module.fail_json(
                            msg="Rename to {0} failed.".format(module.params["rename"])
                        )
            else:
                module.warn(
                    "Rename failed. Target hostname {0} already exists. "
                    "Continuing with any other changes...".format(
                        module.params["rename"]
                    )
                )
        init_changed = vol_changed = pers_changed = pref_changed = chap_changed = False
        volumes = array.list_host_connections(module.params["name"])
        if module.params["iqn"] or module.params["wwns"] or module.params["nqn"]:
            init_changed = _update_host_initiators(module, array)
        if module.params["volume"]:
            current_vols = [vol["vol"] for vol in volumes]
            if not module.params["volume"] in current_vols:
                vol_changed = _connect_new_volume(module, array)
        api_version = array._list_available_rest_versions()
        if AC_REQUIRED_API_VERSION in api_version:
            if module.params["personality"]:
                pers_changed = _update_host_personality(module, array)
        if PREFERRED_ARRAY_API_VERSION in api_version:
            if module.params["preferred_array"]:
                pref_changed = _update_preferred_array(module, array)
        if module.params["target_user"] or module.params["host_user"]:
            chap_changed = _update_chap_security(module, array)
        changed = (
            init_changed
            or vol_changed
            or pers_changed
            or pref_changed
            or chap_changed
            or renamed
        )
    else:
        if module.params["volume"]:
            volumes = array.list_host_connections(module.params["name"])
            current_vols = [vol["vol"] for vol in volumes]
            if module.params["volume"] in current_vols:
                vol_changed = _disconnect_volume(module, array)
            changed = vol_changed
    module.exit_json(changed=changed)


def delete_host(module, array):
    """Delete a host"""
    changed = True
    if not module.check_mode:
        try:
            hgroup = array.get_host(module.params["name"])["hgroup"]
            if hgroup is not None:
                array.set_hgroup(hgroup, remhostlist=[module.params["name"]])
            for vol in array.list_host_connections(module.params["name"]):
                array.disconnect_host(module.params["name"], vol["vol"])
            array.delete_host(module.params["name"])
        except Exception:
            module.fail_json(
                msg="Host {0} deletion failed".format(module.params["name"])
            )
    module.exit_json(changed=changed)


def main():
    argument_spec = purefa_argument_spec()
    argument_spec.update(
        dict(
            name=dict(type="str", required=True, aliases=["host"]),
            state=dict(type="str", default="present", choices=["absent", "present"]),
            protocol=dict(
                type="str",
                choices=["fc", "iscsi", "nvme", "mixed"],
                removed_from_collection="1.13",
                removed_in_version="2.0.0",
            ),
            nqn=dict(type="list", elements="str"),
            iqn=dict(type="list", elements="str"),
            wwns=dict(type="list", elements="str"),
            host_password=dict(type="str", no_log=True),
            host_user=dict(type="str"),
            target_password=dict(type="str", no_log=True),
            target_user=dict(type="str"),
            volume=dict(type="str"),
            rename=dict(type="str"),
            lun=dict(type="int"),
            count=dict(type="int"),
            start=dict(type="int", default=0),
            digits=dict(type="int", default=1),
            suffix=dict(type="str"),
            personality=dict(
                type="str",
                default="",
                choices=[
                    "hpux",
                    "vms",
                    "aix",
                    "esxi",
                    "solaris",
                    "hitachi-vsp",
                    "oracle-vm-server",
                    "delete",
                    "",
                ],
            ),
            preferred_array=dict(type="list", elements="str"),
        )
    )

    required_together = [
        ["host_password", "host_user"],
        ["target_password", "target_user"],
    ]

    module = AnsibleModule(
        argument_spec, supports_check_mode=True, required_together=required_together
    )

    array = get_system(module)
    module.params["name"] = module.params["name"].lower()
    pattern = re.compile("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$")
    if module.params["rename"]:
        module.params["rename"] = module.params["rename"].lower()
        if not pattern.match(module.params["rename"]):
            module.fail_json(
                msg="Rename value {0} does not conform to naming convention".format(
                    module.params["rename"]
                )
            )
    if not pattern.match(module.params["name"]):
        module.fail_json(
            msg="Host name {0} does not conform to naming convention".format(
                module.params["name"]
            )
        )
    if _is_cbs(array):
        if module.params["wwns"] or module.params["nqn"]:
            module.fail_json(msg="Cloud Block Store only supports iSCSI as a protocol")
    api_version = array._list_available_rest_versions()
    if module.params["nqn"] is not None and NVME_API_VERSION not in api_version:
        module.fail_json(msg="NVMe protocol not supported. Please upgrade your array.")
    state = module.params["state"]
    if module.params["suffix"]:
        suffix_len = len(module.params["suffix"])
    else:
        suffix_len = 0
    if module.params["count"]:
        if not HAS_PURESTORAGE:
            module.fail_json(
                msg="py-pure-client sdk is required to support 'count' parameter"
            )
        if MULTI_HOST_VERSION not in api_version:
            module.fail_json(
                msg="'count' parameter is not supported until Purity//FA 6.0.0 or higher"
            )
        if module.params["digits"] and module.params["digits"] not in range(1, 10):
            module.fail_json(msg="'digits' must be in the range of 1 to 10")
        if module.params["start"] < 0:
            module.fail_json(msg="'start' must be a positive number")
        if not pattern.match(module.params["name"]):
            module.fail_json(
                msg="Host name pattern {0} does not conform to naming convention".format(
                    module.params["name"]
                )
            )
        elif module.params["suffix"] and not pattern.match(module.params["suffix"]):
            module.fail_json(
                msg="Suffix pattern {0} does not conform to naming convention".format(
                    module.params["suffix"]
                )
            )
        elif (
            len(module.params["name"])
            + max(
                len(str(module.params["count"] + module.params["start"])),
                module.params["digits"],
            )
            + suffix_len
            > 63
        ):
            module.fail_json(msg="Host name length exceeds maximum allowed")
        host = get_multi_hosts(module)
        if not host and state == "present":
            make_multi_hosts(module)
    else:
        host = get_host(module, array)
        if module.params["lun"] and not 1 <= module.params["lun"] <= 4095:
            module.fail_json(
                msg="LUN ID of {0} is out of range (1 to 4095)".format(
                    module.params["lun"]
                )
            )
        if module.params["volume"]:
            try:
                array.get_volume(module.params["volume"])
            except Exception:
                module.fail_json(
                    msg="Volume {0} not found".format(module.params["volume"])
                )
        if module.params["preferred_array"]:
            try:
                if module.params["preferred_array"] != ["delete"]:
                    all_connected_arrays = array.list_array_connections()
                    if not all_connected_arrays:
                        module.fail_json(
                            msg="No target arrays connected to source array - preferred arrays not possible."
                        )
                    else:
                        current_arrays = [array.get()["array_name"]]
                        api_version = array._list_available_rest_versions()
                        if NVME_API_VERSION in api_version:
                            for current_array in range(0, len(all_connected_arrays)):
                                if (
                                    all_connected_arrays[current_array]["type"]
                                    == "sync-replication"
                                ):
                                    current_arrays.append(
                                        all_connected_arrays[current_array][
                                            "array_name"
                                        ]
                                    )
                        else:
                            for current_array in range(0, len(all_connected_arrays)):
                                if all_connected_arrays[current_array]["type"] == [
                                    "sync-replication"
                                ]:
                                    current_arrays.append(
                                        all_connected_arrays[current_array][
                                            "array_name"
                                        ]
                                    )
                    for array_to_connect in range(
                        0, len(module.params["preferred_array"])
                    ):
                        if (
                            module.params["preferred_array"][array_to_connect]
                            not in current_arrays
                        ):
                            module.fail_json(
                                msg="Array {0} is not a synchronously connected array.".format(
                                    module.params["preferred_array"][array_to_connect]
                                )
                            )
            except Exception:
                module.fail_json(msg="Failed to get existing array connections.")

        if host is None and state == "present" and not module.params["rename"]:
            make_host(module, array)
        elif host is None and state == "present" and module.params["rename"]:
            module.exit_json(changed=False)
        elif host and state == "present":
            update_host(module, array)
        elif host and state == "absent" and module.params["volume"]:
            update_host(module, array)
        elif host and state == "absent":
            delete_host(module, array)
        elif host is None and state == "absent":
            module.exit_json(changed=False)

    module.exit_json(changed=False)


if __name__ == "__main__":
    main()