From 050f4d917b37bf0bd9d198114ac6160d8d05a5e5 Mon Sep 17 00:00:00 2001
From: Aleksandr Kurbatov <aleksandr.kurbatov@GL1342.local>
Date: Thu, 25 Apr 2024 21:33:05 +0100
Subject: [PATCH] Rework of check_optical levels

Added parsers to get optical values per lane for both Junos and SROS.
Junos: getting output in JSON, then parsing is easy.
SROS: no option to get output in JSON, hence need to parse using `ansible.utils.cli_parse`.
`templates/sros_show_port_optical.yaml` is part of SROS parsing.

For the "remaining" side of a trunk:
Because it will be the same router/vendor in PRE and POST, we can produce the diff on PRE/POST.
So then all the PRE and POST results are saved in individual files and the final task would be to produce the diff.
However, the optical levels always fluctuate a little, so there always be a diff on wide range of values.
---
 .../tasks/check_optical_levels.yaml           | 71 +++++++++++++------
 .../tasks/diff_optical_results.yaml           | 27 +++++++
 .../roles/iptrunk_checks/tasks/main.yml       | 33 ++++++++-
 .../templates/sros_show_port_optical.yaml     |  8 +++
 4 files changed, 116 insertions(+), 23 deletions(-)
 create mode 100644 geant/gap_ansible/roles/iptrunk_checks/tasks/diff_optical_results.yaml
 create mode 100644 geant/gap_ansible/roles/iptrunk_checks/templates/sros_show_port_optical.yaml

diff --git a/geant/gap_ansible/roles/iptrunk_checks/tasks/check_optical_levels.yaml b/geant/gap_ansible/roles/iptrunk_checks/tasks/check_optical_levels.yaml
index b4b83f5c..c6b85f77 100644
--- a/geant/gap_ansible/roles/iptrunk_checks/tasks/check_optical_levels.yaml
+++ b/geant/gap_ansible/roles/iptrunk_checks/tasks/check_optical_levels.yaml
@@ -5,28 +5,46 @@
     - name: Run Juniper Optical info
       junipernetworks.junos.junos_command:
         commands:
-          # - show interfaces diagnostics optics {{ item.interface_name }} | no-more
-          - show interfaces terse {{ item.interface_name }}
-        # display: json
+          - "show interfaces diagnostics optics {{ ae_member.interface_name }}"
+        display: json
       register: optical_status
 
-    - name: Show optical levels on Juniper interface "{{ item.interface_name }}"
-      ansible.builtin.debug:
-        msg: "{{ optical_status }}"
+    - name: Set variable for lanes
+      ansible.builtin.set_fact:
+        lanes: "{{ lanes | default([]) }}"
+
+    - name: Populate the lanes data for "{{ ae_member.interface_name }}"
+      ansible.builtin.set_fact:
+        lanes: "{{ lanes + [{
+          'lane_id': iface_opt_lane['lane-index'][0]['data'],
+          'laser_current': iface_opt_lane['laser-bias-current'][0]['data'],
+          'laser_tx_mw': iface_opt_lane['laser-output-power'][0]['data'],
+          'laser_tx_dbm': iface_opt_lane['laser-output-power-dbm'][0]['data'],
+          'laser_rx_mw': iface_opt_lane['laser-rx-optical-power'][0]['data'],
+          'laser_rx_dbm': iface_opt_lane['laser-rx-optical-power-dbm'][0]['data'] }] }}"
+
+      vars:
+        obj: "{{ optical_status.stdout_lines[0]['interface-information'][0]['physical-interface'][0]['optics-diagnostics'][0] }}"
+      loop: "{{ obj['optics-diagnostics-lane-values'] }}"
+      loop_control:
+        loop_var: iface_opt_lane
+
+    - name: Set variable for the PHY interface
+      ansible.builtin.set_fact:
+        ae_result: "{{ ae_result | combine({item.key: item.value}) }}"
+      with_items:
+        - { key: "{{ ae_member.interface_name }}", value: "{{ lanes }}" }
 
-    - name: Write output to the file
-      ansible.builtin.blockinfile:
-        path: "{{ opt_checks_file }}"
-        marker: "# {mark} --- {{ check | uppper }} check on {{ inventory_hostname }} for {{ item.interface_name }} ---"
-        append_newline: true
-        block: "{{ optical_status }}"
+    - name: Empty lanes variable for the next cycle
+      ansible.builtin.set_fact:
+        lanes: []
 
-- name: Get Nokia optical levels on port "{{ item.interface_name }}"
+- name: Get Nokia optical levels on port "{{ ae_member.interface_name }}"
   when: local_side.iptrunk_side_node.vendor == "nokia"
   block:
     - name: Prepare the connector var
       ansible.builtin.set_fact:
-        connector: "{{ item.interface_name | regex_search('[1-9]/[1-9]/c[1-9]+') }}"
+        connector: "{{ ae_member.interface_name | regex_search('[1-9]/[1-9]/c[1-9]+') }}"
 
     - name: Run Nokia show command
       ansible.netcommon.netconf_rpc:
@@ -35,13 +53,26 @@
         content: |
             <global-operations xmlns="urn:nokia.com:sros:ns:yang:sr:oper-global">
               <md-cli-raw-command>
-              <md-cli-input-line>show port {{ connector }} optical | match "Lane ID" pre-lines 1 post-lines 5</md-cli-input-line>
+              <md-cli-input-line>show port {{ connector }} optical</md-cli-input-line>
               </md-cli-raw-command>
             </global-operations>
         display: json
-      register: output
+      register: out
+
+    - name: Parse SROS port optical output
+      ansible.utils.cli_parse:
+        text: "{{out.output['rpc-reply']['nokiaoper:results']['nokiaoper:md-cli-output-block'].split('\n')}}"
+        parser:
+          name: ansible.netcommon.native
+          template_path: templates/sros_show_port_optical.yaml
+        set_fact: lanes
 
-    - name: Show optical levels on Nokia port "{{ item.interface_name }}"
-      ansible.builtin.debug:
-        # msg: "{{ output }}"
-        msg: "{{out.output['rpc-reply']['nokiaoper:results']['nokiaoper:md-cli-output-block'].split('\n')}}"
+    - name: Set variable for the PHY interface
+      ansible.builtin.set_fact:
+        ae_result: "{{ ae_result | combine({item.key: item.value}) }}"
+      with_items:
+        - { key: "{{ ae_member.interface_name }}", value: "{{ lanes }}" }
+
+    - name: Empty lanes variable for the next cycle
+      ansible.builtin.set_fact:
+        lanes: []
diff --git a/geant/gap_ansible/roles/iptrunk_checks/tasks/diff_optical_results.yaml b/geant/gap_ansible/roles/iptrunk_checks/tasks/diff_optical_results.yaml
new file mode 100644
index 00000000..3d42a9e2
--- /dev/null
+++ b/geant/gap_ansible/roles/iptrunk_checks/tasks/diff_optical_results.yaml
@@ -0,0 +1,27 @@
+---
+- name: Set the PRE file var name
+  ansible.builtin.set_fact:
+    pre_file_name: "{{ opt_checks_dir }}/PRE_{{ inventory_hostname }}.yaml"
+
+- name: Set the PRE file var name
+  ansible.builtin.set_fact:
+    post_file_name: "{{ opt_checks_dir }}/POST_{{ inventory_hostname }}.yaml"
+
+- name: Check if PRE file exists
+  ansible.builtin.stat:
+    path: "{{ pre_file_name }}"
+  register: pre_file_stat
+
+- name: Check if POST file exists
+  ansible.builtin.stat:
+    path: "{{ post_file_name }}"
+  register: post_file_stat
+
+- name: Compare PRE and POST
+  when: >-
+    pre_file_stat is defined
+    and
+    post_file_stat is defined
+  ansible.utils.fact_diff:
+    before: "{{ lookup('ansible.builtin.file', 'pre_file_name') }}"
+    after: "{{ lookup('ansible.builtin.file', 'post_file_name') }}"
diff --git a/geant/gap_ansible/roles/iptrunk_checks/tasks/main.yml b/geant/gap_ansible/roles/iptrunk_checks/tasks/main.yml
index 48646bfa..5dbcbc8e 100644
--- a/geant/gap_ansible/roles/iptrunk_checks/tasks/main.yml
+++ b/geant/gap_ansible/roles/iptrunk_checks/tasks/main.yml
@@ -51,10 +51,15 @@
   ansible.builtin.set_fact:
     opt_checks_dir: "/var/tmp/ansible_trunk_checks_{{ trunks[0].id }}"
 
