Retry failed startup without another WAN event
This commit is contained in:
@@ -23,6 +23,8 @@ const RELOAD_LOCK_DIR = getenv("FORKOP_RELOAD_LOCK_DIR") || "/var/run/forkop.rel
|
|||||||
const RUNTIME_STATE_DIR = getenv("FORKOP_RUNTIME_STATE_DIR") || "/var/run/forkop";
|
const RUNTIME_STATE_DIR = getenv("FORKOP_RUNTIME_STATE_DIR") || "/var/run/forkop";
|
||||||
const PENDING_RELOAD_FILE = getenv("FORKOP_PENDING_RELOAD_FILE") || RUNTIME_STATE_DIR + "/reload.pending";
|
const PENDING_RELOAD_FILE = getenv("FORKOP_PENDING_RELOAD_FILE") || RUNTIME_STATE_DIR + "/reload.pending";
|
||||||
const START_RETRY_FILE = getenv("FORKOP_START_RETRY_FILE") || RUNTIME_STATE_DIR + "/start.retry";
|
const START_RETRY_FILE = getenv("FORKOP_START_RETRY_FILE") || RUNTIME_STATE_DIR + "/start.retry";
|
||||||
|
const START_RETRY_PID_FILE = getenv("FORKOP_START_RETRY_PID_FILE") || RUNTIME_STATE_DIR + "/start-retry.pid";
|
||||||
|
const START_RETRY_DELAY_SECONDS = getenv("FORKOP_START_RETRY_DELAY_SECONDS") || "30";
|
||||||
const SERVICE_TRIGGER_SYNC_FILE = getenv("FORKOP_SERVICE_TRIGGER_SYNC_FILE") || RUNTIME_STATE_DIR + "/service-triggers.sync";
|
const SERVICE_TRIGGER_SYNC_FILE = getenv("FORKOP_SERVICE_TRIGGER_SYNC_FILE") || RUNTIME_STATE_DIR + "/service-triggers.sync";
|
||||||
const INTERNAL_CONFIG_TRIGGER_GUARD = getenv("FORKOP_INTERNAL_CONFIG_TRIGGER_GUARD") || "/var/run/forkop.internal-config-change";
|
const INTERNAL_CONFIG_TRIGGER_GUARD = getenv("FORKOP_INTERNAL_CONFIG_TRIGGER_GUARD") || "/var/run/forkop.internal-config-change";
|
||||||
const CONFIG_CHANGE_REASON = getenv("FORKOP_CONFIG_CHANGE_REASON") || "on_config_change";
|
const CONFIG_CHANGE_REASON = getenv("FORKOP_CONFIG_CHANGE_REASON") || "on_config_change";
|
||||||
@@ -247,6 +249,40 @@ function start_retry_pending(path) {
|
|||||||
return file_exists(as_string(path || START_RETRY_FILE));
|
return file_exists(as_string(path || START_RETRY_FILE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cancel_scheduled_start_retry(path) {
|
||||||
|
path = as_string(path || START_RETRY_PID_FILE);
|
||||||
|
let pid = first_line_value(path);
|
||||||
|
if (pid_alive(pid))
|
||||||
|
command_success_from_args([ "kill", pid ]);
|
||||||
|
if (file_exists(path))
|
||||||
|
unlink_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedule_start_retry(path, delay_seconds) {
|
||||||
|
path = as_string(path || START_RETRY_PID_FILE);
|
||||||
|
delay_seconds = as_string(delay_seconds || START_RETRY_DELAY_SECONDS);
|
||||||
|
if (!numeric_text(delay_seconds))
|
||||||
|
delay_seconds = "30";
|
||||||
|
|
||||||
|
let scheduled_pid = first_line_value(path);
|
||||||
|
if (pid_alive(scheduled_pid))
|
||||||
|
return true;
|
||||||
|
if (file_exists(path))
|
||||||
|
unlink_file(path);
|
||||||
|
if (!ensure_parent_dir(path))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let worker = command_from_args([ "sleep", delay_seconds ]) +
|
||||||
|
"; " + command_from_args([ "rm", "-f", path ]) +
|
||||||
|
"; exec " + command_from_args([ SERVICE_INIT, "retry_start_on_wan_up" ]);
|
||||||
|
let result = command_capture(command_from_args([ "sh", "-c", worker ]) + " >/dev/null 2>&1 & echo $!");
|
||||||
|
let pid = trim(result.output);
|
||||||
|
if (result.status != 0 || !numeric_text(pid))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return write_text_file(path, pid + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
function consume_pending_reload(path) {
|
function consume_pending_reload(path) {
|
||||||
path = as_string(path || PENDING_RELOAD_FILE);
|
path = as_string(path || PENDING_RELOAD_FILE);
|
||||||
if (!file_exists(path))
|
if (!file_exists(path))
|
||||||
@@ -492,11 +528,14 @@ function start_service(reason, owner_pid) {
|
|||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
let status = command_status_from_args([ BIN_PATH, "start" ]);
|
let status = command_status_from_args([ BIN_PATH, "start" ]);
|
||||||
if (status == 0)
|
if (status == 0) {
|
||||||
clear_start_retry(START_RETRY_FILE);
|
clear_start_retry(START_RETRY_FILE);
|
||||||
|
cancel_scheduled_start_retry(START_RETRY_PID_FILE);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
mark_start_retry(START_RETRY_FILE, as_string(reason) == "triggered" ? "wan_retry_failed" : "start_failed");
|
mark_start_retry(START_RETRY_FILE, as_string(reason) == "triggered" ? "wan_retry_failed" : "start_failed");
|
||||||
command_success_from_args([ "logger", "-t", SERVICE_NAME, "[warn] Forkop start failed; will retry after WAN comes up" ]);
|
schedule_start_retry(START_RETRY_PID_FILE, START_RETRY_DELAY_SECONDS);
|
||||||
|
command_success_from_args([ "logger", "-t", SERVICE_NAME, "[warn] Forkop start failed; scheduled an automatic retry" ]);
|
||||||
}
|
}
|
||||||
finish_external_service_action("start", plan.job_id, status);
|
finish_external_service_action("start", plan.job_id, status);
|
||||||
return status;
|
return status;
|
||||||
@@ -525,6 +564,7 @@ function stop_finish(job_id, status) {
|
|||||||
|
|
||||||
function stop_service(owner_pid) {
|
function stop_service(owner_pid) {
|
||||||
clear_start_retry(START_RETRY_FILE);
|
clear_start_retry(START_RETRY_FILE);
|
||||||
|
cancel_scheduled_start_retry(START_RETRY_PID_FILE);
|
||||||
let job_id = begin_external_service_action("stop", "initd", owner_pid);
|
let job_id = begin_external_service_action("stop", "initd", owner_pid);
|
||||||
if (!file_executable(BIN_PATH)) {
|
if (!file_executable(BIN_PATH)) {
|
||||||
restore_dnsmasq_failsafe();
|
restore_dnsmasq_failsafe();
|
||||||
@@ -671,6 +711,10 @@ else if (mode == "clear-start-retry")
|
|||||||
clear_start_retry(ARGV[1]);
|
clear_start_retry(ARGV[1]);
|
||||||
else if (mode == "start-retry-pending")
|
else if (mode == "start-retry-pending")
|
||||||
exit(start_retry_pending(ARGV[1]) ? 0 : 1);
|
exit(start_retry_pending(ARGV[1]) ? 0 : 1);
|
||||||
|
else if (mode == "schedule-start-retry")
|
||||||
|
exit(schedule_start_retry(ARGV[1], ARGV[2]) ? 0 : 1);
|
||||||
|
else if (mode == "cancel-scheduled-start-retry")
|
||||||
|
cancel_scheduled_start_retry(ARGV[1]);
|
||||||
else if (mode == "begin-action") {
|
else if (mode == "begin-action") {
|
||||||
let job_id = begin_external_service_action(ARGV[1], ARGV[2] || "initd", ARGV[3]);
|
let job_id = begin_external_service_action(ARGV[1], ARGV[2] || "initd", ARGV[3]);
|
||||||
if (job_id != "")
|
if (job_id != "")
|
||||||
|
|||||||
@@ -123,6 +123,30 @@ if initd_ucode start-retry-pending "$start_retry_file" >/dev/null 2>&1; then
|
|||||||
fail "cleared start retry marker should not be pending"
|
fail "cleared start retry marker should not be pending"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
retry_pid_file="$WORK_DIR/start-retry.pid"
|
||||||
|
retry_call_file="$WORK_DIR/start-retry.called"
|
||||||
|
retry_service="$WORK_DIR/forkop-init"
|
||||||
|
cat >"$retry_service" <<EOF
|
||||||
|
#!/bin/sh
|
||||||
|
printf '%s\n' "\$1" >>"$retry_call_file"
|
||||||
|
EOF
|
||||||
|
chmod +x "$retry_service"
|
||||||
|
FORKOP_SERVICE_INIT="$retry_service" \
|
||||||
|
FORKOP_START_RETRY_PID_FILE="$retry_pid_file" \
|
||||||
|
FORKOP_START_RETRY_DELAY_SECONDS=1 \
|
||||||
|
initd_ucode schedule-start-retry >/dev/null ||
|
||||||
|
fail "failed start should schedule an automatic retry"
|
||||||
|
[ -s "$retry_pid_file" ] ||
|
||||||
|
fail "scheduled automatic retry should record its worker pid"
|
||||||
|
for _ in 1 2 3 4 5; do
|
||||||
|
[ -s "$retry_call_file" ] && break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
grep -Fxq 'retry_start_on_wan_up' "$retry_call_file" ||
|
||||||
|
fail "scheduled automatic retry should call the marker-gated retry action"
|
||||||
|
[ ! -e "$retry_pid_file" ] ||
|
||||||
|
fail "automatic retry worker should clear its pid before retrying"
|
||||||
|
|
||||||
[ "$(initd_ucode retry-start-on-wan-up-action 1 1 1)" = "skip_running" ] ||
|
[ "$(initd_ucode retry-start-on-wan-up-action 1 1 1)" = "skip_running" ] ||
|
||||||
fail "WAN retry should skip when runtime is already running"
|
fail "WAN retry should skip when runtime is already running"
|
||||||
[ "$(initd_ucode retry-start-on-wan-up-action 0 0 1)" = "skip_disabled" ] ||
|
[ "$(initd_ucode retry-start-on-wan-up-action 0 0 1)" = "skip_disabled" ] ||
|
||||||
@@ -215,6 +239,8 @@ grep -Fq 'mode == "retry-start-on-wan-up"' "$INITD_UC" ||
|
|||||||
fail "service/initd.uc must expose the complete WAN retry entrypoint"
|
fail "service/initd.uc must expose the complete WAN retry entrypoint"
|
||||||
grep -Fq 'mode == "retry-start-on-wan-up-action"' "$INITD_UC" ||
|
grep -Fq 'mode == "retry-start-on-wan-up-action"' "$INITD_UC" ||
|
||||||
fail "service/initd.uc must expose the WAN retry decision fixture"
|
fail "service/initd.uc must expose the WAN retry decision fixture"
|
||||||
|
grep -Fq 'schedule_start_retry(START_RETRY_PID_FILE, START_RETRY_DELAY_SECONDS)' "$INITD_UC" ||
|
||||||
|
fail "failed service start must schedule a retry even if WAN is already up"
|
||||||
grep -Fq 'start_retry_pending(START_RETRY_FILE)' "$INITD_UC" ||
|
grep -Fq 'start_retry_pending(START_RETRY_FILE)' "$INITD_UC" ||
|
||||||
fail "WAN retry must be gated by a failed-start marker"
|
fail "WAN retry must be gated by a failed-start marker"
|
||||||
service_enabled_line="$(grep -nF 'function service_is_enabled()' "$INITD_UC" | head -n1 | cut -d: -f1)"
|
service_enabled_line="$(grep -nF 'function service_is_enabled()' "$INITD_UC" | head -n1 | cut -d: -f1)"
|
||||||
|
|||||||
Reference in New Issue
Block a user