diff --git a/kubernetes/apps/otbr/deployment.yaml b/kubernetes/apps/otbr/deployment.yaml index f9ce22ea..698695f9 100644 --- a/kubernetes/apps/otbr/deployment.yaml +++ b/kubernetes/apps/otbr/deployment.yaml @@ -115,12 +115,53 @@ spec: # dataset is persisted yet (clean install), the seed Job # (otbr-dataset-bootstrap) will populate it and activate # Thread itself. - if ot-ctl dataset active 2>&1 | grep -qE "Active Timestamp|Network Name"; then - echo "otbr: persisted dataset found; activating Thread" + # + # The dataset check MUST be polled, not sampled once. + # otbr-agent answers `ot-ctl state` as soon as its control + # socket exists, which is *before* it has restored the + # persisted dataset from /var/lib/thread. A single check + # right after the socket appears therefore races the restore + # and loses: on 2026-06-27 this pod booted, saw no dataset, + # took the "awaiting bootstrap Job" branch, and sat with + # Thread `disabled` for twelve days while `ot-ctl dataset + # active` returned NEST-PAN-339C the whole time. The seed Job + # had completed 61 days earlier, so nothing ever activated it. + # Reproduced on a 2026-07-09 restart before this fix landed. + DATASET_FOUND="" + for i in $(seq 1 30); do + if ot-ctl dataset active 2>&1 | grep -qE "Active Timestamp|Network Name"; then + DATASET_FOUND=1 + break + fi + sleep 2 + done + if [ -n "$DATASET_FOUND" ]; then + echo "otbr: persisted dataset found after ${i} attempt(s); activating Thread" ot-ctl ifconfig up || true ot-ctl thread start || true + # Surface the outcome. `thread start` returns before MLE has + # attached, so poll rather than trusting the exit code — a + # silent `disabled` here is exactly the failure this block + # exists to prevent. Note the trailing `|| true`: this script + # runs under `set -e`, so a bare failing test would kill the + # container rather than log a warning. + STATE="" + for i in $(seq 1 30); do + STATE=$(ot-ctl state 2>/dev/null | head -1 || true) + case "$STATE" in + leader|router|child) + echo "otbr: Thread attached as ${STATE}" + break + ;; + esac + sleep 2 + done + case "$STATE" in + leader|router|child) ;; + *) echo "otbr: WARNING Thread not attached after 60s (state=${STATE:-unknown})" ;; + esac else - echo "otbr: no persisted dataset; awaiting bootstrap Job" + echo "otbr: no persisted dataset after 60s; awaiting bootstrap Job" fi wait "$ENTRYPOINT_PID" env: