1187 lines
42 KiB
YAML
1187 lines
42 KiB
YAML
# sot-updater.yml — Read fw on Dev1 + Dev2, publish to NetBox via Rabbit (telemetry mode, no upgrade scheduling)
|
|
|
|
- name: Read fw on Dev1 + Dev2, publish NetBox custom fields (full base, AIRPINGs, soft-fail telemetry, no upgrade scheduling)
|
|
hosts: all
|
|
gather_facts: no
|
|
|
|
vars:
|
|
pathprefix: "PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH; "
|
|
dev1_user: "root"
|
|
dev1_pass: "wavewave"
|
|
ssh_timeout: 30
|
|
|
|
dev2_host: "192.168.1.1"
|
|
dev2_port: 22
|
|
dev2_ssh_user: "root"
|
|
dev2_passfiles: [ "basicpass", "basicpass2" ]
|
|
|
|
dev2_side_ip: "192.168.1.11/24"
|
|
dev1_iface: "br-wan"
|
|
arping_iface: "eth0"
|
|
dev2_side_ip_addr: "{{ dev2_side_ip.split('/')[0] }}"
|
|
|
|
ssh_opts_common: >-
|
|
-o PreferredAuthentications=password
|
|
-o PubkeyAuthentication=no
|
|
-o StrictHostKeyChecking=no
|
|
-o UserKnownHostsFile=/dev/null
|
|
-o NumberOfPasswordPrompts=1
|
|
-o ConnectTimeout=15
|
|
|
|
rmq_host: "{{ lookup('env','RMQ_HOST') | default('10.210.12.2', true) }}"
|
|
rmq_port: "{{ lookup('env','RMQ_PORT') | default('15672', true) }}"
|
|
rmq_user: "{{ lookup('env','RMQ_USER') | default('admin', true) }}"
|
|
rmq_pass: "{{ lookup('env','RMQ_PASS') | default('change_me', true) }}"
|
|
rmq_vhost: "{{ lookup('env','RMQ_VHOST') | default('app', true) }}"
|
|
rmq_exchange: "{{ lookup('env','RMQ_EXCHANGE') | default('controls', true) }}"
|
|
control_queue: "{{ lookup('env','CONTROLQUEUE') | default('queue_controls', true) }}"
|
|
|
|
# --- Retry attempt tracking (default 0 if missing) ---
|
|
requeue_attempt: "{{ (lookup('env','REQUEUE_ATTEMPT') | default('0', true)) | int }}"
|
|
|
|
pre_tasks:
|
|
- name: "NB preflight | Verify script exists"
|
|
delegate_to: localhost
|
|
ansible.builtin.stat:
|
|
path: "/opt/containers/ansible-worker/app/nb_onedevice_update.py"
|
|
register: nb_script
|
|
|
|
- name: "NB preflight | Abort softly if script missing"
|
|
when: not nb_script.stat.exists
|
|
delegate_to: localhost
|
|
ansible.builtin.debug:
|
|
msg:
|
|
- "NB preflight skipped: /opt/containers/ansible-worker/app/nb_onedevice_update.py not found."
|
|
- "Tip: adjust chdir/path or script name."
|
|
|
|
- name: "NB preflight | Run nb_onedevice_update.py for {{ inventory_hostname }}"
|
|
when: nb_script.stat.exists
|
|
delegate_to: localhost
|
|
environment:
|
|
PYTHONUNBUFFERED: "1"
|
|
args:
|
|
chdir: "/opt/containers/ansible-worker/app"
|
|
executable: /bin/bash
|
|
shell: |
|
|
set -o pipefail
|
|
python3 -u nb_onedevice_update.py "{{ inventory_hostname }}" --chatty 2>&1
|
|
register: nb_preflight
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: "NB preflight | Show results"
|
|
when: nb_script.stat.exists
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "rc={{ nb_preflight.rc }}"
|
|
- "stdout_lines:"
|
|
- "{{ (nb_preflight.stdout_lines | default(['<no stdout>'])) }}"
|
|
- "stderr_lines:"
|
|
- "{{ (nb_preflight.stderr_lines | default(['<no stderr>'])) }}"
|
|
- "raw stdout (joined): {{ nb_preflight.stdout | default('') | trim }}"
|
|
|
|
# --- Detect cloud failure (rc==3 or FAIL Cloud line) ---
|
|
- name: "NB preflight | Detect cloud failure"
|
|
when: nb_script.stat.exists
|
|
delegate_to: localhost
|
|
vars:
|
|
_lines: >-
|
|
{{ (nb_preflight.stdout | default('') | regex_replace('\r','')) | split('\n') | map('trim') | list }}
|
|
_cloud_fail_line: >-
|
|
{{ (_lines | select('match', '^FAIL\\s+Cloud\\b') | list | last | default('')) }}
|
|
_rc_is_3: "{{ (nb_preflight.rc | default(1)) | int == 3 }}"
|
|
set_fact:
|
|
cloud_fail_line: "{{ _cloud_fail_line }}"
|
|
cloud_bad: "{{ _rc_is_3 or (( _cloud_fail_line | length ) > 0) }}"
|
|
|
|
# --- Extract upgrade_cmd line if any ---
|
|
- name: "NB preflight | Extract upgrade_cmd line"
|
|
when: nb_script.stat.exists
|
|
delegate_to: localhost
|
|
set_fact:
|
|
nb_upgrade_line: >-
|
|
{{
|
|
(
|
|
nb_preflight.stdout_lines | default([]) | map('regex_replace','\r','') | map('trim')
|
|
| select('match', '^NB:\\s*upgrade_cmd\\s*=')
|
|
| list | first
|
|
) | default('')
|
|
}}
|
|
|
|
- name: "NB preflight | Parse upgrade_cmd value"
|
|
when: nb_upgrade_line | length > 0
|
|
delegate_to: localhost
|
|
shell: |
|
|
printf '%s\n' "{{ nb_upgrade_line }}" | awk -F'=' '{print $2}' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
|
|
args: { executable: /bin/bash }
|
|
register: up_cmd_sh
|
|
changed_when: false
|
|
|
|
- name: "NB preflight | Set parsed upgrade_cmd"
|
|
when: nb_upgrade_line | length > 0
|
|
delegate_to: localhost
|
|
set_fact:
|
|
nb_upgrade_cmd: "{{ up_cmd_sh.stdout | default('') | trim }}"
|
|
|
|
- name: "NB preflight | Debug parsed upgrade_cmd"
|
|
when: nb_upgrade_line | length > 0
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "upgrade_cmd='{{ nb_upgrade_cmd }}' (len={{ nb_upgrade_cmd|length }})"
|
|
|
|
- name: "NB preflight | Parse OK line"
|
|
when: nb_script.stat.exists
|
|
delegate_to: localhost
|
|
vars:
|
|
nb_lines: >-
|
|
{{ (nb_preflight.stdout | default('') | regex_replace('\r','')) | split('\n') | map('trim') | list }}
|
|
nb_ok_line: >-
|
|
{{ (nb_lines | select('match', '^OK\\s+') | list | last | default('')) }}
|
|
nb_tokens: >-
|
|
{{ (nb_ok_line | regex_replace('^OK\\s+', '')).split() }}
|
|
nb_kv: >-
|
|
{{
|
|
dict(
|
|
nb_tokens
|
|
| select('match', '^[a-zA-Z_]+=')
|
|
| map('split', '=', 1)
|
|
| map('list')
|
|
)
|
|
}}
|
|
set_fact:
|
|
nb_ok: "{{ (nb_preflight.rc|default(1)) == 0 and (nb_ok_line|length)>0 }}"
|
|
nb_dev: "{{ (nb_tokens|first|default('')) if (nb_tokens|length>0) else '' }}"
|
|
nb_ip: "{{ nb_kv.get('ip','') }}"
|
|
nb_fw: "{{ nb_kv.get('fw','') }}"
|
|
nb_node: "{{ nb_kv.get('node','') }}"
|
|
nb_sector: "{{ nb_kv.get('sector','') }}"
|
|
nb_small: "{{ nb_kv.get('small','') }}"
|
|
nb_ok_line: "{{ nb_ok_line }}"
|
|
|
|
|
|
- name: "NB preflight | Detect IP change"
|
|
when: nb_script.stat.exists
|
|
delegate_to: localhost
|
|
vars:
|
|
out: "{{ (nb_preflight.stdout | default('') | regex_replace('\r','')) }}"
|
|
reason: >-
|
|
{%- if 'IP: moving ' in out -%}moving
|
|
{%- elif 'IP: create new ' in out -%}create new
|
|
{%- elif 'IP: pruning stale ' in out -%}pruning stale
|
|
{%- else -%}none{%- endif -%}
|
|
set_fact:
|
|
nb_ip_changed: "{{ reason != 'none' }}"
|
|
nb_change_reason: "{{ reason }}"
|
|
|
|
- name: "NB preflight | Verdict"
|
|
when: nb_script.stat.exists
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "Preflight verdict:"
|
|
- "Cloud fail: {{ cloud_bad|default(false) }}"
|
|
- "IP changed: {{ nb_ip_changed|default(false) }}"
|
|
- "Reason: {{ nb_change_reason|default('none') }}"
|
|
|
|
- name: "NB preflight | Compute next_attempt"
|
|
delegate_to: localhost
|
|
set_fact:
|
|
next_attempt: "{{ (requeue_attempt | int) + 1 }}"
|
|
|
|
# --- Requeue control: only 3 attempts, 300s apart ---
|
|
- name: "NB preflight | Publish delayed requeue (300s (10s)) if needed"
|
|
when:
|
|
- nb_script.stat.exists
|
|
- (cloud_bad | default(false)) or (nb_ip_changed | default(false))
|
|
- (requeue_attempt | int) < 3
|
|
delegate_to: localhost
|
|
uri:
|
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/app/deviceconfig.delayed/publish"
|
|
method: POST
|
|
user: "admin"
|
|
password: "change_me"
|
|
force_basic_auth: true
|
|
status_code: 200
|
|
return_content: yes
|
|
headers: { content-type: "application/json" }
|
|
body_format: json
|
|
body:
|
|
properties:
|
|
content_type: "application/json"
|
|
headers: { x-delay: 10000 }
|
|
routing_key: "deviceconfig"
|
|
payload: "{{ {
|
|
'inscope_device': (ansible_hostname | default(inventory_hostname)),
|
|
'task_name': 'sot-updater',
|
|
'attempt': next_attempt
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
register: rmq_requeue
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: "NB preflight | Log requeue publish response"
|
|
when:
|
|
- nb_script.stat.exists
|
|
- ((cloud_bad | default(false)) or (nb_ip_changed | default(false)))
|
|
- (requeue_attempt | int) < 3
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "Requeue published (attempt {{ (requeue_attempt | int) + 1 }})"
|
|
- "HTTP: {{ rmq_requeue.status|default('n/a') }}"
|
|
- "JSON: {{ rmq_requeue.json|default('<none>') }}"
|
|
|
|
- name: "NB preflight | Stop host after requeue"
|
|
when:
|
|
- nb_script.stat.exists
|
|
- ((cloud_bad | default(false)) or (nb_ip_changed | default(false)))
|
|
meta: end_host
|
|
|
|
- name: "NB preflight | Pause 1s if OK"
|
|
when:
|
|
- nb_script.stat.exists
|
|
- not ((cloud_bad | default(false)) or (nb_ip_changed | default(false)))
|
|
delegate_to: localhost
|
|
pause:
|
|
seconds: 1
|
|
|
|
tasks:
|
|
- name: "⚙️ Start | Dev1 banner → tunnel/auth → Dev2 firmux (telemetry mode)"
|
|
debug:
|
|
msg:
|
|
- "Device: {{ inventory_hostname }}"
|
|
- "Mode: report-only (soft-fail telemetry)"
|
|
|
|
# ----------------------- Temp IP on DEV1 -----------------------
|
|
- name: Add temporary IP on DEV1
|
|
raw: "{{ pathprefix }} ip a add {{ dev2_side_ip }} dev {{ dev1_iface }}"
|
|
register: add_ip
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Add temporary IP on DEV1 (tolerate 'File exists')
|
|
raw: >
|
|
{{ pathprefix }}
|
|
ip a add {{ dev2_side_ip }} dev {{ dev1_iface }}
|
|
register: add_ip
|
|
changed_when: add_ip.rc == 0
|
|
failed_when: >
|
|
add_ip.rc != 0
|
|
and ('File exists' not in (add_ip.stdout|default('')))
|
|
and ('File exists' not in (add_ip.stderr|default('')))
|
|
|
|
- name: Debug temp IP result
|
|
debug:
|
|
msg:
|
|
- "add_ip.rc={{ add_ip.rc|default('') }}"
|
|
- "add_ip.out={{ add_ip.stdout|default('')|trim }}"
|
|
- "add_ip.err={{ add_ip.stderr|default('')|trim }}"
|
|
|
|
- name: Discover DEV2 MAC via bridge fdb
|
|
raw: >
|
|
{{ pathprefix }}
|
|
bridge fdb show {{ dev1_iface }} | grep eth0 | grep -v permanent | grep master | awk '{print $1}' | head -n1
|
|
register: dev2_mac_scan
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Capture discovered MAC
|
|
set_fact:
|
|
dev2_mac: "{{ dev2_mac_scan.stdout|default('')|trim }}"
|
|
|
|
- name: Clear existing ARP entry
|
|
raw: >
|
|
{{ pathprefix }}
|
|
ip neigh del {{ dev2_host }} dev {{ dev1_iface }} 2>/dev/null || true
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Add static ARP if MAC found
|
|
when: dev2_mac|length > 0
|
|
raw: >
|
|
{{ pathprefix }}
|
|
ip neigh add {{ dev2_host }} lladdr {{ dev2_mac }} dev {{ dev1_iface }} nud permanent
|
|
register: dev2_arp_add
|
|
changed_when: dev2_arp_add.rc == 0
|
|
failed_when: >
|
|
dev2_arp_add.rc != 0
|
|
and ('File exists' not in (dev2_arp_add.stdout|default('')))
|
|
and ('File exists' not in (dev2_arp_add.stderr|default('')))
|
|
|
|
- name: Debug ARP summary
|
|
debug:
|
|
msg:
|
|
- "dev2_mac={{ dev2_mac|default('unset') }}"
|
|
- "arp_add.rc={{ dev2_arp_add.rc|default('') }}"
|
|
- "arp_add.out={{ dev2_arp_add.stdout|default('')|trim }}"
|
|
- "arp_add.err={{ dev2_arp_add.stderr|default('')|trim }}"
|
|
|
|
- name: Note skipping static ARP
|
|
when: dev2_mac|length == 0
|
|
debug:
|
|
msg: "No MAC discovered; skipping static ARP add."
|
|
|
|
- name: Refresh ARP
|
|
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
# ----------------------- Tunnel setup -----------------------
|
|
- name: Pick free local port
|
|
delegate_to: localhost
|
|
shell: |
|
|
for i in $(seq 1 50); do
|
|
p="$(shuf -i 20000-39999 -n 1)"
|
|
ss -ltn | awk '{print $4}' | grep -qE "(:|\.)${p}$" || { echo $p; exit 0; }
|
|
done
|
|
exit 1
|
|
register: pick_port
|
|
failed_when: false
|
|
|
|
- name: Create control dir
|
|
delegate_to: localhost
|
|
shell: "mktemp -d"
|
|
register: mktemp_dir
|
|
failed_when: false
|
|
|
|
- name: Set tunnel facts
|
|
delegate_to: localhost
|
|
set_fact:
|
|
_local_port: "{{ pick_port.stdout|default('')|trim }}"
|
|
_ctrl_dir: "{{ mktemp_dir.stdout|default('')|trim }}"
|
|
_ctrl_sock: "{{ mktemp_dir.stdout|default('')|trim }}/ssh_tunnel_ctl"
|
|
|
|
- name: Start SSH tunnel
|
|
delegate_to: localhost
|
|
shell: |
|
|
set -e
|
|
USER="{{ dev1_user }}"
|
|
HOST="{{ ansible_host|default(inventory_hostname) }}"
|
|
sshpass -p '{{ dev1_pass }}' ssh -f -N {{ ssh_opts_common }} \
|
|
-M -S "{{ _ctrl_sock }}" \
|
|
-L "127.0.0.1:{{ _local_port }}:{{ dev2_host }}:{{ dev2_port }}" \
|
|
"${USER}@${HOST}"
|
|
args: { executable: /bin/bash }
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Probe tunnel
|
|
delegate_to: localhost
|
|
shell: "nc -z -w5 127.0.0.1 {{ _local_port }}"
|
|
register: nc_probe
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Set tunnel_ok
|
|
delegate_to: localhost
|
|
set_fact:
|
|
tunnel_ok: "{{ nc_probe.rc == 0 }}"
|
|
|
|
# ----------------------- Dev1 banner -----------------------
|
|
- name: Dev1 | Probe banner
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"cat /etc/banner | grep -i rev | head -n1"
|
|
register: dev1_banner
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Normalize banner
|
|
delegate_to: localhost
|
|
shell: |
|
|
printf '%s\n' "{{ dev1_banner.stdout|trim }}" \
|
|
| awk -F '|' '{print $1}' \
|
|
| sed -E 's/[[:space:]]+rev[[:space:]]+/-r/' \
|
|
| grep -Eo '[0-9]+\.[0-9]+\.[0-9]+-r[0-9]+' || true
|
|
register: dev1_fw_clean_cmd
|
|
changed_when: false
|
|
|
|
- name: Dev1 | Set final fw string
|
|
delegate_to: localhost
|
|
set_fact:
|
|
dev1_fw_clean: "{{ dev1_fw_clean_cmd.stdout|trim }}"
|
|
|
|
- name: Publish Dev1 fw_version
|
|
delegate_to: localhost
|
|
uri:
|
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
|
|
method: POST
|
|
user: "{{ rmq_user }}"
|
|
password: "{{ rmq_pass }}"
|
|
force_basic_auth: true
|
|
status_code: 200
|
|
headers: { content-type: "application/json" }
|
|
body_format: json
|
|
body:
|
|
properties: { content_type: "application/json" }
|
|
routing_key: "{{ control_queue }}"
|
|
payload: "{{ {
|
|
'inscope_device': (ansible_hostname|default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'fw_version',
|
|
'task_result': (dev1_fw_clean if (dev1_fw_clean|length>0) else 'unavailable')
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
# ----------------------- Dev1 SSID count (strict then fallback) -----------------------
|
|
- name: Dev1 | Count SSID lines (strict 'ssid' + 'ikeja')
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"cat /tmp/config.json 2>/dev/null | grep ssid | grep ikeja | wc -l"
|
|
register: ssid_strict_count_raw
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Normalize strict SSID count
|
|
delegate_to: localhost
|
|
set_fact:
|
|
ssid_strict_count: "{{ (ssid_strict_count_raw.stdout | default('0')) | trim }}"
|
|
|
|
- name: Dev1 | Decide if strict count usable
|
|
delegate_to: localhost
|
|
set_fact:
|
|
ssid_strict_ok: >-
|
|
{{
|
|
(ssid_strict_count | int) > 0
|
|
and ((ssid_strict_count | int) % 2 == 0)
|
|
}}
|
|
|
|
- name: Dev1 | Count SSID lines (fallback exclude bssid/auto_ssid/SC/backhaul)
|
|
when: not ssid_strict_ok | default(false)
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"cat /tmp/config.json 2>/dev/null | grep ssid | grep -vE 'bssid|auto_ssid|SC|backhaul' | wc -l"
|
|
register: ssid_fallback_count_raw
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Normalize fallback SSID count
|
|
when: not ssid_strict_ok | default(false)
|
|
delegate_to: localhost
|
|
set_fact:
|
|
ssid_fallback_count: "{{ (ssid_fallback_count_raw.stdout | default('0')) | trim }}"
|
|
|
|
- name: Dev1 | Compute final SSID count
|
|
delegate_to: localhost
|
|
set_fact:
|
|
ssid_final_count: >-
|
|
{{
|
|
(ssid_strict_ok | default(false))
|
|
| ternary(
|
|
(ssid_strict_count | default('0') | int) // 2,
|
|
(ssid_fallback_count | default('0') | int) // 2
|
|
)
|
|
}}
|
|
|
|
- name: Dev1 | SSID counting debug
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "ssid_strict_ok={{ ssid_strict_ok | default(false) }}"
|
|
- "ssid_strict_count={{ ssid_strict_count | default('n/a') }}"
|
|
- "ssid_fallback_count={{ ssid_fallback_count | default('n/a') }}"
|
|
- "ssid_final_count={{ ssid_final_count | default('n/a') }}"
|
|
|
|
- name: Dev1 | Build multissidfix value
|
|
delegate_to: localhost
|
|
when: ssid_final_count is defined
|
|
set_fact:
|
|
multissidfix_value: "{{ (ssid_final_count | int) ~ ' ssids' }}"
|
|
|
|
- name: Dev1 | multissidfix debug
|
|
delegate_to: localhost
|
|
when: multissidfix_value is defined
|
|
debug:
|
|
msg: "multissidfix_value={{ multissidfix_value }}"
|
|
|
|
- name: Publish Dev1 multissidfix custom field
|
|
delegate_to: localhost
|
|
uri:
|
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
|
|
method: POST
|
|
user: "{{ rmq_user }}"
|
|
password: "{{ rmq_pass }}"
|
|
force_basic_auth: true
|
|
status_code: 200
|
|
headers: { content-type: "application/json" }
|
|
body_format: json
|
|
body:
|
|
properties: { content_type: "application/json" }
|
|
routing_key: "{{ control_queue }}"
|
|
payload: "{{ {
|
|
'inscope_device': (ansible_hostname|default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'multissidfix',
|
|
'task_result': multissidfix_value
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
# ----------------------- Dev1 scroll24.sh check & publish -----------------------
|
|
- name: Dev1 | Check scroll24.sh presence
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"test -f /root/scroll24.sh && echo present || echo missing"
|
|
register: scroll24_present_raw
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Set scroll24 presence fact
|
|
delegate_to: localhost
|
|
set_fact:
|
|
scroll24_present: "{{ (scroll24_present_raw.stdout | default('') | trim) == 'present' }}"
|
|
|
|
- name: Dev1 | Read scroll24.sh version line (may be empty -> v1)
|
|
when: scroll24_present | default(false)
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"grep -i 'scroll24_version' /root/scroll24.sh 2>/dev/null | head -n1 || true"
|
|
register: scroll24_ver_line
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Normalize scroll24 version
|
|
when: scroll24_present | default(false)
|
|
delegate_to: localhost
|
|
set_fact:
|
|
scroll24_ver_clean: >-
|
|
{{
|
|
(
|
|
scroll24_ver_line.stdout | default('') | trim
|
|
)
|
|
| regex_replace('^.*=', '')
|
|
| trim
|
|
if (scroll24_ver_line is defined
|
|
and (scroll24_ver_line.stdout | default('') | trim | length) > 0)
|
|
else 'v1'
|
|
}}
|
|
|
|
- name: Dev1 | Read scroll24 crontab entry (first match)
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"crontab -l 2>/dev/null | grep 'scroll24.sh' | head -n1 || true"
|
|
register: scroll24_cron_raw
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Normalize scroll24 cron line
|
|
delegate_to: localhost
|
|
set_fact:
|
|
scroll24_cron_line: "{{ scroll24_cron_raw.stdout | default('') | trim }}"
|
|
|
|
- name: Dev1 | Derive scroll24 schedule summary
|
|
delegate_to: localhost
|
|
vars:
|
|
cron_parts: "{{ scroll24_cron_line.split() if scroll24_cron_line|length > 0 else [] }}"
|
|
min_field: "{{ cron_parts[0] if cron_parts|length > 0 else '' }}"
|
|
hour_field: "{{ cron_parts[1] if cron_parts|length > 1 else '' }}"
|
|
every_n_minutes: >-
|
|
{{
|
|
(min_field is match('^\\*/[0-9]+$')) and (hour_field == '*')
|
|
}}
|
|
every_n_hours: >-
|
|
{{
|
|
hour_field is match('^\\*/[0-9]+$')
|
|
}}
|
|
interval_value: >-
|
|
{%- if every_n_minutes -%}
|
|
{{ min_field.split('/')[1] }}
|
|
{%- elif every_n_hours -%}
|
|
{{ hour_field.split('/')[1] }}
|
|
{%- else -%}
|
|
custom
|
|
{%- endif -%}
|
|
interval_unit: >-
|
|
{%- if every_n_minutes -%}
|
|
m
|
|
{%- elif every_n_hours -%}
|
|
h
|
|
{%- else -%}
|
|
''
|
|
{%- endif -%}
|
|
set_fact:
|
|
scroll24_schedule: >-
|
|
{%- if scroll24_cron_line|length == 0 -%}
|
|
nocron
|
|
{%- elif interval_value == 'custom' or interval_unit|length == 0 -%}
|
|
custom
|
|
{%- else -%}
|
|
{{ interval_value ~ interval_unit }}
|
|
{%- endif -%}
|
|
|
|
- name: Dev1 | Compute scroll24 value
|
|
delegate_to: localhost
|
|
set_fact:
|
|
scroll24_value: >-
|
|
{%- if not (scroll24_present | default(false)) -%}
|
|
missing
|
|
{%- elif scroll24_schedule == 'nocron' -%}
|
|
{{ scroll24_ver_clean | default('v1') }}_nocron
|
|
{%- elif scroll24_schedule == 'custom' -%}
|
|
{{ scroll24_ver_clean | default('v1') }}_custom
|
|
{%- else -%}
|
|
{{ scroll24_ver_clean | default('v1') }}_{{ scroll24_schedule }}
|
|
{%- endif -%}
|
|
|
|
- name: Dev1 | scroll24 debug
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "scroll24_present={{ scroll24_present | default(false) }}"
|
|
- "scroll24_ver_clean={{ scroll24_ver_clean | default('n/a') }}"
|
|
- "scroll24_cron_line={{ scroll24_cron_line | default('<none>') }}"
|
|
- "scroll24_schedule={{ scroll24_schedule | default('n/a') }}"
|
|
- "scroll24_value={{ scroll24_value | default('n/a') }}"
|
|
|
|
- name: Publish Dev1 scroll24 custom field
|
|
delegate_to: localhost
|
|
uri:
|
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
|
|
method: POST
|
|
user: "{{ rmq_user }}"
|
|
password: "{{ rmq_pass }}"
|
|
force_basic_auth: true
|
|
status_code: 200
|
|
headers: { content-type: "application/json" }
|
|
body_format: json
|
|
body:
|
|
properties: { content_type: "application/json" }
|
|
routing_key: "{{ control_queue }}"
|
|
payload: "{{ {
|
|
'inscope_device': (ansible_hostname|default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'scroll24',
|
|
'task_result': scroll24_value
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
# ----------------------- Dev1 wifidebug.sh check & publish -----------------------
|
|
- name: Dev1 | Check wifidebug.sh presence
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"test -f /root/wifidebug.sh && echo present || echo missing"
|
|
register: wd_present
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Set presence fact
|
|
delegate_to: localhost
|
|
set_fact:
|
|
wifidebug_present: "{{ (wd_present.stdout | default('') | trim) == 'present' }}"
|
|
|
|
- name: Dev1 | Read wifidebug.sh version (SCRIPTVERSION)
|
|
when: wifidebug_present | default(false)
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"grep -m1 'SCRIPTVERSION' /root/wifidebug.sh 2>/dev/null | awk -F'=' '{print \$2}' | tr -d '[:space:]' | sed -E 's/^v?([0-9].*)$/\1/'"
|
|
register: wd_version_raw
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Normalize wifidebug version
|
|
delegate_to: localhost
|
|
set_fact:
|
|
wd_version_clean: "{{ wd_version_raw.stdout | default('') | trim }}"
|
|
|
|
- name: Dev1 | Compute wifidebug value
|
|
delegate_to: localhost
|
|
set_fact:
|
|
wifidebug_value: >-
|
|
{%- if not (wifidebug_present | default(false)) -%}missing
|
|
{%- elif (wd_version_clean | default('') | length) > 0 -%}v{{ wd_version_clean }}
|
|
{%- else -%}n/a{%- endif -%}
|
|
|
|
- name: Publish Dev1 wifidebug custom field
|
|
delegate_to: localhost
|
|
uri:
|
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
|
|
method: POST
|
|
user: "{{ rmq_user }}"
|
|
password: "{{ rmq_pass }}"
|
|
force_basic_auth: true
|
|
status_code: 200
|
|
headers: { content-type: "application/json" }
|
|
body_format: json
|
|
body:
|
|
properties: { content_type: "application/json" }
|
|
routing_key: "{{ control_queue }}"
|
|
payload: "{{ {
|
|
'inscope_device': (ansible_hostname|default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'wifidebug',
|
|
'task_result': wifidebug_value
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
# ----------------------- Dev1 fw_printenv size → cpe_bootenv -----------------------
|
|
- name: Dev1 | Read fw_printenv size (line count)
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"fw_printenv 2>/dev/null | wc -l"
|
|
args:
|
|
executable: /bin/bash
|
|
register: dev1_bootenv_wc
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Normalize cpe_bootenv value
|
|
delegate_to: localhost
|
|
set_fact:
|
|
cpe_bootenv_value: >-
|
|
{{
|
|
'unavailable'
|
|
if (
|
|
(dev1_bootenv_wc.rc | default(1)) != 0 or
|
|
(dev1_bootenv_wc.stdout | default('') | trim) in ['', '0']
|
|
)
|
|
else (dev1_bootenv_wc.stdout | default('') | trim)
|
|
}}
|
|
|
|
- name: Publish Dev1 cpe_bootenv custom field
|
|
delegate_to: localhost
|
|
uri:
|
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
|
|
method: POST
|
|
user: "{{ rmq_user }}"
|
|
password: "{{ rmq_pass }}"
|
|
force_basic_auth: true
|
|
status_code: 200
|
|
headers: { content-type: "application/json" }
|
|
body_format: json
|
|
body:
|
|
properties: { content_type: "application/json" }
|
|
routing_key: "{{ control_queue }}"
|
|
payload: "{{ {
|
|
'inscope_device': (ansible_hostname|default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'cpe_bootenv',
|
|
'task_result': cpe_bootenv_value
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
|
|
# ----------------------- Dev2 auth + firmux -----------------------
|
|
- name: Try DEV2 login
|
|
when: tunnel_ok|default(false)
|
|
delegate_to: localhost
|
|
shell: |
|
|
PORT="{{ _local_port }}"
|
|
# First try: basicpass
|
|
if timeout {{ ssh_timeout }}s sshpass -f basicpass ssh {{ ssh_opts_common }} \
|
|
-o ConnectionAttempts=1 -o ConnectTimeout={{ ssh_timeout }} \
|
|
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" "echo ok" >/dev/null 2>&1; then
|
|
echo basicpass
|
|
exit 0
|
|
fi
|
|
|
|
# Second try: basicpass2
|
|
if timeout {{ ssh_timeout }}s sshpass -f basicpass2 ssh {{ ssh_opts_common }} \
|
|
-o ConnectionAttempts=1 -o ConnectTimeout={{ ssh_timeout }} \
|
|
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" "echo ok" >/dev/null 2>&1; then
|
|
echo basicpass2
|
|
exit 0
|
|
fi
|
|
|
|
# Both failed
|
|
echo NONE
|
|
exit 1
|
|
args:
|
|
executable: /bin/bash
|
|
register: dev2_auth
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Set dev2_passfile_used
|
|
delegate_to: localhost
|
|
set_fact:
|
|
dev2_passfile_used: "{{ (dev2_auth.rc == 0)|ternary(dev2_auth.stdout|trim,'NONE') }}"
|
|
|
|
- name: Dev2 | Auth debug
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "dev2_auth.rc={{ dev2_auth.rc | default('n/a') }}"
|
|
- "dev2_auth.stdout={{ dev2_auth.stdout | default('') | trim }}"
|
|
- "dev2_passfile_used={{ dev2_passfile_used }}"
|
|
|
|
- name: Dev2 | Read firmux (tunnel)
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used != 'NONE'
|
|
delegate_to: localhost
|
|
shell: |
|
|
PORT="{{ _local_port }}"
|
|
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
|
|
-o ConnectTimeout={{ ssh_timeout }} \
|
|
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
|
"cat /usr/lib/release/firmux 2>/dev/null || true"
|
|
args:
|
|
executable: /bin/bash
|
|
register: dev2_firmux
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
# ------ LLDP candidate IP when Dev2 login failed (tunnel up but Dev2 unreachable) ------
|
|
- name: Dev1 | Try LLDP-derived Dev2 IP (auth to Dev2 failed)
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
delegate_to: localhost
|
|
shell: |
|
|
DIGITS="{{ inventory_hostname | regex_replace('[^0-9]', '') }}"
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
|
"cat /var/run/lldp_server.json 2>/dev/null | grep ${DIGITS} -A 10 | grep address | grep -vE 'subtype|ipv6' | awk -F'\"' '{ print \$4 }' | head -n1"
|
|
register: lldp_ip_raw
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Show LLDP candidate IP (Dev2, after SSH auth failure)
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- >-
|
|
LLDP-derived Dev2 IP (raw best-effort):
|
|
{{
|
|
(lldp_ip_raw.stdout | default('') | trim)
|
|
if ((lldp_ip_raw.stdout | default('') | trim | length) > 0)
|
|
else '<none>'
|
|
}}
|
|
- >-
|
|
LLDP 10.0.0.0/8 usable:
|
|
{{
|
|
((lldp_ip_raw.stdout | default('') | trim) is match('^10\.'))
|
|
| ternary('yes (10/8)', 'no (not 10/8) or none')
|
|
}}
|
|
|
|
- name: Set LLDP IP fact (10.0.0.0/8 only)
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
delegate_to: localhost
|
|
set_fact:
|
|
lldp_dev2_ip_raw: "{{ lldp_ip_raw.stdout | default('') | trim }}"
|
|
lldp_dev2_ip: >-
|
|
{{
|
|
(lldp_ip_raw.stdout | default('') | trim)
|
|
if ((lldp_ip_raw.stdout | default('') | trim) is match('^10\.'))
|
|
else ''
|
|
}}
|
|
|
|
- name: Try DEV2 login via direct LLDP IP
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
- lldp_dev2_ip | length > 0
|
|
delegate_to: localhost
|
|
shell: |
|
|
for f in basicpass basicpass2; do
|
|
if timeout 20s sshpass -f "$f" ssh {{ ssh_opts_common }} \
|
|
-o ConnectionAttempts=1 -o ConnectTimeout=10 \
|
|
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" "echo ok" >/dev/null 2>&1; then
|
|
echo "$f"; exit 0
|
|
fi
|
|
done
|
|
exit 1
|
|
register: dev2_auth_lldp
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev2 | LLDP path selection debug
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
- lldp_dev2_ip | length > 0
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "LLDP candidate IP={{ lldp_dev2_ip }}"
|
|
- "LLDP auth rc={{ dev2_auth_lldp.rc | default('n/a') }}"
|
|
- "passfile (if any) will follow in next step"
|
|
|
|
- name: Set dev2_passfile_used_lldp
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
- lldp_dev2_ip | length > 0
|
|
delegate_to: localhost
|
|
set_fact:
|
|
dev2_passfile_used_lldp: >-
|
|
{{
|
|
((dev2_auth_lldp.rc | default(1)) == 0)
|
|
| ternary(dev2_auth_lldp.stdout | default('') | trim, 'NONE')
|
|
}}
|
|
|
|
- name: Dev2 | LLDP passfile decision
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
- lldp_dev2_ip | length > 0
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "Using passfile for LLDP path: {{ dev2_passfile_used_lldp }}"
|
|
|
|
- name: Dev2 | Check TCP/22 reachability (LLDP)
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
- lldp_dev2_ip | length > 0
|
|
delegate_to: localhost
|
|
shell: "nc -z -w3 {{ lldp_dev2_ip }} 22"
|
|
register: lldp_ssh_tcp
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev2 | Read firmux (direct LLDP IP)
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
- lldp_dev2_ip | length > 0
|
|
- dev2_passfile_used_lldp != 'NONE'
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
|
|
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
|
-o ConnectTimeout=15 \
|
|
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
|
|
"cat /usr/lib/release/firmux 2>/dev/null || true"
|
|
register: dev2_firmux_lldp
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
# ------ Mux: choose whichever firmux output succeeded ------
|
|
- name: Dev2 | Choose firmux output (tunnel or LLDP)
|
|
delegate_to: localhost
|
|
set_fact:
|
|
dev2_firmux_text: >-
|
|
{{
|
|
(dev2_firmux.stdout | default('') | trim)
|
|
if ((dev2_firmux.stdout | default('') | trim | length) > 0)
|
|
else (dev2_firmux_lldp.stdout | default('') | trim)
|
|
}}
|
|
|
|
- name: Dev2 | firmux selection debug
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "dev2_firmux_text='{{ dev2_firmux_text | default('') | regex_replace('\n','\\n') }}'"
|
|
- "indoor_fw_norm='{{ indoor_fw_norm | default('') }}'"
|
|
|
|
- name: Dev2 | Extract firmware version line
|
|
delegate_to: localhost
|
|
run_once: true
|
|
set_fact:
|
|
indoor_fw_norm: >-
|
|
{{
|
|
(
|
|
(dev2_firmux_text | default('') | regex_replace('\r',''))|split('\n')
|
|
| map('trim') | select('truthy') | list
|
|
) | select('match','(?i).*\\brev\\s*[0-9]+.*')
|
|
| list | first | default(
|
|
((dev2_firmux_text | default('') | regex_replace('\r',''))
|
|
| split('\n')|map('trim')|select('truthy')|list|first|default(''))
|
|
) | trim
|
|
}}
|
|
|
|
- name: Dev2 | firmux selection debug
|
|
delegate_to: localhost
|
|
debug:
|
|
msg:
|
|
- "dev2_firmux_text='{{ dev2_firmux_text | default('') | regex_replace('\n','\\n') }}'"
|
|
- "indoor_fw_norm='{{ indoor_fw_norm | default('') }}'"
|
|
|
|
- name: Publish Dev2 indoor_fwver
|
|
delegate_to: localhost
|
|
uri:
|
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
|
|
method: POST
|
|
user: "{{ rmq_user }}"
|
|
password: "{{ rmq_pass }}"
|
|
force_basic_auth: true
|
|
status_code: 200
|
|
headers: { content-type: "application/json" }
|
|
body_format: json
|
|
body:
|
|
properties: { content_type: "application/json" }
|
|
routing_key: "{{ control_queue }}"
|
|
payload: "{{ {
|
|
'inscope_device': (ansible_hostname|default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'indoor_fwver',
|
|
'task_result': (indoor_fw_norm if indoor_fw_norm|length>0 else 'unavailable')
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
# ----------------------- Dev2 fw_printenv size → indoor_bootvar -----------------------
|
|
- name: Dev2 | Read fw_printenv size (tunnel)
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used != 'NONE'
|
|
delegate_to: localhost
|
|
shell: |
|
|
PORT="{{ _local_port }}"
|
|
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
|
|
-o ConnectTimeout={{ ssh_timeout }} \
|
|
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
|
"fw_printenv 2>/dev/null | wc -l"
|
|
args:
|
|
executable: /bin/bash
|
|
register: dev2_bootenv_wc_tunnel
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev2 | Read fw_printenv size (LLDP)
|
|
when:
|
|
- tunnel_ok | default(false)
|
|
- dev2_passfile_used == 'NONE'
|
|
- lldp_dev2_ip | length > 0
|
|
- dev2_passfile_used_lldp is defined
|
|
- dev2_passfile_used_lldp != 'NONE'
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
|
|
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
|
-o ConnectTimeout=15 \
|
|
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
|
|
"fw_printenv 2>/dev/null | wc -l"
|
|
args:
|
|
executable: /bin/bash
|
|
register: dev2_bootenv_wc_lldp
|
|
changed_when: false
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev2 | Select fw_printenv size output
|
|
delegate_to: localhost
|
|
set_fact:
|
|
indoor_bootvar_raw: >-
|
|
{{
|
|
(dev2_bootenv_wc_tunnel.stdout | default('') | trim)
|
|
if (
|
|
(dev2_bootenv_wc_tunnel is defined) and
|
|
((dev2_bootenv_wc_tunnel.rc | default(1)) == 0) and
|
|
((dev2_bootenv_wc_tunnel.stdout | default('') | trim | length) > 0)
|
|
)
|
|
else (
|
|
(dev2_bootenv_wc_lldp.stdout | default('') | trim)
|
|
if (
|
|
(dev2_bootenv_wc_lldp is defined) and
|
|
((dev2_bootenv_wc_lldp.rc | default(1)) == 0) and
|
|
((dev2_bootenv_wc_lldp.stdout | default('') | trim | length) > 0)
|
|
)
|
|
else ''
|
|
)
|
|
}}
|
|
|
|
- name: Dev2 | Normalize indoor_bootvar value
|
|
delegate_to: localhost
|
|
set_fact:
|
|
indoor_bootvar_value: >-
|
|
{{
|
|
'unavailable'
|
|
if (indoor_bootvar_raw | default('') | trim) in ['', '0']
|
|
else (indoor_bootvar_raw | default('') | trim)
|
|
}}
|
|
|
|
- name: Publish Dev2 indoor_bootvar custom field
|
|
delegate_to: localhost
|
|
uri:
|
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
|
|
method: POST
|
|
user: "{{ rmq_user }}"
|
|
password: "{{ rmq_pass }}"
|
|
force_basic_auth: true
|
|
status_code: 200
|
|
headers: { content-type: "application/json" }
|
|
body_format: json
|
|
body:
|
|
properties: { content_type: "application/json" }
|
|
routing_key: "{{ control_queue }}"
|
|
payload: "{{ {
|
|
'inscope_device': (ansible_hostname|default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'indoor_bootvar',
|
|
'task_result': indoor_bootvar_value
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
|
|
|
|
post_tasks:
|
|
- name: "✅ Cleanup start"
|
|
debug:
|
|
msg: "Closing tunnel and removing temporary IP"
|
|
|
|
- name: Refresh ARP (cleanup)
|
|
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Close SSH ControlMaster
|
|
delegate_to: localhost
|
|
shell: "ssh -S '{{ _ctrl_sock|default('/dev/null') }}' -O exit 2>/dev/null || true"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Remove control dir
|
|
delegate_to: localhost
|
|
file:
|
|
path: "{{ _ctrl_dir|default('/tmp/none') }}"
|
|
state: absent
|
|
ignore_errors: true
|
|
|
|
- name: Remove temporary IP
|
|
raw: "{{ pathprefix }} ip a del {{ dev2_side_ip }} dev {{ dev1_iface }}"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: "✅ Completed | Device processed"
|
|
debug:
|
|
msg:
|
|
- "Device: {{ inventory_hostname }}"
|
|
- "Status: DONE (telemetry only)"
|