diff --git a/files/ansible-playbooks/ptsd-migrate.yml b/files/ansible-playbooks/ptsd-migrate.yml index b397ce9..2df1cc7 100644 --- a/files/ansible-playbooks/ptsd-migrate.yml +++ b/files/ansible-playbooks/ptsd-migrate.yml @@ -50,6 +50,11 @@ changed_when: false - name: Read DEV1 hostname (busybox-safe) + - name: Init ptsd migrate report accumulator + ansible.builtin.set_fact: + ptsd_migrate_report: "" + changed_when: false + ansible.builtin.raw: > {{ pathprefix }} (cat /proc/sys/kernel/hostname 2>/dev/null || echo "") @@ -399,67 +404,44 @@ - (remote_eth0_mac | lower) != (dev2_mac | lower) ansible.builtin.fail: msg: >- - MAC guard failed: remote eth0 MAC={{ remote_eth0_mac }}, expected DEV2 MAC={{ dev2_mac }}. - Refusing to proceed. + Safety stop: tunnel reached wrong device. + expected_dev2_mac={{ dev2_mac }}, remote_eth0_mac={{ remote_eth0_mac }} - # ============================ PHASE 0: LLDP IPv4 FALLBACK AUTH (after tunnel attempt) ============================ - - name: Try DEV2 login via LLDP IPv4 (fallback) with basicpass + # ============================ 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 | default('NONE')) == "NONE" - - (lldp_dev2_ip | default('') | length) > 0 + - dev2_passfile_used_tunnel == "NONE" + - (lldp_dev2_ip | default('')) is match('^10\\.') delegate_to: localhost ansible.builtin.shell: | set -e HOST="{{ lldp_dev2_ip }}" - sshpass -f basicpass ssh {{ ssh_opts_common }} \ - -o AddressFamily=inet \ - "{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1 + 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: Select basicpass for LLDP IPv4 fallback if succeeded + - name: Record LLDP IPv4 fallback decision when: - dev2_conn_method == "tunnel" - - (dev2_passfile_used_tunnel | default('NONE')) == "NONE" - - dev2_auth_lldp4.rc == 0 + - dev2_passfile_used_tunnel == "NONE" + - (lldp_dev2_ip | default('')) is match('^10\\.') delegate_to: localhost ansible.builtin.set_fact: - dev2_passfile_used_lldp4: "basicpass" + dev2_passfile_used_lldp4: "{{ (dev2_auth_lldp4.rc == 0) | ternary((dev2_auth_lldp4.stdout | trim), 'NONE') }}" changed_when: false - - name: Try DEV2 login via LLDP IPv4 (fallback) with basicpass2 (only if first failed) - when: - - dev2_conn_method == "tunnel" - - (dev2_passfile_used_tunnel | default('NONE')) == "NONE" - - (dev2_passfile_used_lldp4 | default('NONE')) == "NONE" - - (lldp_dev2_ip | default('') | length) > 0 - delegate_to: localhost - ansible.builtin.shell: | - set -e - HOST="{{ lldp_dev2_ip }}" - sshpass -f basicpass2 ssh {{ ssh_opts_common }} \ - -o AddressFamily=inet \ - "{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1 - args: { executable: /bin/bash } - register: dev2_auth_lldp4_2 - changed_when: false - ignore_errors: true - - - name: Select basicpass2 for LLDP IPv4 fallback if succeeded - when: - - dev2_conn_method == "tunnel" - - (dev2_passfile_used_tunnel | default('NONE')) == "NONE" - - (dev2_passfile_used_lldp4 | default('NONE')) == "NONE" - - dev2_auth_lldp4_2.rc == 0 - delegate_to: localhost - ansible.builtin.set_fact: - dev2_passfile_used_lldp4: "basicpass2" - changed_when: false - - # ============================ PHASE 0: LLDP IPv6 LAST RESORT (nested via DEV1) ============================ - name: Copy DEV2 passfiles to DEV1 for IPv6 nested SSH (last resort) when: - dev2_conn_method == "tunnel" @@ -555,6 +537,11 @@ - "lldp ipv4={{ lldp_dev2_ip | default('') }}" - "lldp ipv6={{ lldp_dev2_ip6 | default('') }}" + - name: Report milestone connectivity selected + ansible.builtin.set_fact: + ptsd_migrate_report: "{{ (ptsd_migrate_report ~ 'conn ' ~ dev2_conn_final ~ '; ') }}" + changed_when: false + - name: Build DEV2 exec wrapper (controller-side) for Phase 1 commands delegate_to: localhost ansible.builtin.set_fact: @@ -773,7 +760,13 @@ args: { executable: /bin/bash } changed_when: true - - name: PHASE 1 | Download /tmp/config.json from DEV2 (tunnel) + + - name: Report milestone marker set + ansible.builtin.set_fact: + ptsd_migrate_report: "{{ (ptsd_migrate_report ~ 'marker set; ') }}" + changed_when: false + + - name: PHASE 1 | Download /tmp/config.json from DEV2 to controller (tunnel) when: dev2_conn_final == "tunnel" delegate_to: localhost ansible.builtin.shell: | @@ -788,7 +781,7 @@ changed_when: true failed_when: scp_tunnel.rc != 0 - - name: PHASE 1 | Download /tmp/config.json from DEV2 (direct LLDP IPv4) + - name: PHASE 1 | Download /tmp/config.json from DEV2 to controller (direct LLDP IPv4) when: dev2_conn_final == "direct_lldp" delegate_to: localhost ansible.builtin.shell: | @@ -803,7 +796,7 @@ changed_when: true failed_when: scp_direct.rc != 0 - - name: PHASE 1 | Download /tmp/config.json from DEV2 (LLDP IPv4 fallback) + - name: PHASE 1 | Download /tmp/config.json from DEV2 to controller (LLDP IPv4 fallback) when: dev2_conn_final == "lldp4_fallback" delegate_to: localhost ansible.builtin.shell: | @@ -818,36 +811,38 @@ changed_when: true failed_when: scp_lldp4.rc != 0 - - name: PHASE 1 | Download /tmp/config.json from DEV2 (LLDP IPv6 via DEV1 nested; last resort) + - name: PHASE 1 | Download /tmp/config.json from DEV2 to controller (LLDP IPv6 via DEV1 nested; last resort) when: dev2_conn_final == "lldp6_via_dev1" delegate_to: localhost ansible.builtin.shell: | set -e IP6="{{ lldp_dev2_ip6 }}" F="{{ dev2_passfile_used_lldp6 }}" - OUT="{{ dev2_config_local_path }}" - STAGE_ON_DEV1="/tmp/config_{{ inventory_hostname }}.json" + TMP_REMOTE="/tmp/config_{{ inventory_hostname }}.json" + # 1) On DEV1: scp from DEV2 (ipv6 link-local) to DEV1 /tmp sshpass -p "{{ dev1_pass }}" ssh {{ ssh_opts_common }} \ "{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}" \ "sshpass -f '/tmp/${F}' scp \ -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \ -o PreferredAuthentications=password -o PubkeyAuthentication=no -o NumberOfPasswordPrompts=1 \ - '/tmp/config.json' '{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]':${STAGE_ON_DEV1}" + '{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]':/tmp/config.json '${TMP_REMOTE}'" + # 2) From DEV1 to controller sshpass -p "{{ dev1_pass }}" scp {{ ssh_opts_common }} \ - "{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}:${STAGE_ON_DEV1}" \ - "${OUT}" + "{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}:${TMP_REMOTE}" \ + "{{ dev2_config_local_path }}" + # 3) Cleanup on DEV1 (best-effort) sshpass -p "{{ dev1_pass }}" ssh {{ ssh_opts_common }} \ "{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}" \ - "rm -f '${STAGE_ON_DEV1}' 2>/dev/null || true" + "rm -f '${TMP_REMOTE}' 2>/dev/null || true" args: { executable: /bin/bash } register: scp_lldp6 changed_when: true failed_when: scp_lldp6.rc != 0 - - name: PHASE 1 | md5sum local downloaded config.json + - name: PHASE 1 | Controller md5sum of downloaded config.json delegate_to: localhost ansible.builtin.shell: | set -e @@ -856,48 +851,53 @@ register: local_config_md5 changed_when: false - - name: PHASE 1 | Compare controller vs DEV2 md5sum for config.json (fail on mismatch) + - name: PHASE 1 | Compare DEV2 vs controller md5sum (fail on mismatch) delegate_to: localhost ansible.builtin.fail: msg: >- - MD5 mismatch for config.json: - local={{ local_config_md5.stdout | trim }}, + MD5 mismatch for /tmp/config.json: dev2={{ dev2_config_md5.stdout | trim }}, - local_file={{ dev2_config_local_path }} + local={{ local_config_md5.stdout | trim }}, + file={{ dev2_config_local_path }} when: (dev2_config_md5.stdout | trim) != (local_config_md5.stdout | trim) - - name: PHASE 1 | Check that dhcp script exists on controller + - name: PHASE 1 | Debug config.json download + md5 + when: debugging | bool delegate_to: localhost - ansible.builtin.shell: | - set -e - test -f ./dhcp_migrate.py && echo YES || echo NO - args: { executable: /bin/bash } - register: dhcp_script_present - changed_when: false - failed_when: false + ansible.builtin.debug: + msg: + - "Downloaded DEV2 /tmp/config.json to {{ dev2_config_local_path }}" + - "DEV2 md5={{ dev2_config_md5.stdout | trim }}" + - "Local md5={{ local_config_md5.stdout | trim }}" - - name: PHASE 1 | Abort if dhcp_migrate.py is missing - when: (dhcp_script_present.stdout | default('') | trim) != "YES" - ansible.builtin.fail: - msg: "dhcp_migrate.py missing on controller" - - - name: PHASE 1 | Convert config.json to config-dhcp.json on controller - delegate_to: localhost - ansible.builtin.shell: | - set -e - ./dhcp_migrate.py "{{ dev2_config_local_path }}" > "{{ dev2_config_local_dir }}/config-dhcp_{{ inventory_hostname }}.json" - args: { executable: /bin/bash } - register: convert_run - changed_when: true - - - name: PHASE 1 | Set DHCP config paths (local + remote) + - name: PHASE 1 | Set controller paths for DHCP conversion delegate_to: localhost ansible.builtin.set_fact: - dev2_config_dhcp_local_path: "{{ dev2_config_local_dir }}/config-dhcp_{{ inventory_hostname }}.json" + pppoe_to_dhcp_script: "/opt/containers/ansible-worker/app/pppoe_to_dhcp.py" + dev2_config_dhcp_local_path: "{{ dev2_config_local_dir }}/dev2_config_dhcp_{{ inventory_hostname }}.json" dev2_config_dhcp_remote_path: "/tmp/config-dhcp.json" changed_when: false - - name: PHASE 1 | md5sum local config-dhcp.json + - name: PHASE 1 | Abort if pppoe_to_dhcp.py is missing on controller + delegate_to: localhost + ansible.builtin.shell: | + test -f "{{ pppoe_to_dhcp_script }}" + args: { executable: /bin/bash } + register: dhcp_script_present + changed_when: false + failed_when: dhcp_script_present.rc != 0 + + - name: PHASE 1 | Convert config.json -> config-dhcp.json (controller) + delegate_to: localhost + ansible.builtin.shell: | + set -e + python3 "{{ pppoe_to_dhcp_script }}" "{{ dev2_config_local_path }}" -o "{{ dev2_config_dhcp_local_path }}" + args: { executable: /bin/bash } + register: convert_run + changed_when: true + failed_when: convert_run.rc != 0 + + - name: PHASE 1 | Controller md5sum of generated config-dhcp.json delegate_to: localhost ansible.builtin.shell: | set -e @@ -905,6 +905,7 @@ args: { executable: /bin/bash } register: local_dhcp_md5 changed_when: false + failed_when: (local_dhcp_md5.stdout | trim | length) == 0 - name: PHASE 1 | Upload config-dhcp.json to DEV2 (tunnel) when: dev2_conn_final == "tunnel" @@ -1034,6 +1035,11 @@ args: { executable: /bin/bash } changed_when: true + - name: Report milestone config converted and staged + ansible.builtin.set_fact: + ptsd_migrate_report: "{{ (ptsd_migrate_report ~ 'config backed up, converted, uploaded; ') }}" + changed_when: false + - name: PHASE 1 | DEV2 arm parachute reboot delayed 600 seconds delegate_to: localhost ansible.builtin.shell: | @@ -1042,6 +1048,11 @@ args: { executable: /bin/bash } changed_when: true + - name: Report milestone parachute armed + ansible.builtin.set_fact: + ptsd_migrate_report: "{{ (ptsd_migrate_report ~ 'parachute reboot armed; ') }}" + changed_when: false + - name: PHASE 1 | DEV2 simulate upgrade marker /tmp/AA_startedupgrade delegate_to: localhost ansible.builtin.shell: | @@ -1059,6 +1070,11 @@ args: { executable: /bin/bash } changed_when: true + - name: Report milestone lld restart scheduled + ansible.builtin.set_fact: + ptsd_migrate_report: "{{ (ptsd_migrate_report ~ 'lld restart scheduled; ') }}" + changed_when: false + - name: PHASE 1 | initiating actual upgrade delegate_to: localhost ansible.builtin.shell: | @@ -1067,6 +1083,11 @@ args: { executable: /bin/bash } changed_when: true + - name: Report milestone upgrade started + ansible.builtin.set_fact: + ptsd_migrate_report: "{{ (ptsd_migrate_report ~ 'upgrade started; ') }}" + changed_when: false + post_tasks: - name: Cleanup note delegate_to: localhost @@ -1111,183 +1132,20 @@ and ('Cannot assign requested address' not in (del_ip.stdout | default(''))) and ('Cannot assign requested address' not in (del_ip.stderr | default(''))) - - name: REPORT Initialize cumulative migrate report - delegate_to: localhost + - name: Report milestone cleanup completed + when: dev2_conn_method | default('') == "tunnel" ansible.builtin.set_fact: - migrate_report: "migrate observed" + ptsd_migrate_report: "{{ (ptsd_migrate_report ~ 'cleanup done; ') }}" changed_when: false - - name: REPORT Append connectivity summary - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ('conn_initial ' ~ (dev2_conn_method | default('unknown'))) - ~ ', ' - ~ ('conn_final ' ~ (dev2_conn_final | default('unknown'))) - }} - changed_when: false - - - name: REPORT Append LLDP candidates - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ('lldp4 ' ~ (lldp_dev2_ip | default(''))) - ~ ', ' - ~ ('lldp4_class ' ~ (lldp_ip_class | default('none'))) - ~ ', ' - ~ ('lldp6 ' ~ (lldp_dev2_ip6 | default(''))) - }} - changed_when: false - - - name: REPORT Append passfiles used - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ('pass_direct ' ~ (dev2_passfile_used_direct | default('NONE'))) - ~ ', ' - ~ ('pass_tunnel ' ~ (dev2_passfile_used_tunnel | default('NONE'))) - ~ ', ' - ~ ('pass_lldp4 ' ~ (dev2_passfile_used_lldp4 | default('NONE'))) - ~ ', ' - ~ ('pass_lldp6 ' ~ (dev2_passfile_used_lldp6 | default('NONE'))) - }} - changed_when: false - - - name: REPORT Append inprogress marker status - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ( - 'marker present' - if (dev2_inprogress_marker.stdout | default('') | trim) == 'YES' - else 'marker absent' - ) - }} - changed_when: false - - - name: REPORT Append pre migration interface state - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ('ppp0_has_ipv4 ' ~ ((dev2_ppp0_has_ipv4.stdout | default('') | trim) | default('UNKNOWN'))) - ~ ', ' - ~ ('eth0_4000_exists ' ~ ((dev2_eth04000_exists.stdout | default('') | trim) | default('UNKNOWN'))) - ~ ', ' - ~ ( - 'precheck ok old ppp' - if (dev2_pre_migration_ok | default(false) | bool) - else - ( - 'precheck mixed' - if (dev2_mixed_state | default(false) | bool) - else 'precheck not old ppp' - ) - ) - }} - changed_when: false - - - name: REPORT Append config json md5 status - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ( - 'config.json md5 match' - if (dev2_config_md5.stdout | default('') | trim) == (local_config_md5.stdout | default('') | trim) - else 'config.json md5 mismatch' - ) - }} - changed_when: false - - - name: REPORT Append dhcp conversion status - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ( - 'convert ok' - if (convert_run.rc | default(1)) == 0 - else 'convert failed' - ) - }} - changed_when: false - - - name: REPORT Append upload method - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ( - 'upload tunnel' - if (scp_up_tunnel is defined) - else - ( - 'upload direct_lldp' - if (scp_up_direct is defined) - else - ( - 'upload lldp4_fallback' - if (scp_up_lldp4 is defined) - else - ( - 'upload lldp6_via_dev1' - if (scp_up_lldp6 is defined) - else 'upload unknown' - ) - ) - ) - ) - }} - changed_when: false - - - name: REPORT Append config dhcp md5 status - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: >- - {{ - migrate_report - ~ ', ' - ~ ( - 'config-dhcp md5 match' - if (dev2_dhcp_md5.stdout | default('') | trim) == (local_dhcp_md5.stdout | default('') | trim) - else 'config-dhcp md5 mismatch' - ) - }} - changed_when: false - - - name: REPORT Append upgrade actions started - delegate_to: localhost - ansible.builtin.set_fact: - migrate_report: "{{ migrate_report ~ ', staged config-test and armed parachute reboot and initiated config-test.lua 400' }}" - changed_when: false - - - name: REPORT Final cumulative migrate report +- name: PHASE Y | Final cumulative migrate report + hosts: all + gather_facts: false + tasks: + - name: Migrate final cumulative report delegate_to: localhost ansible.builtin.debug: - msg: - - "{{ migrate_report }}" - - "DEV2 uptime via {{ dev2_conn_final | default('unknown') }}={{ (dev2_uptime.stdout | default('') ) | regex_replace('\r','') }}" + msg: "{{ ptsd_migrate_report | default('') | regex_replace('\\s+', ' ') | trim }}" changed_when: false - name: PHASE X | Wait 45 seconds after config activation @@ -1298,3 +1156,4 @@ seconds: 45 - import_playbook: ptsd_reacquire_attempt.yml +