-- name: Set the optical checks results file name
-  when: check == "optical_pre" or check == "optical_post"
+- name: Set the optical checks results file name for PRE
+  when: check == "optical_pre"
+  ansible.builtin.set_fact:
+    opt_checks_file: "{{ opt_checks_dir }}/PRE_{{ inventory_hostname }}.yaml"
+
+- name: Set the optical checks results file name for POST
+  when: check == "optical_post"
   ansible.builtin.set_fact:
-    opt_checks_file: "{{ opt_checks_dir }}/checks_{{ process_id }}.txt"
+    opt_checks_file: "{{ opt_checks_dir }}/POST_{{ inventory_hostname }}.yaml"
 
 - name: Create a folder for the optical check results
   when: check == "optical_pre" or check == "optical_post"
@@ -72,7 +77,29 @@
     mode: '0644'
   delegate_to: localhost
 
+- name: Create var for the result storage
+  when: check == "optical_pre" or check == "optical_post"
+  ansible.builtin.set_fact:
+    ae_result: "{{ ae_result | default({}) }}"
+
 - name: Check optical levels
   when: check == "optical_pre" or check == "optical_post"
   ansible.builtin.include_tasks: check_optical_levels.yaml
   loop: "{{ local_side.iptrunk_side_ae_members }}"
+  loop_control:
+    loop_var: ae_member
+
+- name: Write result to the "{{ opt_checks_file}}" file
+  when: check == "optical_pre" or check == "optical_post"
+  ansible.builtin.copy:
+    content: "{{ ae_result | to_nice_yaml }}"
+    dest: "{{ opt_checks_file }}"
+
+- name: Display "{{ check | upper }}" check results for "{{ inventory_hostname }}"
+  when: check == "optical_pre" or check == "optical_post"
+  ansible.builtin.debug:
+    msg: "{{ ae_result }}"
+
+- name: Produce the diff of optical check results on the "remaining" side
+  when: check == "optical_post"
+  ansible.builtin.include_tasks: diff_optical_results.yaml
diff --git a/geant/gap_ansible/roles/iptrunk_checks/templates/sros_show_port_optical.yaml b/geant/gap_ansible/roles/iptrunk_checks/templates/sros_show_port_optical.yaml
new file mode 100644
index 00000000..a8754bac
--- /dev/null
+++ b/geant/gap_ansible/roles/iptrunk_checks/templates/sros_show_port_optical.yaml
@@ -0,0 +1,8 @@
+---
+- example: "    1              -              73.2              1.67              1.43"
+  getval: '\s+(?P<lane_id>\d)\s+-\s+(?P<tx_bias_ma>\d+(\.\d+)?)\s+(?P<tx_power_dbm>\d+(\.\d+)?)\s+(?P<rx_power_dbm>\d+(\.\d+)?)'
+  result:
+    "Lane {{ lane_id }}":
+      TX_bias_mA: "{{ tx_bias_ma }}"
+      TX_power_dBm: "{{ tx_power_dbm }}"
+      rx_power_dBm: "{{ rx_power_dbm }}"
-- 
GitLab