---
# This playbook is intended to bootstrap network configuration of the iDRACs on
# a set of Dell servers. By default, iDRACs have a known static IP address
# configured. Since all iDRACs have the same default IP address, we need a way
# to isolate a single iDRAC while we set its network configuration. We do this
# using a temporary VLAN accessible from one of the controllers.
#
# We use the following procedure:
# 1. Create a VLAN interface on the controller node with IP in the iDRAC
#    default subnet.
# 2. Create the temporary bootstrap VLAN on the switch, accessible by the
#    controller and trunked to all switches within the network.
# 3. For each iDRAC switch port in turn, flip to the temporary VLAN and
#    configure the iDRAC's IP address, before returning the port to the iDRAC
#    management VLAN.
# 4. Remove the temporary bootstrap VLAN from the switch.
# 5. Remove the VLAN interface on the controller node.

# Playbook variables:
#
# idrac_limit: Colon-separated list of names of idracs to limit bootstrappping
#              to. These names should be present in the variable
#              idrac_network_ips. If omitted all idracs will be bootstrapped.

- name: Ensure the iDRAC switches are supported
  hosts: "{{ idrac_bootstrap_switch_group }}"
  gather_facts: no
  vars:
    supported_switch_types:
      - dellos6
  tasks:
    - name: Ensure switch type is supported
      fail:
        msg: >
          The iDRAC bootstrap process currently only supports DellOS6 switches.
      when: switch_type not in supported_switch_types

# 1. Create a VLAN interface on the controller node with IP in the iDRAC
#    default subnet.
- name: Ensure the controller bootstrap network is configured
  hosts: "{{ idrac_bootstrap_controller_group ~ '[0]' }}"
  tasks:
    # Install Dell server admin tools.
    - block:
        - name: Ensure wget is installed
          package:
            name: wget
            state: present
            cache_valid_time: "{{ apt_cache_valid_time if ansible_facts.os_family == 'Debian' else omit }}"
            update_cache: "{{ True if ansible_facts.os_family == 'Debian' else omit }}"

        - name: Ensure Dell srvadmin repository is installed
          shell: "wget -q -O - http://linux.dell.com/repo/hardware/latest/bootstrap.cgi | bash"

        - name: Ensure Dell srvadmin-idrac7 package is installed
          package:
            name: srvadmin-idrac7
            state: present

        # Configure access to the temporary network on a controller.
        - block:
            # Clear any previous state.
            - name: Ensure iDRAC bootstrap network namespace is deleted from the controller
              command: "ip netns delete {{ idrac_bootstrap_net_namespace }}"
              args:
                removes: "/var/run/netns/{{ idrac_bootstrap_net_namespace }}"

            - name: Ensure iDRAC bootstrap network namespace exists on controller
              command: "ip netns add {{ idrac_bootstrap_net_namespace }}"

            - name: Ensure bootstrap VLAN interface exists on the controller
              command: "ip link add link {{ idrac_bootstrap_controller_interface }} name {{ idrac_bootstrap_controller_vlan_interface }} type vlan id {{ idrac_bootstrap_vlan }}"

            - name: Ensure bootstrap VLAN interface is in network namespace
              command: "ip link set {{ idrac_bootstrap_controller_vlan_interface }} netns {{ idrac_bootstrap_net_namespace }}"

            - name: Ensure the bootstrap VLAN interface is active
              command: "ip netns exec {{ idrac_bootstrap_net_namespace }} ip link set {{ idrac_bootstrap_controller_vlan_interface }} up"

            - name: Ensure the bootstrap VLAN interface IP address is configured
              command: "ip netns exec {{ idrac_bootstrap_net_namespace }} ip address add {{ idrac_bootstrap_controller_ip }}/24 dev {{ idrac_bootstrap_controller_vlan_interface }}"
          rescue:
            - name: Rescue | Ensure the bootstrap network namespace is removed from the controller
              command: "ip netns delete {{ idrac_bootstrap_net_namespace }}"

            - name: Rescue | Fail playbook execution on error
              fail:
                msg: >
                  Failed to configure access to temporary iDRAC bootstrap
                  network on controller.
      become: True

# 2. Create the temporary bootstrap VLAN on the switch, accessible by the
#    controller and trunked to all switches within the network.
- name: Ensure the bootstrap VLAN is configured on switches
  hosts: "{{ idrac_bootstrap_switch_group }}"
  gather_facts: no
  vars:
    switch_interface_config_bootstrap_trunk:
      config:
        - "switchport trunk allowed vlan add {{ idrac_bootstrap_vlan }}"
    # Initialise the switch interface configuration.
    switch_interface_config_bootstrap: {}
  pre_tasks:
    - name: Update facts about switch trunk interfaces
      set_fact:
        switch_interface_config_bootstrap: >
          {{ switch_interface_config_bootstrap | combine({item.key: switch_interface_config_bootstrap_trunk}) }}
      with_dict: "{{ switch_interface_config }}"
      when: >
        {{ item.value.description | default == groups[idrac_bootstrap_controller_group][0] or
           item.value.description | default | replace('-trunk', '') in groups[idrac_bootstrap_switch_group] }}

  roles:
    # Configure bootstrap VLAN on the switch and add controller and trunk
    # interfaces to it.
    - role: dell-switch
      dell_switch_type: "{{ switch_type }}"
      dell_switch_provider: "{{ switch_dellos_provider }}"
      dell_switch_config:
        - "vlan {{ idrac_bootstrap_vlan }}"
      dell_switch_interface_config: "{{ switch_interface_config_bootstrap }}"
      when: switch_interface_config_bootstrap != {}

