---
# Test code for the netapp_e_host module
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

- name: NetApp Test Host module
  fail:
    msg: 'Please define netapp_e_api_username, netapp_e_api_password, netapp_e_api_host, and netapp_e_ssid.'
  when: netapp_e_api_username is undefined or netapp_e_api_password is undefined or
        netapp_e_api_host is undefined or netapp_e_ssid is undefined
  vars:
    gather_facts: yes
    credentials: &creds
      api_url: "https://{{ netapp_e_api_host }}/devmgr/v2"
      api_username: "{{ netapp_e_api_username }}"
      api_password: "{{ netapp_e_api_password }}"
      ssid: "{{ netapp_e_ssid }}"
      validate_certs: no
    hosts: &hosts
      1:
        host_type: 27
        update_host_type: 28
        ports:
          - type: 'iscsi'
            label: 'I1_1'
            port: 'iqn.1996-04.de.suse:01:56f86f9bd1fe-PORT1'
          - type: 'iscsi'
            label: 'I1_2'
            port: 'iqn.1996-04.de.suse:01:56f86f9bd1ff-port1'
        ports2:
          - type: 'iscsi'
            label: 'I1_1'
            port: 'iqn.1996-04.de.suse:01:56f86f9bd1fe-port2'
          - type: 'iscsi'
            label: 'I1_2'
            port: 'iqn.1996-04.de.suse:01:56f86f9bd1ff-port2'
          - type: 'iscsi'
            label: 'I1_3'
            port: 'iqn.1996-04.redhat:01:56f86f9bd1fe-PORT1'
      2:
        host_type: 27
        update_host_type: 28
        ports:
          - type: 'iscsi'
            label: 'I2_1'
            port: 'iqn.1996-04.redhat:01:56f86f9bd1fe-port1'
          - type: 'iscsi'
            label: 'I2_2'
            port: 'iqn.1996-04.redhat:01:56f86f9bd1ff-port1'
        ports2:
          - type: 'iscsi'
            label: 'I2_1'
            port: 'iqn.1996-04.redhat:01:56f86f9bd1fe-port2'
          - type: 'iscsi'
            label: 'I2_2'
            port: 'iqn.1996-04.redhat:01:56f86f9bd1ff-PORT2'


# ********************************************
# *** Ensure jmespath package is installed ***
# ********************************************
# NOTE: jmespath must be installed for the json_query filter
- name: Ensure that jmespath is installed
  pip:
    name: jmespath
    state: present
  register: jmespath
- fail:
    msg: "Restart playbook, the jmespath package was installed and is need for the playbook's execution."
  when: jmespath.changed


# *****************************************
# *** Set credential and host variables ***
# *****************************************
- name: Set hosts variable
  set_fact:
    hosts: *hosts
- name: set credentials
  set_fact:
    credentials: *creds
- name: Show some debug information
  debug:
    msg: "Using user={{ credentials.api_username }} on server={{ credentials.api_url }}."

# *** Remove any existing hosts to set initial state and verify state ***
- name: Remove any existing hosts
  netapp_e_host:
    <<: *creds
    state: absent
    name: "{{ item.key }}"
  with_dict: *hosts

# Retrieve array host definitions
- name: HTTP request for all host definitions from array
  uri:
    url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/hosts"
    user: "{{ credentials.api_username }}"
    password: "{{ credentials.api_password }}"
    body_format: json
    validate_certs: no
  register: result

# Verify that host 1 and 2 host objects do not exist
- name: Collect host side port labels
  set_fact:
    host_labels: "{{ result | json_query('json[*].label') }}"
- name: Assert hosts were removed
  assert:
    that: "'{{ item.key }}' not in host_labels"
    msg: "Host, {{ item.key }}, failed to be removed from the hosts!"
  loop: "{{ lookup('dict', hosts) }}"


# *****************************************************************
# *** Create host definitions and validate host object creation ***
# *****************************************************************
- name: Define hosts
  netapp_e_host:
    <<: *creds
    state: present
    host_type: "{{ item.value.host_type }}"
    ports: "{{ item.value.ports }}"
    name: "{{ item.key }}"
  with_dict: *hosts

# Retrieve array host definitions
- name: https request to validate host definitions were created
  uri:
    url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/hosts"
    user: "{{ credentials.api_username }}"
    password: "{{ credentials.api_password }}"
    body_format: json
    validate_certs: no
  register: result

# Verify hosts were indeed created
- name: Collect host label list
  set_fact:
    hosts_labels: "{{ result | json_query('json[*].label') }}"
