Add optional automatic check cycle (clear queue -> scan FIPs -> wait -> repeat)

An admin-controlled scenario that repeats what the operator does by hand:
clear the IP queue, scan and enqueue all free Floating IPs, wait until every
queued address reaches a terminal state (so results are in the Registry),
then wait a configurable interval and start over.

- control-api: new auto_cycle singleton table (migration 0008) holding
  enabled/interval/max-run settings and persisted phase state, so the cycle
  survives restarts; engine in orchestrator/autocycle.go driven from the
  existing loop tick with an injectable "now" for deterministic tests.
- Interval (default 1h, min 60s) and max wait (default unlimited, timeout
  outcome) are runtime settings, never hardcoded.
- The periodic fip_scan_interval_seconds scan is skipped while the cycle is
  enabled. An emptied queue mid-cycle counts as finished; stopping during
  the pause keeps the last cycle's outcome.
- API: GET/PUT /api/v1/admin/auto-cycle, POST .../start, POST .../stop.
- admin-dashboard: "Автоматический цикл" panel on /settings and an
  "Автоцикл активен" indicator on /overview.
- Tests for db, orchestrator, httpapi and dashboard; run-local-e2e.sh now
  exercises a full auto cycle; docs updated; bin/ rebuilt with refreshed
  SHA256SUMS.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5.5 committed 2026-10-01 10:28:53 +03:00
1 parent 008ae1b0db
commit cd37b10f3b
35 files changed
+2205 -16

No files matched your search

+58
View File
@@ -226,6 +226,64 @@ done
echo "--- ip results after forced re-check (attempt_number should have advanced) ---"
ips | python3 -m json.tool
echo "--- automatic cycle: enable it and wait for the first cycle (clear queue -> scan FIPs -> checks -> registry) ---"
AC_URL="http://127.0.0.1:28080/api/v1/admin/auto-cycle"
registry_cycles() {
curl -fs "http://127.0.0.1:28080/api/v1/admin/registry" | python3 -c "
import json,sys
for r in json.load(sys.stdin):
if r['ip_address'] == '127.0.0.1':
print(r['total_cycles'])
break
else:
print(0)"
}
ac_field() { curl -fs "$AC_URL" | python3 -c "import json,sys; print(json.load(sys.stdin)['$1'])"; }
CYCLES_BEFORE="$(registry_cycles)"
# 60s is the smallest interval control-api accepts; the second cycle is
# covered by unit tests, so the script stops the auto-cycle after the first.
curl -fs -X PUT "$AC_URL" -H 'Content-Type: application/json' \
-d '{"interval_seconds":60,"max_run_seconds":120}' >/dev/null
curl -fs -X POST "$AC_URL/start" | python3 -m json.tool
echo "--- waiting for the first auto cycle to complete (up to 90s) ---"
AC_OUTCOME=""
for i in $(seq 1 180); do
sleep 0.5
AC_OUTCOME="$(ac_field last_outcome 2>/dev/null || true)"
if [ -n "$AC_OUTCOME" ]; then
echo "first auto cycle finished after ~$((i / 2))s: outcome=$AC_OUTCOME"
break
fi
done
echo "--- auto-cycle status ---"
curl -fs "$AC_URL" | python3 -m json.tool
CYCLES_AFTER="$(registry_cycles)"
echo "registry total_cycles for 127.0.0.1: $CYCLES_BEFORE -> $CYCLES_AFTER"
if [ "$AC_OUTCOME" != "completed" ]; then
echo "FAIL: expected auto-cycle outcome 'completed', got '$AC_OUTCOME'"
exit 1
fi
if [ "$(ac_field phase)" != "waiting" ] || [ "$(ac_field runs_total)" != "1" ]; then
echo "FAIL: expected phase=waiting and runs_total=1 after the first cycle"
exit 1
fi
if [ "$CYCLES_AFTER" -le "$CYCLES_BEFORE" ]; then
echo "FAIL: the auto cycle did not add a new check cycle to the registry"
exit 1
fi
echo "--- automatic cycle: disable it; no further cycles must start ---"
curl -fs -X POST "$AC_URL/stop" | python3 -m json.tool
if [ "$(ac_field enabled)" != "False" ] || [ "$(ac_field phase)" != "idle" ]; then
echo "FAIL: expected enabled=false and phase=idle after stop"
exit 1
fi
echo "auto-cycle e2e: OK"
echo "--- logs are in $WORK_DIR (kept for inspection; workdir NOT auto-deleted) ---"
trap - EXIT
cleanup