# 3. For each iDRAC switch port in turn, flip to the temporary VLAN and
#    configure the iDRAC's IP address, before returning the port to the iDRAC
#    management VLAN.
- name: Ensure iDRACs are bootstrapped
  hosts: "{{ idrac_bootstrap_switch_group }}"
  gather_facts: no
  vars:
    # Set this to a colon-separated list of idrac hostnames to bootstrap.
    # If unset, all idracs will be bootstrapped.
    idrac_limit: ""
    idrac_limit_list: "{{ idrac_limit.split(':') }}"
  # This is a separate play so that we can apply the serial keyword.
  serial: 1
  tasks:
    - name: Initialise facts containing successful, unchanged and failed iDRACs
      set_fact:
        idrac_bootstrap_success: []
        idrac_bootstrap_unchanged: []
        idrac_bootstrap_failed: []

    # Iterate over each switch port with an iDRAC attached in turn.
    - name: Ensure iDRACs are (sequentially) bootstrapped
      include_tasks: idrac-bootstrap-one.yml
      vars:
        dell_switch_type: "{{ switch_type }}"
        dell_switch_provider: "{{ switch_dellos_provider }}"
        switch_interface_name: "{{ item.key }}"
        idrac_port_description: "{{ item.value.description }}"
        idrac_network_ip: "{{ idrac_network_ips[idrac_port_description] }}"
        idrac_bootstrap_controller: "{{ hostvars[groups[idrac_bootstrap_controller_group][0]].ansible_host }}"
      with_dict: "{{ switch_interface_config }}"
      when: >
        {{ item.value.description | default in idrac_network_ips and
           (not idrac_limit or item.value.description | default in idrac_limit_list) }}

# 4. Remove the temporary bootstrap VLAN from the switch.
- name: Ensure the bootstrap VLAN is removed from switches
  hosts: "{{ idrac_bootstrap_switch_group }}"
  gather_facts: no
  vars:
    switch_interface_config_bootstrap_trunk:
      config:
        - "switchport trunk allowed vlan remove {{ idrac_bootstrap_vlan }}"
    # Initialise the switch interface configuration.
    switch_interface_config_bootstrap: {}
  pre_tasks:
    - name: Update facts about switch trunk interfaces
      set_fact:
        switch_interface_config_bootstrap: >
          {{ switch_interface_config_bootstrap | combine({item.key: switch_interface_config_bootstrap_trunk}) }}
      with_dict: "{{ switch_interface_config }}"
      when: >
        {{ item.value.description | default == groups[idrac_bootstrap_controller_group][0] or
           item.value.description | default | replace('-trunk', '') in groups[idrac_bootstrap_switch_group] }}
  roles:
    # Remove bootstrap VLAN from the switch and remove controller and trunk
    # interfaces from it.
    - role: dell-switch
      dell_switch_type: "{{ switch_type }}"
      dell_switch_provider: "{{ switch_dellos_provider }}"
      dell_switch_config:
        - "no vlan {{ idrac_bootstrap_vlan }}"
      dell_switch_interface_config: "{{ switch_interface_config_bootstrap }}"
      when: switch_interface_config_bootstrap != {}

# 5. Remove the VLAN interface on the controller node.
- name: Ensure the controller bootstrap network is cleaned up
  hosts: "{{ idrac_bootstrap_controller_group ~ '[0]' }}"
  tasks:
    # This should also delete the network interface within the namespace.
    - name: Ensure the bootstrap network namespace is removed from the controller
      command: "ip netns delete {{ idrac_bootstrap_net_namespace }}"
      become: True

- name: Display the results of the iDRAC bootstrap procedure
  hosts: "{{ idrac_bootstrap_switch_group }}"
  gather_facts: no
  tasks:
    - name: Display a list of failed iDRACs
      set_fact:
        idrac_bootstrap_failed_port_descriptions: "{{ idrac_bootstrap_failed | map(attribute='port description') | list }}"
      when: idrac_bootstrap_failed | length > 0

    - name: Display a list of successfully bootstrapped iDRACs
      debug:
        var: idrac_bootstrap_success

    - name: Display a list of iDRACs that did not require bootstrapping
      debug:
        var: idrac_bootstrap_unchanged

    - name: Display a list of failed iDRACs
      debug:
        var: idrac_bootstrap_failed_port_descriptions
      when: idrac_bootstrap_failed | length > 0

    - name: Display a list of failed iDRACs with debug output for the failed tasks
      debug:
        var: idrac_bootstrap_failed
      when: idrac_bootstrap_failed | length > 0

    - name: Fail if there were any iDRAC bootstrapping failures
      fail:
        msg: >
          One or more iDRACs failed to bootstrap, see the list above for
          details.
      when: idrac_bootstrap_failed | length > 0
