diff --git a/files/ansible-playbooks/update-indoor.yml b/files/ansible-playbooks/update-indoor.yml index 0626e06..9fc319d 100644 --- a/files/ansible-playbooks/update-indoor.yml +++ b/files/ansible-playbooks/update-indoor.yml @@ -1,6 +1,6 @@ --- # update-secondline.yml -- name: Second-line upgrade tunneled noninvasive engine +- name: Second-line indoor upgrade via DEV1 → tunnel → DEV2 (non-invasive control path) hosts: all gather_facts: no @@ -29,7 +29,7 @@ # SSH options used from controller ssh_opts_common: "-o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=1 -o ConnectTimeout=15" - # Image to stage on DEV2 noninvasive we do not run update -w here + # Image to stage on DEV2 (we validate first; the actual write happens later) image_filename: "fox200-2.2.1-r6801.bin" image_md5: "56b7211709de617e058b98d4204e2562" # Optional SHA256; leave empty to skip SHA256 checks @@ -52,21 +52,21 @@ pre_tasks: # ------------------------------- Hostname sanity DEV1 ------------------------------- - - name: Read DEV1 hostname from env busybox safe + - name: Read DEV1 hostname (busybox-safe) ansible.builtin.raw: > {{ pathprefix }} (cat /proc/sys/kernel/hostname 2>/dev/null || echo "") register: dev1_host_read changed_when: false - - name: Stop if DEV1 connected hostname differs from inventory + - name: Stop early if connected DEV1 hostname != inventory ansible.builtin.meta: end_host when: (dev1_host_read.stdout | trim | length > 0) and ((dev1_host_read.stdout | trim) != (inventory_hostname | string)) tasks: # ---------------------------- Idempotent temp IP on DEV1 ---------------------------- - - name: Add temporary IP on DEV1 idempotent treat File exists as OK + - name: Add temporary IP on DEV1 (tolerate 'File exists') ansible.builtin.raw: > {{ pathprefix }} ip a add {{ dev2_side_ip }} dev {{ dev1_iface }} @@ -77,7 +77,7 @@ and ('File exists' not in (add_ip.stdout | default(''))) and ('File exists' not in (add_ip.stderr | default(''))) - - name: Debug add ip results + - name: Debug: result of adding temp IP to DEV1 ansible.builtin.debug: msg: - "add_ip.rc={{ add_ip.rc | default('') }}" @@ -85,19 +85,19 @@ - "add_ip.stderr={{ (add_ip.stderr | default('')) | trim }}" # ---------------------------- Discover MAC via bridge FDB and add static ARP on DEV1 ---------------------------- - - name: Read dynamic MAC on eth0 behind {{ dev1_iface }} via bridge fdb + - name: Discover DEV2 MAC via bridge fdb on DEV1 (best-effort) ansible.builtin.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 - - name: Set dev2_mac from fdb scan (if any) + - name: Capture discovered DEV2 MAC (if any) ansible.builtin.set_fact: dev2_mac: "{{ (dev2_mac_scan.stdout | default('') ) | trim }}" changed_when: false - - name: Remove current ARP entry for DEV2 on DEV1 (best-effort) + - name: Clear existing ARP entry for DEV2 on DEV1 (best-effort) ansible.builtin.raw: > {{ pathprefix }} ip neigh del {{ dev2_host }} dev {{ dev1_iface }} 2>/dev/null || true @@ -105,7 +105,7 @@ changed_when: false failed_when: false - - name: Add static ARP for DEV2 on DEV1 + - name: Add static ARP entry on DEV1 (locks DEV2 IP → discovered MAC) ansible.builtin.raw: > {{ pathprefix }} ip neigh add {{ dev2_host }} lladdr {{ dev2_mac }} dev {{ dev1_iface }} nud permanent @@ -116,7 +116,7 @@ and ('File exists' not in (dev2_arp_add.stdout | default(''))) and ('File exists' not in (dev2_arp_add.stderr | default(''))) - - name: Debug static ARP result + - name: Debug: ARP action summary on DEV1 ansible.builtin.debug: msg: - "dev2_mac={{ dev2_mac | default('UNSET') }}" @@ -124,13 +124,13 @@ - "arp_add.out={{ (dev2_arp_add.stdout | default('')) | trim }}" - "arp_add.err={{ (dev2_arp_add.stderr | default('')) | trim }}" - - name: Skip ARP add because MAC not found + - name: Note: skipping static ARP add (no MAC discovered) when: dev2_mac is not defined or dev2_mac | length == 0 ansible.builtin.debug: msg: "No suitable dynamic MAC found via bridge fdb; skipping static ARP add on DEV1" # ---------------------------- Local tunnel preparation ---------------------------- - - name: Pick a free local port for the tunnel controller + - name: Pick a free local TCP port for the tunnel (controller side) delegate_to: localhost ansible.builtin.shell: | set -e @@ -153,22 +153,22 @@ register: pick_port changed_when: false - - name: Stop if no free local port + - name: Stop if no free local port was found ansible.builtin.meta: end_host when: (pick_port.stdout | trim | length) == 0 - - name: Record chosen local port and control directory + - name: Record chosen local port and create control dir for SSH ControlMaster delegate_to: localhost ansible.builtin.set_fact: _local_port: "{{ pick_port.stdout | trim }}" _ctrl_dir: "{{ lookup('ansible.builtin.pipe', 'mktemp -d') }}" - - name: Build tunnel control socket path + - name: Build path for SSH ControlMaster socket delegate_to: localhost ansible.builtin.set_fact: _ctrl_sock: "{{ _ctrl_dir }}/ssh_tunnel_ctl" - - name: Debug chosen tunnel params + - name: Debug: tunnel parameters (controller side) delegate_to: localhost ansible.builtin.debug: msg: @@ -176,11 +176,11 @@ - "ctrl_dir={{ _ctrl_dir }}" - "ctrl_sock={{ _ctrl_sock }}" - - name: issue arping with own IP + - name: Refresh ARP on DEV1’s LAN (send unsolicited ARP from temporary IP) ansible.builtin.raw: arping -U -I eth0 192.168.1.11 -c 3 # ---------------------------- Start SSH local forward via DEV1 ---------------------------- - - name: Start SSH tunnel via DEV1 background ControlMaster + - name: Start SSH ControlMaster and forward 127.0.0.1:local_port → DEV2:22 via DEV1 delegate_to: localhost ansible.builtin.shell: | set -e @@ -195,13 +195,13 @@ register: start_tunnel changed_when: true - - name: Small delay for tunnel to settle + - name: Wait a moment for tunnel to settle delegate_to: localhost ansible.builtin.wait_for: timeout: 1 changed_when: false - - name: Verify tunnel master running proper O check with destination + - name: Verify ControlMaster is running (ssh -O check) delegate_to: localhost ansible.builtin.shell: | set -e @@ -210,7 +210,7 @@ register: tun_check changed_when: false - - name: Debug tunnel check + - name: Debug: ControlMaster status delegate_to: localhost ansible.builtin.debug: msg: @@ -218,7 +218,7 @@ - "tunnel_check.out={{ (tun_check.stdout | default('')) | trim }}" # ---------------------------- Controller-side sanity for DEV2 auth ---------------------------- - - name: Sanity confirm tunnel TCP reachability to DEV2 + - name: Probe TCP reachability to DEV2 through the tunnel (nc) delegate_to: localhost ansible.builtin.shell: | set -e @@ -227,7 +227,7 @@ changed_when: false ignore_errors: true - - name: Debug reachability result + - name: Debug: tunnel reachability result delegate_to: localhost ansible.builtin.debug: msg: @@ -235,11 +235,11 @@ - "nc.stdout={{ (nc_probe.stdout | default('')) | trim }}" - "nc.stderr={{ (nc_probe.stderr | default('')) | trim }}" - - name: Stop if tunnel TCP check failed + - name: Stop if tunnel TCP probe failed ansible.builtin.meta: end_host when: nc_probe.rc != 0 - - name: Sanity show presence and permissions of passfiles on controller + - name: Show passfiles available on controller (ls) delegate_to: localhost ansible.builtin.shell: | set -e @@ -247,15 +247,15 @@ register: dev2_ls changed_when: false - - name: Debug passfiles listing + - name: Debug: passfiles presence delegate_to: localhost ansible.builtin.debug: msg: - "{{ (dev2_ls.stdout | default('')) | trim }}" - "{{ (dev2_ls.stderr | default('')) | trim }}" - # ---------------------------- Pick DEV2 password root only ---------------------------- - - name: Try DEV2 login with basicpass root + # ---------------------------- Pick DEV2 password for root ---------------------------- + - name: Try DEV2 login with 'basicpass' (root) delegate_to: localhost ansible.builtin.shell: | set -e @@ -270,14 +270,14 @@ changed_when: false ignore_errors: true - - name: Select basicpass as working passfile + - name: Select 'basicpass' if previous login succeeded when: dev2_try_basicpass.rc == 0 delegate_to: localhost ansible.builtin.set_fact: dev2_passfile_used: "basicpass" changed_when: false - - name: Try DEV2 login with basicpass2 root only if first failed + - name: Try DEV2 login with 'basicpass2' (only if first failed) when: dev2_passfile_used is not defined delegate_to: localhost ansible.builtin.shell: | @@ -293,21 +293,21 @@ changed_when: false ignore_errors: true - - name: Select basicpass2 as working passfile + - name: Select 'basicpass2' if second login succeeded when: dev2_passfile_used is not defined and dev2_try_basicpass2.rc == 0 delegate_to: localhost ansible.builtin.set_fact: dev2_passfile_used: "basicpass2" changed_when: false - - name: Set dev2_passfile_used to NONE if neither worked + - name: Mark DEV2 auth as NONE if both attempts failed when: dev2_passfile_used is not defined delegate_to: localhost ansible.builtin.set_fact: dev2_passfile_used: "NONE" changed_when: false - - name: Debug selected DEV2 passfile or NONE + - name: Debug: selected DEV2 passfile delegate_to: localhost ansible.builtin.debug: msg: @@ -333,13 +333,13 @@ register: dev2_host_read changed_when: false - - name: Normalize hostnames for strict compare + - name: Normalize hostnames for strict compare (inventory/DEV1/DEV2) ansible.builtin.set_fact: _inv_hn: "{{ (inventory_hostname | string) | trim | regex_replace('\\r+$','') | lower }}" _dev1_hn: "{{ (dev1_host_read.stdout | default('')) | trim | regex_replace('\\r+$','') | lower }}" _dev2_hn: "{{ (dev2_host_read.stdout | default('')) | trim | regex_replace('\\r+$','') | lower }}" - - name: Debug hostname bytes (hex) + - name: Debug: normalized hostnames (JSON-escaped) delegate_to: localhost ansible.builtin.debug: msg: @@ -348,7 +348,7 @@ - "dev2={{ _dev2_hn | tojson }}" # ---------------- Hostname equality guard (soft-journal and stop) ---------------- - - name: Enforce DEV2 hostname equals inventory and DEV1 + - name: Guard: DEV2 hostname must equal inventory AND DEV1 (prevents IP churn mistakes) block: - name: Fail if DEV2 hostname differs from inventory/DEV1 ansible.builtin.fail: @@ -365,12 +365,12 @@ _prep_blocked: false delegate_to: localhost - - name: Append hostname mismatch to journal + - name: Append hostname mismatch info to journal ansible.builtin.set_fact: _journal: "{{ _journal + [ 'Hostname mismatch: DEV2=' ~ _dev2_hn ~ ', inventory=' ~ _inv_hn ~ ', DEV1=' ~ _dev1_hn ] }}" delegate_to: localhost - - name: Build control queue payload for indoor aborted journal (hostname) + - name: Build control queue payload for indoor aborted journal (hostname check) ansible.builtin.set_fact: journal_indoor_aborted: inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}" @@ -379,7 +379,7 @@ indoor: update aborted with following reason(s): {{ (_journal | default([])) | join('; ') }} delegate_to: localhost - - name: Publish indoor aborted journal to control queue (hostname) + - name: Publish indoor aborted journal (hostname mismatch) ansible.builtin.uri: url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish" method: POST @@ -408,17 +408,17 @@ ansible.builtin.meta: end_host # ---------------------------- SOFT-FAIL JOURNAL INIT + PREP MARKER CHECK ---------------------------- - - name: Initialize journal and flags + - name: Init soft-fail journal flags ansible.builtin.set_fact: _journal: [] _prep_blocked: false _blocked: false - - name: Build specific marker filename (no .bin) + - name: Build specific image marker path on DEV2 (/tmp/prepared_for_) ansible.builtin.set_fact: _marker_specific: "/tmp/prepared_for_{{ image_filename | regex_replace('\\.bin$','') }}" - - name: Check for any preparation markers on DEV2 (wildcard count) + - name: Count existing preparation markers on DEV2 (best-effort) when: dev2_passfile_used != "NONE" delegate_to: localhost ansible.builtin.shell: | @@ -437,7 +437,7 @@ changed_when: false ignore_errors: true - - name: Soft-block if any prep markers already present + - name: Soft-block if preparation markers already present on DEV2 when: dev2_prep_count is defined and (dev2_prep_count.stdout is defined) and ((dev2_prep_count.stdout | trim | int) > 0) ansible.builtin.set_fact: _prep_blocked: true @@ -445,7 +445,7 @@ _journal: "{{ _journal + [ 'Preparation markers already present on DEV2 (count=' ~ (dev2_prep_count.stdout | trim) ~ '). Skipping staging/write' ] }}" # ---------------------------- DEV2 version firmux primary check ---------------------------- - - name: Read DEV2 firmux release + - name: Read DEV2 /usr/lib/release/firmux (if present) when: dev2_passfile_used != "NONE" delegate_to: localhost ansible.builtin.shell: | @@ -464,14 +464,14 @@ changed_when: false ignore_errors: true - - name: Debug firmux content DEV2 + - name: Debug: DEV2 firmux banner (if any) when: dev2_firmux is defined delegate_to: localhost ansible.builtin.debug: msg: "DEV2 firmux={{ (dev2_firmux.stdout | default('')) | trim }}" # ---------------------------- Normalize rebootin early (HOURS) ---------------------------- - - name: "Normalize rebootin (phase 1: raw/is_now/is_int/HOURS)" + - name: Normalize 'rebootin' (detect 'now' or integer HOURS) delegate_to: localhost ansible.builtin.set_fact: _reboot_raw: "{{ rebootin | default('') | string | trim | lower }}" @@ -483,15 +483,10 @@ else (rebootin | int if ((rebootin | default('') | string | trim) is match('^\\d+$') ) else -1) }} - - name: "Normalize rebootin (phase 2: requested flag)" + - name: Compute reboot flags (requested? seconds? minutes?) delegate_to: localhost ansible.builtin.set_fact: _reboot_requested: "{{ _reboot_is_now or _reboot_is_int }}" - - # Compute effective seconds from HOURS (0h → ~20 seconds grace); clamp non-negative - - name: "Normalize rebootin (phase 3: compute seconds from HOURS)" - delegate_to: localhost - ansible.builtin.set_fact: _reboot_seconds: >- {{ 20 @@ -501,28 +496,31 @@ if (_reboot_requested | default(false)) else 0 }} - - # Derive minutes from seconds (non-negative), for tasks/summary that expect minutes - - name: "Normalize rebootin (phase 4: derive minutes)" - delegate_to: localhost - ansible.builtin.set_fact: _reboot_minutes: "{{ ((_reboot_seconds | int) // 60) if (_reboot_seconds | int) > 0 else 0 }}" - # ---------------------------- Journal: indoor start (after we know we can proceed) ---------------------------- - - name: Build control queue payload for indoor start journal + - name: Debug: reboot plan summary + delegate_to: localhost + ansible.builtin.debug: + msg: + - "reboot_requested={{ _reboot_requested | default(false) }}" + - "reboot_seconds={{ _reboot_seconds | default(0) }}" + - "reboot_minutes={{ _reboot_minutes | default(0) }}" + + # ---------------------------- Journal: indoor start (we can proceed) ---------------------------- + - name: Build control queue payload for 'indoor start' journal ansible.builtin.set_fact: journal_indoor_start: inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}" task_name: "journal_add" task_result: >- - Indoor: Dev2 is reachable, starting with update. + Indoor: Dev2 is reachable, starting update. image={{ image_filename }}, md5={{ image_md5 }}, firmux={{ (dev2_firmux.stdout | default('unknown')) | trim }}, reboot={{ 'in ' ~ (_reboot_seconds | int) ~ ' seconds' if (_reboot_is_now and (_reboot_minutes | int) == 0) else (rebootin | default('') | trim) }} when: dev2_passfile_used != "NONE" delegate_to: localhost - - name: Publish indoor start journal to control queue + - name: Publish 'indoor start' journal to control queue ansible.builtin.uri: url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish" method: POST @@ -548,20 +546,20 @@ when: journal_indoor_start is defined delegate_to: localhost - # ---------------------------- Stage image on DEV2 no write ---------------------------- - - name: Check local presence of image file + # ---------------------------- Stage image on DEV2 (no write yet) ---------------------------- + - name: Check local presence of image file on controller delegate_to: localhost ansible.builtin.stat: path: "{{ image_filename }}" register: local_img - - name: Soft-block if image missing locally + - name: Soft-block if local image is missing when: not local_img.stat.exists ansible.builtin.set_fact: _blocked: true _journal: "{{ _journal + [ 'Local image missing on controller: ' ~ image_filename ] }}" - - name: Compute local md5 of the image + - name: Compute local md5 of the image (controller) when: local_img.stat.exists delegate_to: localhost ansible.builtin.shell: | @@ -590,7 +588,7 @@ changed_when: false ignore_errors: true - - name: Soft-block if local sha256 mismatch or unavailable + - name: Soft-block if local sha256 mismatch/unavailable when: local_img.stat.exists and (image_sha256 | default('') | length) > 0 and (local_sha256 is not defined or (local_sha256.stdout | trim) != (image_sha256 | trim)) ansible.builtin.set_fact: _prep_blocked: true @@ -598,7 +596,7 @@ _journal: "{{ _journal + [ 'Local sha256 mismatch/unavailable: have=' ~ ((local_sha256.stdout | default('NA')) | trim) ~ ' expected=' ~ (image_sha256 | trim) ] }}" # fw_printenv health before upload (soft-fail) - - name: Read fw_printenv size (line count) on DEV2 + - name: Read fw_printenv size (line count) on DEV2 (soft health) when: local_img.stat.exists and dev2_passfile_used != "NONE" and not (_blocked | default(false)) delegate_to: localhost ansible.builtin.shell: | @@ -616,14 +614,14 @@ changed_when: false ignore_errors: true - - name: Soft-block if fw_printenv too small + - name: Soft-block if fw_printenv too small (<30 lines) when: dev2_fwenv_wc is defined and (dev2_fwenv_wc.stdout is defined) and ((dev2_fwenv_wc.stdout | trim | int) < 30) ansible.builtin.set_fact: _prep_blocked: true _blocked: true _journal: "{{ _journal + [ 'fw_printenv too small on DEV2: ' ~ (dev2_fwenv_wc.stdout | trim) ~ ' lines (<30). Skipping image staging' ] }}" - - name: Check existing image md5 on DEV2 NOFILE if absent + - name: Check existing DEV2 image md5 (NOFILE if missing) when: local_img.stat.exists and dev2_passfile_used != "NONE" and not (_blocked | default(false)) delegate_to: localhost ansible.builtin.shell: | @@ -640,7 +638,7 @@ register: dev2_md5_before changed_when: false - - name: Copy image to DEV2 via tunnel only if missing or md5 mismatch + - name: Copy image to DEV2 if missing or md5 mismatch when: local_img.stat.exists and dev2_passfile_used != "NONE" and not (_blocked | default(false)) and ((dev2_md5_before.stdout | trim) != image_md5) delegate_to: localhost ansible.builtin.shell: | @@ -655,7 +653,7 @@ register: scp_push changed_when: true - - name: Compute md5 of image on DEV2 after copy or if exists + - name: Compute md5 of image on DEV2 after copy (or if already present) when: local_img.stat.exists and dev2_passfile_used != "NONE" and not (_blocked | default(false)) delegate_to: localhost ansible.builtin.shell: | @@ -672,7 +670,7 @@ register: dev2_md5_after changed_when: false - - name: Verify DEV2 md5 matches expected (soft-fail journal) + - name: Soft-block if DEV2 md5 != expected when: local_img.stat.exists and dev2_passfile_used != "NONE" and not (_blocked | default(false)) and ((dev2_md5_after.stdout | trim) != image_md5) ansible.builtin.set_fact: _prep_blocked: true @@ -705,7 +703,7 @@ _blocked: true _journal: "{{ _journal + [ 'Remote sha256 mismatch/unavailable on DEV2: have=' ~ ((dev2_sha256_after.stdout | default('NA')) | trim) ~ ' expected=' ~ (image_sha256 | trim) ] }}" - - name: Run noninvasive update check on DEV2 update c + - name: Pre-validate image on DEV2 with 'update -c' (non-invasive) when: local_img.stat.exists and dev2_passfile_used != "NONE" and not (_blocked | default(false)) delegate_to: localhost ansible.builtin.shell: | @@ -723,13 +721,13 @@ register: dev2_update_check changed_when: false - - name: Debug update c output DEV2 + - name: Debug: output from 'update -c' when: dev2_update_check is defined delegate_to: localhost ansible.builtin.debug: msg: "{{ (dev2_update_check.stdout | default('')) | trim }}" - - name: Soft-block if update -c did not say valid + - name: Soft-block if 'update -c' did not return 'valid' when: dev2_update_check is defined and not ((dev2_update_check.stdout | default('') | lower) is search('valid')) ansible.builtin.set_fact: _prep_blocked: true @@ -737,7 +735,7 @@ _journal: "{{ _journal + [ 'update -c did not return valid on DEV2; output=' ~ ((dev2_update_check.stdout | default('')) | trim) ] }}" # ---------------------------- Journal: indoor aborted (if any blockers) ---------------------------- - - name: Build control queue payload for indoor aborted journal + - name: Build control queue payload for 'indoor aborted' journal ansible.builtin.set_fact: journal_indoor_aborted: inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}" @@ -747,7 +745,7 @@ when: (_blocked | default(false)) delegate_to: localhost - - name: Publish indoor aborted journal to control queue + - name: Publish 'indoor aborted' journal to control queue ansible.builtin.uri: url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish" method: POST @@ -774,10 +772,10 @@ delegate_to: localhost # ============================ ACTUAL UPGRADE WRITE + BANK FLIP (only if not blocked) ============================ - - name: Upgrade write and flip block + - name: Upgrade write and bank flip on DEV2 (guarded by soft-block) when: not (_blocked | default(false)) block: - - name: Write image on DEV2 (this will take a while) + - name: Write image to inactive bank on DEV2 (update -w) delegate_to: localhost ansible.builtin.shell: | set -e @@ -795,7 +793,7 @@ changed_when: true failed_when: dev2_up_write.stdout is not search('update is complete') - - name: Read current active partition on DEV2 + - name: Read current active partition on DEV2 (before flip) delegate_to: localhost ansible.builtin.shell: | set -e @@ -813,12 +811,12 @@ changed_when: false failed_when: (dev2_active_before.stdout | trim) not in ['1','2'] - - name: Determine new active value for DEV2 + - name: Determine new active value for DEV2 (flip 1↔2) when: dev2_active_before is defined and (dev2_active_before.stdout is defined) and ((dev2_active_before.stdout | trim) in ['1','2']) ansible.builtin.set_fact: dev2_new_active: "{{ '1' if (dev2_active_before.stdout | trim) == '2' else '2' }}" - - name: Switch active partition to {{ dev2_new_active }} + - name: Set fw_setenv active={{ dev2_new_active }} on DEV2 when: dev2_new_active is defined delegate_to: localhost ansible.builtin.shell: | @@ -836,7 +834,7 @@ register: dev2_setenv_out changed_when: true - - name: Verify active partition flipped on DEV2 + - name: Verify active partition flipped on DEV2 (read back) when: dev2_new_active is defined delegate_to: localhost ansible.builtin.shell: | @@ -855,7 +853,7 @@ changed_when: false failed_when: (dev2_active_after.stdout | trim) != (dev2_new_active | string) - - name: Create specific prep marker on DEV2 (no generic) + - name: Create specific prep marker on DEV2 for this image delegate_to: localhost ansible.builtin.shell: | set -e @@ -874,7 +872,7 @@ ignore_errors: true # ---------------------------- Reboot scheduling (normalized) ---------------------------- - - name: Schedule delayed reboot on DEV2 minutes + - name: Schedule DEV2 reboot after computed delay (seconds) when: _reboot_requested and (_reboot_minutes | int) >= 0 and dev2_passfile_used != "NONE" and not (_blocked | default(false)) delegate_to: localhost ansible.builtin.shell: | @@ -894,7 +892,7 @@ changed_when: true ignore_errors: true - - name: Build control queue payload for indoor-restart-scheduled tag + - name: Build 'indoor-restart-scheduled' tag payload ansible.builtin.set_fact: tag_restart_sched_payload: inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}" @@ -903,7 +901,7 @@ when: _reboot_requested and (_reboot_minutes | int) >= 0 and dev2_passfile_used != "NONE" and not (_blocked | default(false)) delegate_to: localhost - - name: Publish indoor-restart-scheduled tag to control queue via RabbitMQ HTTP API + - name: Publish 'indoor-restart-scheduled' tag to control queue ansible.builtin.uri: url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish" method: POST @@ -929,19 +927,28 @@ when: tag_restart_sched_payload is defined delegate_to: localhost - - name: Note reboot request bad format + - name: Note: reboot was requested but value is invalid (format warning) when: (rebootin | default('') | string | trim | length) > 0 and not _reboot_requested ansible.builtin.debug: msg: "Reboot requested but value '{{ rebootin | string | trim }}' is invalid; not applied" # ---------------------------- Journal: indoor updated and reboot schedule ---------------------------- - - name: Build control queue payload for indoor updated journal + - name: Compute write success flag (_write_success) ansible.builtin.set_fact: _write_success: "{{ (dev2_up_write.stdout | default('')) is search('update is complete') if (dev2_up_write is defined) else false }}" when: not (_blocked | default(false)) delegate_to: localhost - - name: Build indoor updated journal payload text + - name: Debug: write result and active banks (quick summary) + when: not (_blocked | default(false)) + delegate_to: localhost + ansible.builtin.debug: + msg: + - "write_success={{ _write_success | default(false) }}" + - "active_before={{ (dev2_active_before.stdout | default('NA')) | trim }}" + - "active_after={{ (dev2_active_after.stdout | default('NA')) | trim }}" + + - name: Build 'indoor updated' journal payload text ansible.builtin.set_fact: journal_indoor_updated: inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}" @@ -958,7 +965,7 @@ when: not (_blocked | default(false)) and (_write_success | bool) delegate_to: localhost - - name: Publish indoor updated journal to control queue + - name: Publish 'indoor updated' journal to control queue ansible.builtin.uri: url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish" method: POST @@ -984,9 +991,8 @@ when: journal_indoor_updated is defined delegate_to: localhost - # ──────────────────────────────── NEW: derive expected version for the checker ──────────────────────────────── - # Schedule window (we only send after-upgrade if write succeeded & not blocked) - - name: Init after-upgrade scheduling vars (attempt 1 + delay from reboot + 6m) + # ──────────────────────────────── Derive expected version for the checker (strict) ──────────────────────────────── + - name: Init after-upgrade scheduling vars (attempt=1, delay=reboot + 6m) ansible.builtin.set_fact: au_attempt: 1 au_max_attempts: 3 @@ -994,15 +1000,14 @@ when: not (_blocked | default(false)) and (_write_success | bool) delegate_to: localhost - - name: Generate correlation ID and original emitted timestamp + - name: Generate correlation ID and UTC timestamp (for after-upgrade tracking) ansible.builtin.set_fact: au_correlation_id: "{{ lookup('pipe', 'date +%s%N | sha1sum | cut -c1-12') }}" au_original_emitted_at: "{{ lookup('pipe', 'date -u +%FT%TZ') }}" when: au_delay_sec is defined delegate_to: localhost - # Strict, single-source expected version: "X.Y.Z-rNNNN" from image_filename - - name: Compute expected firmware core "X.Y.Z-rNNNN" from image filename (strict) + - name: Derive expected target_version from image filename (strict: X.Y.Z-rNNNN) delegate_to: localhost ansible.builtin.set_fact: expected_fw_core: >- @@ -1012,19 +1017,7 @@ }} when: au_delay_sec is defined - - name: Ensure expected_fw_core is set (strict parse from filename) - delegate_to: localhost - ansible.builtin.set_fact: - expected_fw_core: >- - {{ - expected_fw_core | default( - image_filename - | regex_replace('.*?([0-9]+\\.[0-9]+\\.[0-9]+)-r([0-9]+).*','\\1-r\\2') - ) - }} - when: au_delay_sec is defined - - - name: Debug derived target version + - name: Debug: derived expected target_version for checker delegate_to: localhost ansible.builtin.debug: msg: @@ -1047,13 +1040,16 @@ when: au_delay_sec is defined delegate_to: localhost - - name: Debug x-delay about to be sent (ms) + - name: Debug: after-upgrade plan (routing + delay + version) ansible.builtin.debug: - msg: "x-delay(ms) = {{ (au_delay_sec | int) * 1000 }}" + msg: + - "routing_key={{ afterupgrade_routing_key }}" + - "x-delay(ms)={{ (au_delay_sec | int) * 1000 }}" + - "target_version={{ afterupgrade_payload.target_version | default('NA') }}" when: afterupgrade_payload is defined delegate_to: localhost - - name: Publish delayed after-upgrade check to holding exchange + - name: Publish delayed after-upgrade check (headers.x-delay) to holding exchange ansible.builtin.uri: url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ 'deviceconfig.delayed' | urlencode }}/publish" method: POST @@ -1080,8 +1076,8 @@ when: afterupgrade_payload is defined delegate_to: localhost - # ---------------------------- summary ---------------------------- - - name: Summary show key results + # ---------------------------- Final operator summary (concise) ---------------------------- + - name: Summary: key outcomes (one-liners) delegate_to: localhost ansible.builtin.debug: msg: @@ -1104,28 +1100,28 @@ - "journal={{ (_journal | default([])) | join(' || ') }}" post_tasks: - - name: Cleanup always + - name: Cleanup (always) block: - ansible.builtin.debug: - msg: "entering cleanup block" + msg: "Entering cleanup block" changed_when: false delegate_to: localhost always: - - name: Close tunnel best effort + - name: Close SSH ControlMaster (best-effort) delegate_to: localhost ansible.builtin.shell: | ssh -S "{{ _ctrl_sock | default('/dev/null') }}" -O exit 2>/dev/null || true changed_when: false ignore_errors: true - - name: Remove control dir best effort + - name: Remove tunnel control dir (best-effort) delegate_to: localhost ansible.builtin.file: path: "{{ _ctrl_dir | default('/tmp/none') }}" state: absent ignore_errors: true - - name: Remove temporary IP on DEV1 idempotent Cannot assign requested address OK + - name: Remove temporary IP on DEV1 (tolerate 'Cannot assign requested address') ansible.builtin.raw: > {{ pathprefix }} ip a del {{ dev2_side_ip }} dev {{ dev1_iface }} @@ -1136,7 +1132,7 @@ and ('Cannot assign requested address' not in (del_ip.stdout | default(''))) and ('Cannot assign requested address' not in (del_ip.stderr | default(''))) - - name: Debug del ip results + - name: Debug: temp IP removal result ansible.builtin.debug: msg: - "del_ip.rc={{ del_ip.rc | default('') }}"