This commit is contained in:
2026-01-20 13:59:41 +02:00
parent 5be0f3684d
commit 0fc35c61fe

View File

@@ -1,18 +1,7 @@
# ptsd_reacquire_attempt.yml — DHCP reacquire verification + CASE A cleanup (standalone)
# Purpose:
# 1) Re-run Phase 0 connectivity to DEV2 (same logic as main playbook)
# 2) Verify migrated DHCP state:
# - ppp0 is absent
# - eth0.4000 exists AND has IPv4
# 3) If CASE A confirmed (DHCP ok, no PPP), perform minimal cleanup step:
# - detect pending reboot process; if found, kill it
# Maintain cumulative cleanup report and print at end.
#
# Notes:
# - Does NOT respect /tmp/ptsd.inprogress (verification runs while migration is in progress).
# - Does NOT do DEV1 teardown cleanup here (no tunnel/IP removal); this is intentional.
# ptsd_reacquire_attempt.yml
# PTSD DHCP Reacquire Attempt | Phase 0 | Connectivity + wrapper + DHCP state check + case A cleanup
- name: "PTSD DHCP Reacquire Attempt | Phase 0 | Connectivity + wrapper + DHCP state check + CASE A cleanup"
- name: "PTSD DHCP Reacquire Attempt | Phase 0 | Connectivity + wrapper + DHCP state check"
hosts: all
gather_facts: no
@@ -72,7 +61,6 @@
((dev1_host_read.stdout | trim) != (inventory_hostname | string))
tasks:
# ============================ PHASE 0: LLDP DISCOVERY (IPv4 + IPv6) ============================
- name: Compute hostname digits key for LLDP lookup (DEV2)
ansible.builtin.set_fact:
dev2_lldp_digits: "{{ (inventory_hostname | string) | regex_replace('[^0-9]', '') }}"
@@ -132,7 +120,6 @@
- "LLDP IPv4 class={{ lldp_ip_class | default('none') }}"
- "LLDP IPv6 candidate={{ lldp_dev2_ip6 | default('<none>') }}"
# ============================ PHASE 0: PRIMARY PATH DECISION ============================
- name: Override dev2_host from LLDP when candidate is 192.168.x.x
when: (lldp_ip_class | trim) == "192_168" and (lldp_dev2_ip | trim | length > 0)
delegate_to: localhost
@@ -146,7 +133,6 @@
dev2_conn_method: "{{ 'direct_lldp' if ((lldp_ip_class | trim) == '10') else 'tunnel' }}"
changed_when: false
# ============================ PHASE 0: DIRECT LLDP IPv4 AUTH (10.x) ============================
- name: Try DEV2 login via direct LLDP IPv4 (10.x) with basicpass
when: dev2_conn_method == "direct_lldp"
delegate_to: localhost
@@ -189,7 +175,6 @@
dev2_passfile_used_direct: "basicpass2"
changed_when: false
# ============================ PHASE 0: TUNNEL PREP (DEV1 temp IP + ARP + tunnel) ============================
- name: Add temporary IP on DEV1 (tolerate 'File exists')
when: dev2_conn_method == "tunnel"
ansible.builtin.raw: >
@@ -291,7 +276,7 @@
_ctrl_sock: "{{ (mktemp_dir.stdout | trim) }}/ssh_tunnel_ctl"
changed_when: false
- name: Start SSH ControlMaster and forward 127.0.0.1:local_port DEV2:22 via DEV1
- name: Start SSH ControlMaster and forward 127.0.0.1:local_port to DEV2 22 via DEV1
when: dev2_conn_method == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
@@ -363,145 +348,6 @@
dev2_passfile_used_tunnel: "basicpass2"
changed_when: false
# ============================ PHASE 0: SAFETY GUARD (MAC CHECK) ============================
- name: Read remote eth0 MAC via tunnel (guard, sanitized)
when:
- dev2_conn_method == "tunnel"
- (dev2_passfile_used_tunnel | default('NONE')) != "NONE"
- (dev2_mac | default('') | length) > 0
delegate_to: localhost
ansible.builtin.shell: |
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used_tunnel }}" ssh {{ ssh_opts_common }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
"cat /sys/class/net/eth0/address 2>/dev/null || ip link show eth0 2>/dev/null" 2>&1
args: { executable: /bin/bash }
register: dev2_eth0_mac_raw
changed_when: false
failed_when: false
- name: Normalize remote eth0 MAC (extract last MAC-like token)
when:
- dev2_conn_method == "tunnel"
- (dev2_passfile_used_tunnel | default('NONE')) != "NONE"
- (dev2_mac | default('') | length) > 0
delegate_to: localhost
ansible.builtin.set_fact:
remote_eth0_mac: >-
{{
(
(dev2_eth0_mac_raw.stdout | default('') | regex_replace('\r','')) ~ "\n" ~
(dev2_eth0_mac_raw.stderr | default('') | regex_replace('\r',''))
)
| regex_findall('([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})')
| last
| default('')
| lower
}}
changed_when: false
- name: Abort if remote eth0 MAC != discovered DEV2 MAC
when:
- dev2_conn_method == "tunnel"
- (dev2_passfile_used_tunnel | default('NONE')) != "NONE"
- (dev2_mac | default('') | length) > 0
- (remote_eth0_mac | default('') | length) > 0
- (remote_eth0_mac | lower) != (dev2_mac | lower)
ansible.builtin.fail:
msg: >-
Safety stop: tunnel reached wrong device.
expected_dev2_mac={{ dev2_mac }}, remote_eth0_mac={{ remote_eth0_mac }}
# ============================ PHASE 0: FALLBACKS (only if tunnel auth failed) ============================
- name: Try DEV2 login via LLDP IPv4 10.x (fallback if tunnel auth failed)
when:
- dev2_conn_method == "tunnel"
- dev2_passfile_used_tunnel == "NONE"
- (lldp_dev2_ip | default('')) is match('^10\\.')
delegate_to: localhost
ansible.builtin.shell: |
set -e
HOST="{{ lldp_dev2_ip }}"
for f in {{ dev2_passfiles | join(' ') }}; do
if timeout 20s sshpass -f "$f" ssh {{ ssh_opts_common }} \
-o AddressFamily=inet \
-o ConnectTimeout=10 \
"{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1; then
echo "$f"; exit 0
fi
done
echo "NONE"; exit 1
args: { executable: /bin/bash }
register: dev2_auth_lldp4
changed_when: false
failed_when: false
ignore_errors: true
- name: Record LLDP IPv4 fallback decision
when:
- dev2_conn_method == "tunnel"
- dev2_passfile_used_tunnel == "NONE"
- (lldp_dev2_ip | default('')) is match('^10\\.')
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_lldp4: "{{ (dev2_auth_lldp4.rc == 0) | ternary((dev2_auth_lldp4.stdout | trim), 'NONE') }}"
changed_when: false
- name: Copy DEV2 passfiles to DEV1 for IPv6 nested SSH (last resort)
when:
- dev2_conn_method == "tunnel"
- dev2_passfile_used_tunnel == "NONE"
- dev2_passfile_used_lldp4 == "NONE"
- (lldp_dev2_ip6 | default('') | length) > 0
ansible.builtin.copy:
src: "{{ item }}"
dest: "/tmp/{{ item }}"
mode: "0600"
loop: "{{ dev2_passfiles }}"
ignore_errors: true
- name: Try DEV2 login via LLDP IPv6 (nested SSH through DEV1; last resort)
when:
- dev2_conn_method == "tunnel"
- dev2_passfile_used_tunnel == "NONE"
- dev2_passfile_used_lldp4 == "NONE"
- (lldp_dev2_ip6 | default('') | length) > 0
delegate_to: localhost
ansible.builtin.shell: |
set -e
IP6="{{ lldp_dev2_ip6 }}"
for f in {{ dev2_passfiles | join(' ') }}; do
if sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"timeout {{ ssh_timeout }}s sshpass -f '/tmp/${f}' ssh \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
-o PubkeyAuthentication=no \
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' 'echo OK' " \
>/dev/null 2>&1; then
echo "$f"; exit 0
fi
done
echo "NONE"; exit 1
args: { executable: /bin/bash }
register: dev2_auth_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Record LLDP IPv6 fallback decision
when:
- dev2_conn_method == "tunnel"
- dev2_passfile_used_tunnel == "NONE"
- dev2_passfile_used_lldp4 == "NONE"
- (lldp_dev2_ip6 | default('') | length) > 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_lldp6: "{{ (dev2_auth_lldp6.rc == 0) | ternary((dev2_auth_lldp6.stdout | trim), 'NONE') }}"
changed_when: false
# ============================ PHASE 0: FINAL DECISION + WRAPPER FACTS ============================
- name: Decide final DEV2 connection mode (direct_lldp > tunnel > lldp4 > lldp6)
delegate_to: localhost
ansible.builtin.set_fact:
@@ -510,10 +356,6 @@
direct_lldp
{%- elif dev2_conn_method == 'tunnel' and (dev2_passfile_used_tunnel | default('NONE')) != 'NONE' -%}
tunnel
{%- elif (dev2_passfile_used_lldp4 | default('NONE')) != 'NONE' -%}
lldp4_fallback
{%- elif (dev2_passfile_used_lldp6 | default('NONE')) != 'NONE' -%}
lldp6_via_dev1
{%- else -%}
none
{%- endif -%}
@@ -523,10 +365,9 @@
when: dev2_conn_final == "none"
ansible.builtin.fail:
msg: >
DEV2 unreachable: tunnel auth={{ dev2_passfile_used_tunnel | default('n/a') }},
direct auth={{ dev2_passfile_used_direct | default('n/a') }},
lldp4 auth={{ dev2_passfile_used_lldp4 | default('n/a') }},
lldp6 auth={{ dev2_passfile_used_lldp6 | default('n/a') }}.
DEV2 unreachable:
tunnel={{ dev2_passfile_used_tunnel | default('n/a') }},
direct={{ dev2_passfile_used_direct | default('n/a') }}.
- name: Debug final connectivity decision
when: debugging | bool
@@ -536,9 +377,7 @@
- "dev2_conn_final={{ dev2_conn_final }}"
- "tunnel passfile={{ dev2_passfile_used_tunnel | default('n/a') }}"
- "direct passfile={{ dev2_passfile_used_direct | default('n/a') }}"
- "lldp4 passfile={{ dev2_passfile_used_lldp4 | default('n/a') }}"
- "lldp6 passfile={{ dev2_passfile_used_lldp6 | default('n/a') }}"
- "tunnel target={{ dev2_host }}:{{ dev2_port }} (forwarded to 127.0.0.1:{{ _local_port | default('na') }})"
- "tunnel target={{ dev2_host }}:{{ dev2_port }} forwarded 127.0.0.1:{{ _local_port | default('na') }}"
- "lldp ipv4={{ lldp_dev2_ip | default('') }}"
- "lldp ipv6={{ lldp_dev2_ip6 | default('') }}"
@@ -549,7 +388,7 @@
set -e
MODE="{{ dev2_conn_final }}"
if [ -z "${DEV2_CMD:-}" ]; then
echo "ERROR: DEV2_CMD is empty" >&2
echo "ERROR DEV2_CMD empty" >&2
exit 2
fi
@@ -570,82 +409,60 @@
"{{ dev2_ssh_user }}@${HOST}" \
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
;;
lldp4_fallback)
HOST="{{ lldp_dev2_ip }}"
PASS="{{ dev2_passfile_used_lldp4 }}"
sshpass -f "$PASS" ssh {{ ssh_opts_common }} \
-o AddressFamily=inet \
"{{ dev2_ssh_user }}@${HOST}" \
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
;;
lldp6_via_dev1)
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
sshpass -p "{{ dev1_pass }}" ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}" \
"sshpass -f '/tmp/${F}' ssh \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
-o PubkeyAuthentication=no \
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
'{{ pathprefix }} '"${DEV2_CMD}" 2>&1"
;;
*)
echo "ERROR: unknown MODE=$MODE" >&2
echo "ERROR unknown MODE=$MODE" >&2
exit 3
;;
esac
changed_when: false
# ============================ VERIFY: DHCP MIGRATION STATE ============================
- name: VERIFY | DEV2 check ppp0 exists?
- name: VERIFY DEV2 check ppp0 exists?
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD="ip link show ppp0 >/dev/null 2>&1 && echo YES || echo NO"
{{ dev2_exec_cmd }}
args: { executable: /bin/bash }
register: v_ppp0_exists
register: dev2_ppp0_exists
changed_when: false
failed_when: false
- name: VERIFY | DEV2 check eth0.4000 exists?
- name: VERIFY DEV2 check eth0.4000 exists?
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD="ip link show eth0.4000 >/dev/null 2>&1 && echo YES || echo NO"
{{ dev2_exec_cmd }}
args: { executable: /bin/bash }
register: v_eth04000_exists
register: dev2_eth04000_exists
changed_when: false
failed_when: false
- name: VERIFY | DEV2 check eth0.4000 has IPv4?
- name: VERIFY DEV2 check eth0.4000 has IPv4?
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD="ip -4 addr show dev eth0.4000 2>/dev/null | grep -m1 'inet ' >/dev/null 2>&1 && echo YES || echo NO"
{{ dev2_exec_cmd }}
args: { executable: /bin/bash }
register: v_eth04000_has_ipv4
register: dev2_eth04000_has_ipv4
changed_when: false
failed_when: false
- name: VERIFY | Summarize DHCP migration state
- name: VERIFY Summarize DHCP migration state
delegate_to: localhost
ansible.builtin.set_fact:
ppp0_exists: "{{ (v_ppp0_exists.stdout | default('') | trim) == 'YES' }}"
eth04000_exists: "{{ (v_eth04000_exists.stdout | default('') | trim) == 'YES' }}"
eth04000_has_ipv4: "{{ (v_eth04000_has_ipv4.stdout | default('') | trim) == 'YES' }}"
ppp0_exists: "{{ (dev2_ppp0_exists.stdout | default('') | trim) == 'YES' }}"
eth04000_exists: "{{ (dev2_eth04000_exists.stdout | default('') | trim) == 'YES' }}"
eth04000_has_ipv4: "{{ (dev2_eth04000_has_ipv4.stdout | default('') | trim) == 'YES' }}"
dhcp_migrated_ok: >-
{{
((v_ppp0_exists.stdout | default('') | trim) != 'YES')
((dev2_ppp0_exists.stdout | default('') | trim) != 'YES')
and
((v_eth04000_exists.stdout | default('') | trim) == 'YES')
((dev2_eth04000_exists.stdout | default('') | trim) == 'YES')
and
((v_eth04000_has_ipv4.stdout | default('') | trim) == 'YES')
((dev2_eth04000_has_ipv4.stdout | default('') | trim) == 'YES')
}}
changed_when: false
- name: VERIFY | Result DHCP migrated (SUCCESS) or not (graceful stop)
- name: VERIFY Result DHCP migrated (SUCCESS) or not (graceful stop)
delegate_to: localhost
ansible.builtin.debug:
msg:
@@ -654,53 +471,50 @@
- "eth0.4000_exists={{ eth04000_exists }}"
- "eth0.4000_has_ipv4={{ eth04000_has_ipv4 }}"
- "dhcp_migrated_ok={{ dhcp_migrated_ok }}"
changed_when: false
- name: VERIFY | Graceful stop if DHCP migration state is not confirmed
- name: VERIFY Graceful stop if DHCP migration state is not confirmed
when: not (dhcp_migrated_ok | bool)
delegate_to: localhost
ansible.builtin.debug:
msg:
- "Graceful stop: DHCP migration state is not confirmed (not CASE A)."
- "Graceful stop DHCP state not confirmed"
- "ppp0_exists={{ ppp0_exists }}"
- "eth0.4000_exists={{ eth04000_exists }}"
- "eth0.4000_has_ipv4={{ eth04000_has_ipv4 }}"
- "dev2_conn_final={{ dev2_conn_final }}"
changed_when: false
- name: VERIFY | End host if DHCP migration state is not confirmed
- name: VERIFY End host if DHCP migration state is not confirmed
when: not (dhcp_migrated_ok | bool)
ansible.builtin.meta: end_host
# ============================ CASE A CLEANUP (DHCP OK, NO PPP) ============================
- name: CASE A Initialize cumulative cleanup report
when: dhcp_migrated_ok | bool
delegate_to: localhost
ansible.builtin.set_fact:
case_a_report: "case A observed"
cleanup_report: "case A observed"
changed_when: false
- name: CASE A DEV2 detect pending reboot process
- name: CASE A DEV2 cancel pending reboot if present
when: dhcp_migrated_ok | bool
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD="ps w | grep eboot | grep -v grep >/dev/null 2>&1 && echo YES || echo NO"
set -e
export DEV2_CMD="ps w | grep eboot | grep -v grep >/dev/null 2>&1 && echo FOUND || echo NOT_FOUND"
{{ dev2_exec_cmd }}
args: { executable: /bin/bash }
register: reboot_detect
register: reboot_present
changed_when: false
failed_when: false
- name: CASE A DEV2 cancel pending reboot if found
when:
- dhcp_migrated_ok | bool
- (reboot_detect.stdout | trim) == "YES"
- name: CASE A DEV2 cancel reboot if present
when: dhcp_migrated_ok | bool and (reboot_present.stdout | default('') | trim) == "FOUND"
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD="killall reboot 2>/dev/null || true"
set -e
export DEV2_CMD="ps w | grep eboot | grep -v grep >/dev/null 2>&1 && killall reboot 2>/dev/null || true; echo DONE"
{{ dev2_exec_cmd }}
args: { executable: /bin/bash }
register: reboot_cancel
changed_when: false
failed_when: false
@@ -708,11 +522,16 @@
when: dhcp_migrated_ok | bool
delegate_to: localhost
ansible.builtin.set_fact:
case_a_report: >-
{{ case_a_report
~ '; pending reboot '
~ ((reboot_detect.stdout | trim) == 'YES'
| ternary('found and cancelled', 'not found')) }}
cleanup_report: >-
{{
cleanup_report
~ '; '
~ (
'pending reboot found and cancelled'
if (reboot_present.stdout | default('') | trim) == 'FOUND'
else 'no pending reboot'
)
}}
changed_when: false
- name: CASE A final cumulative cleanup report
@@ -720,6 +539,49 @@
delegate_to: localhost
ansible.builtin.debug:
msg:
- "{{ case_a_report }}"
- "{{ cleanup_report }}"
changed_when: false
post_tasks:
- name: Cleanup note
delegate_to: localhost
ansible.builtin.debug:
msg: "Cleanup best-effort closing tunnel removing temp IP removing staged passfiles"
changed_when: false
- name: Close SSH ControlMaster (best-effort)
when: dev2_conn_method | default('') == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
ssh -S "{{ _ctrl_sock | default('/dev/null') }}" -O exit 2>/dev/null || true
args: { executable: /bin/bash }
changed_when: false
ignore_errors: true
- name: Remove tunnel control dir (best-effort)
when: dev2_conn_method | default('') == "tunnel"
delegate_to: localhost
ansible.builtin.file:
path: "{{ _ctrl_dir | default('/tmp/none') }}"
state: absent
ignore_errors: true
- name: Remove staged passfiles from DEV1 (best-effort)
when: dev2_conn_method | default('') == "tunnel"
ansible.builtin.raw: >
{{ pathprefix }}
rm -f /tmp/basicpass /tmp/basicpass2 2>/dev/null || true
changed_when: false
failed_when: false
- name: Remove temporary IP on DEV1 (tolerate 'Cannot assign requested address')
when: dev2_conn_method | default('') == "tunnel"
ansible.builtin.raw: >
{{ pathprefix }}
ip a del {{ dev2_side_ip }} dev {{ dev1_iface }}
register: del_ip
changed_when: del_ip.rc == 0
failed_when: >
del_ip.rc != 0
and ('Cannot assign requested address' not in (del_ip.stdout | default('')))
and ('Cannot assign requested address' not in (del_ip.stderr | default('')))