---
# Some Dell switch OSs (including Dell Network OS 9.10(0.1)) do not support
# sending interface port description TLVs correctly. Instead of sending the
# interface description, they send the interface name (e.g. TenGigabitEthernet
# 1/1/1). This breaks the discovery process which relies on Ironic node
# introspection data containing the node's name in the interface port
# description. We work around this here by creating an introspection rule for
# each ironic node that matches against the switch system and the relevant
# interface name, then sets the node's name appropriately.
#
# Note that some Dell switches running Dell Networking OS version 9.11(2.0P1)
# and newer do *not* need this workaround. The interface description can be
# sent using the following configuration:
#
# (conf-if-interface)#protocol lldp
# (conf-if-interface-lldp)#advertise interface-port-desc description

- name: Check whether Ironic is enabled
  hosts: controllers
  gather_facts: False
  tags:
    - introspection-rules
    - introspection-rules-dell-lldp-workaround
  tasks:
    - name: Create controllers group with ironic enabled
      group_by:
        key: "controllers_for_introspection_rules_dell_lldp_workaround_{{ kolla_enable_ironic | bool }}"
      changed_when: false

- name: Group controller hosts in systems requiring the workaround
  hosts: controllers_for_introspection_rules_dell_lldp_workaround_True
  gather_facts: False
  tags:
    - introspection-rules
    - introspection-rules-dell-lldp-workaround
  tasks:
    - name: Group controller hosts in systems requiring the Dell switch LLDP workaround
      group_by:
        key: "controllers_require_workaround_{{ groups[inspector_dell_switch_lldp_workaround_group] | default([]) | length > 0 }}"
      changed_when: false

- name: Ensure introspection rules for Dell switch LLDP workarounds are registered in Ironic Inspector
  # Only required to run on a single host.
  hosts: controllers_require_workaround_True[0]
  tags:
    - introspection-rules
    - introspection-rules-dell-lldp-workaround
  vars:
    all_switch_interfaces: []
    ironic_inspector_rules: []
    # This rule template is used in a with_subelements loop.
    inspector_interface_mapping_rule:
      description: "Set {{ item.1.1.description }} node name from {{ inspector_rule_var_lldp_switch_port_interface }} LLDP switch port description"
      conditions:
        - field: "data://all_interfaces.{{ inspector_rule_var_lldp_switch_port_interface }}"
          op: "is-empty"
          invert: True
        - field: "data://all_interfaces.{{ inspector_rule_var_lldp_switch_port_interface }}.lldp_processed"
          op: "is-empty"
          invert: True
        - field: "data://all_interfaces.{{ inspector_rule_var_lldp_switch_port_interface }}.lldp_processed.switch_port_description"
          op: "is-empty"
          invert: True
        - field: "data://all_interfaces.{{ inspector_rule_var_lldp_switch_port_interface }}.lldp_processed.switch_system_name"
          op: "is-empty"
          invert: True
        # Match against the interface name.
        - field: "data://all_interfaces.{{ inspector_rule_var_lldp_switch_port_interface }}.lldp_processed.switch_port_description"
          op: "eq"
          # Our interface names may be of a shortened form e.g. Te1/1/1, but
          # the port description will contain the full interface name. Use a
          # regex to expand to the full form.
          value: "{{ item.1.0 | regex_replace('^Te([a-zA-z ]*)([0-9/]+)$', 'TenGigabitEthernet \\2') }}"
        # Match against the switch system name.
        - field: "data://all_interfaces.{{ inspector_rule_var_lldp_switch_port_interface }}.lldp_processed.switch_system_name"
          op: "eq"
          value: "{{ item.0.host }}"
      actions:
        - action: "set-attribute"
          path: "name"
          value: "{{ item.1.1.description }}"
    inspector_rule_var_lldp_switch_port_interface: "{{ inspector_lldp_switch_port_interface_map.get(item.1.1.description, inspector_lldp_switch_port_interface_default) }}"

  pre_tasks:
    - name: Validate OpenStack password authentication parameters
      fail:
        msg: >
          Required OpenStack authentication parameter {{ item }} is
          {% if item in openstack_auth %}empty{% else %}not present{% endif %}
          in openstack_auth. Have you sourced the environment file?
      when:
        - openstack_auth_type == 'password'
        - item not in openstack_auth or not openstack_auth[item]
      with_items: "{{ openstack_auth_password_required_params }}"
      tags:
        - config-validation

    # We build up the rules using a 2-step process. First we build a list of
    # relevant switch hosts and their interface configuration (in list form).
    # This allows us to use with_subelements in the next task to iterate over
    # the interfaces for each switch.
    - name: Update a fact containing switch interface configuration
      set_fact:
        all_switch_interfaces: >
          {{ all_switch_interfaces +
             [{'host': item.key,
               'interface_config': item.value.switch_interface_config.items()}] }}
      with_dict: "{{ hostvars }}"
      when: item.key in groups[inspector_dell_switch_lldp_workaround_group]

    - name: Update a fact containing Ironic Inspector rules
      set_fact:
        ironic_inspector_rules: >
          {{ ironic_inspector_rules +
             [inspector_interface_mapping_rule] }}
      with_subelements:
        - "{{ all_switch_interfaces }}"
        - interface_config
      when:
        - item.1.1.description is defined
        # Ignore VLAN interfaces.
        - "'vlan' not in item.1.0"
        # Ignore trunk links.
        - "'-trunk' not in item.1.1.description"

  roles:
    - role: ironic-inspector-rules
      os_openstacksdk_install_epel: "{{ dnf_install_epel }}"
      os_openstacksdk_state: latest
      ironic_inspector_venv: "{{ virtualenv_path }}/openstacksdk"
      ironic_inspector_upper_constraints_file: "{{ pip_upper_constraints_file }}"
      ironic_inspector_auth_type: "{{ openstack_auth_type }}"
      ironic_inspector_auth: "{{ openstack_auth }}"
      ironic_inspector_cacert: "{{ openstack_cacert }}"
      ironic_inspector_interface: "{{ openstack_interface }}"
