From 0b79d9dbfcaa48a1ab35ef56cf6074e77f7768f1 Mon Sep 17 00:00:00 2001 From: pavel Date: Thu, 12 Feb 2026 09:01:15 +0200 Subject: [PATCH] 0901 --- .../update-reboot-scheduler.yml | 9 +- .../ansible-playbooks/update-rebootin224.yml | 173 ++++++++++++++++++ 2 files changed, 177 insertions(+), 5 deletions(-) diff --git a/files/ansible-playbooks/update-reboot-scheduler.yml b/files/ansible-playbooks/update-reboot-scheduler.yml index 230552b..4762437 100644 --- a/files/ansible-playbooks/update-reboot-scheduler.yml +++ b/files/ansible-playbooks/update-reboot-scheduler.yml @@ -3,8 +3,7 @@ # Phase 1: Cloud -> NetBox sync (via sot-updater-iponly.yml in wrapper mode) # Phase 2: Refresh ansible_host from nb_ip, reset_connection # Phase 3: Run update-rebootin224.yml -# NOTE: update-rebootin224.yml is responsible for scheduling afterupgrade_check via RabbitMQ (delayed). -# Do NOT import/run afterupgrade_check.yml here. +# Phase 4: Run afterupgrade_check.yml - hosts: all gather_facts: no @@ -42,9 +41,7 @@ _lines: "{{ (nb_preflight.stdout_lines | default([])) | map('regex_replace','\r','') | map('trim') | list }}" _ok_line: "{{ (_lines | select('match','^OK\\s+') | list | last | default('')) }}" _fail_line: "{{ (_lines | select('match','^FAIL\\s+') | list | last | default('')) }}" - # NOTE: regex_search with a replacement can yield a list on some Ansible/Jinja versions; - # keep it as a plain string match for ansible_host. - _ip_raw: "{{ _ok_line | regex_search('([0-9]{1,3}(?:\\.[0-9]{1,3}){3})') | default('') }}" + _ip_raw: "{{ _ok_line | regex_search('([0-9]{1,3}(?:\\.[0-9]{1,3}){3})','\\1') | default('') }}" set_fact: nb_ip: "{{ _ip_raw }}" nb_ip_ok: "{{ (_ip_raw | length) > 0 }}" @@ -66,3 +63,5 @@ - import_playbook: update-rebootin224.yml + vars: + is_run_by: "scheduler" diff --git a/files/ansible-playbooks/update-rebootin224.yml b/files/ansible-playbooks/update-rebootin224.yml index 9eac6bf..6c782b0 100644 --- a/files/ansible-playbooks/update-rebootin224.yml +++ b/files/ansible-playbooks/update-rebootin224.yml @@ -13,6 +13,9 @@ rmq_exchange: "{{ lookup('env','RMQ_EXCHANGE') | default('controls', true) }}" control_queue: "{{ lookup('env','CONTROLQUEUE') | default('queue_controls', true) }}" + # Invocation context (default: manual; scheduler wrapper sets is_run_by=scheduler) + is_run_by_effective: "{{ is_run_by | default('manual') }}" + # NEW: Post-upgrade check scheduling (via holding queue -> DLX) # The holding queue is bound to exchange 'deviceconfig.holding' with routing key 'deviceconfig'. # Messages published here carry a per-message TTL (AMQP 'expiration' property, in ms). @@ -139,9 +142,75 @@ (not (rmq_tag_failed_pre_resp.json.routed | default(false) | bool)) delegate_to: localhost + - name: Scheduler | Publish action_state failed (hostname preflight) + ansible.builtin.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': 'action_state', + 'task_result': 'failed' + } | to_json }}" + payload_encoding: "string" + changed_when: false + delegate_to: localhost + when: is_run_by_effective == 'scheduler' + + - name: Scheduler | Debug action_state failed published (hostname preflight) + ansible.builtin.debug: + msg: "scheduler-run detected; published action_state=failed (hostname preflight)" + when: is_run_by_effective == 'scheduler' + - name: Stop play after hostname preflight failure ansible.builtin.meta: end_play + - name: Scheduler context | Debug is_run_by + ansible.builtin.debug: + msg: "is_run_by={{ is_run_by_effective }}" + + - name: Scheduler | Publish action_state inprogress (scheduler-run) + ansible.builtin.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': 'action_state', + 'task_result': 'inprogress' + } | to_json }}" + payload_encoding: "string" + changed_when: false + delegate_to: localhost + when: is_run_by_effective == 'scheduler' + + - name: Scheduler | Debug action_state inprogress published + ansible.builtin.debug: + msg: "scheduler-run detected; published action_state=inprogress" + when: is_run_by_effective == 'scheduler' + # --- Tag device as update-in-progress at start --- - name: Build control queue payload for update-in-progress tag ansible.builtin.set_fact: @@ -724,6 +793,79 @@ when: afterupgrade_payload is defined delegate_to: localhost + + - name: Scheduler | Publish action_state waiting (success) + ansible.builtin.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': 'action_state', + 'task_result': 'waiting' + } | to_json }}" + payload_encoding: "string" + changed_when: false + delegate_to: localhost + when: is_run_by_effective == 'scheduler' + + - name: Scheduler | Compute action_restart_timestamp (unix seconds) when reboot scheduled + ansible.builtin.set_fact: + action_restart_timestamp: "{{ (lookup('pipe','date -u +%s') | int) + (reboot_seconds | int) }}" + changed_when: false + delegate_to: localhost + when: + - is_run_by_effective == 'scheduler' + - reboot_seconds is defined + - (reboot_seconds | int) > 0 + + - name: Scheduler | Debug computed action_restart_timestamp + ansible.builtin.debug: + msg: "action_restart_timestamp={{ action_restart_timestamp }} (reboot_seconds={{ reboot_seconds | int }})" + when: + - is_run_by_effective == 'scheduler' + - action_restart_timestamp is defined + delegate_to: localhost + + - name: Scheduler | Publish action_restart_timestamp custom field + ansible.builtin.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': 'action_restart_timestamp', + 'task_result': (action_restart_timestamp | string) + } | to_json }}" + payload_encoding: "string" + changed_when: false + delegate_to: localhost + when: + - is_run_by_effective == 'scheduler' + - action_restart_timestamp is defined + rescue: - name: Build failure task name and detail ansible.builtin.set_fact: @@ -807,3 +949,34 @@ (rmq_tag_failed_resp.json is not defined) or (not (rmq_tag_failed_resp.json.routed | default(false) | bool)) delegate_to: localhost + + - name: Scheduler | Publish action_state failed (update failure) + ansible.builtin.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': 'action_state', + 'task_result': 'failed' + } | to_json }}" + payload_encoding: "string" + changed_when: false + delegate_to: localhost + when: is_run_by_effective == 'scheduler' + + - name: Scheduler | Debug action_state failed published (update failure) + ansible.builtin.debug: + msg: "scheduler-run detected; published action_state=failed (update failure)" + when: is_run_by_effective == 'scheduler'