- name: Validate hosts were in fact created
  assert:
    that: "'{{ item.key }}' in hosts_labels"
    msg: "host, {{ item.key }}, not define on array!"
  loop: "{{ lookup('dict', hosts) }}"

# *** Update with no state changes results in no changes ***
- name: Redefine hosts, expecting no changes
  netapp_e_host:
    <<: *creds
    state: present
    host_type: "{{ item.value.host_type }}"
    ports: "{{ item.value.ports }}"
    name: "{{ item.key }}"
  with_dict: *hosts
  register: result

# Verify that no changes occurred
- name: Ensure no change occurred
  assert:
    msg: "A change was not detected!"
    that: "not result.changed"


# ***********************************************************************************
# *** Redefine hosts using ports2 host definitions and validate the updated state ***
# ***********************************************************************************
- name: Redefine hosts, expecting changes
  netapp_e_host:
    <<: *creds
    state: present
    host_type: "{{ item.value.host_type }}"
    ports: "{{ item.value.ports2 }}"
    name: "{{ item.key }}"
    force_port: yes
  with_dict: *hosts
  register: result

# Request from the array all host definitions
- name: HTTP request for port information
  uri:
    url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/hosts"
    user: "{{ credentials.api_username }}"
    password: "{{ credentials.api_password }}"
    body_format: json
    validate_certs: no
  register: result

# Compile a list of array host port information for verifying changes
- name: Compile array host port information list
  set_fact:
    tmp: []

# Append each loop to the previous extraction.  Each loop consists of host definitions and the filters will perform
#   the following: grab host side port lists; combine to each list a dictionary containing the host name(label);
#   lastly, convert the zip_longest object into a list
- set_fact:
    tmp: "{{ tmp }} + {{ [item | json_query('hostSidePorts[*]')] |
                          zip_longest([], fillvalue={'host_name': item.label}) | list }}"
  loop: "{{ result.json }}"

# Make new list, port_info, by combining  each list entry's dictionaries into a single dictionary
- name: Create port information list
  set_fact:
    port_info: []
- set_fact:
    port_info: "{{ port_info + [item[0] |combine(item[1])] }}"
  loop: "{{ tmp }}"

# Compile list of expected host port information for verifying changes
- name: Create expected port information list
  set_fact:
    tmp: []

# Append each loop to the previous extraction.  Each loop consists of host definitions and the filters will perform
#   the following: grab host side port lists; combine to each list a dictionary containing the host name(label);
#   lastly, convert the zip_longest object into a list
- set_fact:
    tmp: "{{ tmp }} + {{ [item | json_query('value.ports2[*]')]|
                          zip_longest([], fillvalue={'host_name': item.key|string}) | list }}"
  loop: "{{ lookup('dict', hosts) }}"

# Make new list, expected_port_info, by combining each list entry's dictionaries into a single dictionary
- name: Create expected port information list
  set_fact:
    expected_port_info: []
- set_fact:
    expected_port_info: "{{ expected_port_info + [ item[0] |combine(item[1]) ] }}"
  loop: "{{ tmp }}"

# Verify that each host object has the expected protocol type and address/port
- name: Assert hosts information was updated with new port information
  assert:
    that: "{{ item[0].host_name != item[1].host_name or
              item[0].label != item[1].label or
              (item[0].type == item[1].type and
               (item[0].address|regex_replace(':','')) == (item[1].port|regex_replace(':',''))) }}"
    msg: "port failed to be updated!"
  loop: "{{ query('nested', port_info, expected_port_info) }}"


# ****************************************************
# *** Remove any existing hosts and verify changes ***
# ****************************************************
- name: Remove any existing hosts
  netapp_e_host:
    <<: *creds
    state: absent
    name: "{{ item.key }}"
  with_dict: *hosts

# Request all host object definitions
- name: HTTP request for all host definitions from array
  uri:
    url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/hosts"
    user: "{{ credentials.api_username }}"
    password: "{{ credentials.api_password }}"
    body_format: json
    validate_certs: no
  register: results

# Collect port label information
- name: Collect host side port labels
  set_fact:
    host_side_port_labels: "{{ results | json_query('json[*].hostSidePorts[*].label') }}"

- name: Collect removed port labels
  set_fact:
    removed_host_side_port_labels: "{{ hosts | json_query('*.ports[*].label') }}"

# Verify host 1 and 2 objects were removed
- name: Assert hosts were removed
  assert:
    that: item not in host_side_port_labels
    msg: "Host {{ item }} failed to be removed from the hosts!"
  loop: "{{ removed_host_side_port_labels }}"
