mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 17:12:27 +03:00
* feat(proxy): add non-destructive auto-disable mode for the proxy health scheduler PROXY_AUTO_REMOVE was the only opt-in action the background proxy health scheduler could take on a consistently failing proxy, and it deletes the row. For a manually-maintained proxy chain (multi-proxy pool/rotation, #6365) that is too destructive just to exclude a temporarily-dead member. Add PROXY_AUTO_DISABLE as a sibling flag: at the same consecutive-failure threshold it soft-disables the proxy (status "dead") instead of removing it. "dead" is already one of the statuses the pool/rotation alive-filter excludes, so a disabled proxy drops out of the active chain immediately with no other code changes. The scheduler keeps probing dead proxies on its normal interval, and the existing recovery branch (previously autoRemove-only) re-activates it automatically once it starts answering again. decision.ts's decideProxyHealthAction() gets an optional `autoDisable` input (defaults to false, so existing callers are unaffected) and a "dead" status value; scheduler.ts wires the new PROXY_AUTO_DISABLE env flag through. If both flags are set, auto-remove wins. getProxyHealthStats() now also surfaces the registry `status` so operators can see when a proxy was auto-disabled, and ProxyStatusBadge now treats the full "not alive" status set (not just the literal string "inactive") as inactive in the dashboard. * test(proxy): assert registry status in getProxyHealthStats output The non-destructive auto-disable change added the live registry status to the stats object returned by getProxyHealthStats. Align the pre-existing db-proxies-crud assertion with the intended output shape. Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> * fix(proxy): preserve auto-disabled status in dashboard edits Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> Co-authored-by: Gi99lin <Gi99lin@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
/**
|
|
* PROXY_AUTO_DISABLE — non-destructive sibling of PROXY_AUTO_REMOVE (#6246 policy D).
|
|
*
|
|
* Mirrors tests/unit/proxy-health-decide-action-6246.test.ts. Where that suite
|
|
* covers the existing autoRemove-only behavior (untouched here), this suite
|
|
* covers the new `autoDisable` input: same consecutive-failure threshold, but
|
|
* the action at threshold is a soft `status: "dead"` write instead of deletion,
|
|
* and recovery re-activates exactly like autoRemove's does.
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { decideProxyHealthAction } = await import("../../src/lib/proxyHealth/decision.ts");
|
|
|
|
test("D: default (autoDisable off, autoRemove off) never mutates status on failure", () => {
|
|
const d = decideProxyHealthAction({
|
|
outcome: "fail",
|
|
priorFailures: 0,
|
|
autoRemove: false,
|
|
autoDisable: false,
|
|
removeAfter: 3,
|
|
});
|
|
assert.equal(d.setStatus, null);
|
|
assert.equal(d.remove, false);
|
|
assert.equal(d.failures, 1);
|
|
});
|
|
|
|
test("D: with autoDisable on, does NOT downgrade before the consecutive threshold", () => {
|
|
const d = decideProxyHealthAction({
|
|
outcome: "fail",
|
|
priorFailures: 1, // this probe makes it 2, threshold is 3
|
|
autoRemove: false,
|
|
autoDisable: true,
|
|
removeAfter: 3,
|
|
});
|
|
assert.equal(d.setStatus, null, "2 < 3 failures must not flip dead");
|
|
assert.equal(d.remove, false);
|
|
assert.equal(d.failures, 2);
|
|
});
|
|
|
|
test("D: with autoDisable on, soft-disables (status=dead) at the threshold — never removes", () => {
|
|
const d = decideProxyHealthAction({
|
|
outcome: "fail",
|
|
priorFailures: 2, // this probe makes it 3 == threshold
|
|
autoRemove: false,
|
|
autoDisable: true,
|
|
removeAfter: 3,
|
|
});
|
|
assert.equal(d.setStatus, "dead");
|
|
assert.equal(d.remove, false, "auto-disable must never delete the proxy row");
|
|
assert.equal(d.failures, 3);
|
|
});
|
|
|
|
test("D: when both autoRemove and autoDisable are on, the destructive action wins", () => {
|
|
const d = decideProxyHealthAction({
|
|
outcome: "fail",
|
|
priorFailures: 2,
|
|
autoRemove: true,
|
|
autoDisable: true,
|
|
removeAfter: 3,
|
|
});
|
|
assert.equal(d.setStatus, "inactive");
|
|
assert.equal(d.remove, true, "auto-remove takes precedence when both flags are set");
|
|
});
|
|
|
|
test("D: ok probe re-activates a proxy that autoDisable manages, and resets the streak", () => {
|
|
const d = decideProxyHealthAction({
|
|
outcome: "ok",
|
|
priorFailures: 5,
|
|
autoRemove: false,
|
|
autoDisable: true,
|
|
removeAfter: 3,
|
|
});
|
|
assert.equal(d.clearFailures, true);
|
|
assert.equal(d.setStatus, "active", "recovery flips a soft-disabled proxy back to active");
|
|
assert.equal(d.failures, 0);
|
|
});
|
|
|
|
test("D: inconclusive probes stay neutral under autoDisable, same as under autoRemove", () => {
|
|
const d = decideProxyHealthAction({
|
|
outcome: "inconclusive",
|
|
priorFailures: 2,
|
|
autoRemove: false,
|
|
autoDisable: true,
|
|
removeAfter: 3,
|
|
});
|
|
assert.equal(d.setStatus, null);
|
|
assert.equal(d.remove, false);
|
|
assert.equal(d.failures, 2);
|
|
assert.equal(d.clearFailures, false);
|
|
});
|
|
|
|
test("D: autoDisable defaults to false when omitted — behaves exactly like pre-existing callers", () => {
|
|
const d = decideProxyHealthAction({
|
|
outcome: "fail",
|
|
priorFailures: 2,
|
|
autoRemove: false,
|
|
removeAfter: 3,
|
|
});
|
|
assert.equal(d.setStatus, null, "omitting autoDisable must not silently opt in");
|
|
assert.equal(d.remove, false);
|
|
});
|