feat: Implement load balancing and health check features for service groups, including DNS-based load balancing modes and health check configurations, enhancing service reliability and performance monitoring
Build, Test, and Push CFDM Docker Image / test (push) Successful in 4m0s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m13s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Failing after 6s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped

This commit is contained in:
Denozordec
2026-06-25 18:24:00 +07:00
parent e5dc483d43
commit 6a1498bf80
35 changed files with 5312 additions and 281 deletions
+124
View File
@@ -0,0 +1,124 @@
import { describe, expect, it, beforeAll, afterAll } from "vitest";
import { createServer, type Server } from "node:net";
import * as healthCheckService from "../src/services/health-check-service.js";
import type { HealthCheckTarget } from "@cfdm/shared";
function startTcpServer(): Promise<{ server: Server; port: number }> {
return new Promise((resolve) => {
const server = createServer();
server.listen(0, "127.0.0.1", () => {
const address = server.address();
const port =
typeof address === "object" && address ? address.port : 0;
resolve({ server, port });
});
});
}
describe("health-check probeTarget", () => {
let server: Server;
let port: number;
beforeAll(async () => {
const started = await startTcpServer();
server = started.server;
port = started.port;
});
afterAll(async () => {
await new Promise<void>((resolve) => server.close(() => resolve()));
});
it("tcp probe succeeds for open port", async () => {
const target: HealthCheckTarget = {
scope: "binding",
ref_id: 1,
ip: "127.0.0.1",
hostname: "test.local",
type: "tcp",
port,
path: null,
expected_status: null,
timeout_ms: 1000,
};
const result = await healthCheckService.probeTarget(target);
expect(result.ok).toBe(true);
expect(result.error).toBeNull();
expect(result.latencyMs).toBeGreaterThanOrEqual(0);
});
it("tcp probe fails for closed port", async () => {
const target: HealthCheckTarget = {
scope: "binding",
ref_id: 1,
ip: "127.0.0.1",
hostname: "test.local",
type: "tcp",
port: 1,
path: null,
expected_status: null,
timeout_ms: 500,
};
const result = await healthCheckService.probeTarget(target);
expect(result.ok).toBe(false);
expect(result.error).not.toBeNull();
});
});
describe("health-check state derivation via runAllChecks", () => {
it("marks ip down after threshold failures and up after recovery", async () => {
const { createMemoryDb, repos, runMigrations } = await import("@cfdm/db");
const { db, sqlite } = createMemoryDb();
runMigrations(sqlite);
const domain = repos.createDomain(db, null, "example.com", "zone-id");
const service = repos.createService(db, "Svc", "svc");
const binding = repos.insertBinding(
db,
domain.id,
service.id,
"@",
null,
);
repos.updateBindingLbConfig(db, binding.id, {
health_check_enabled: true,
health_check_type: "tcp",
health_check_port: 1,
health_check_timeout_ms: 200,
});
repos.replaceBindingIpsWithMeta(db, binding.id, [
{ ip: "127.0.0.1", weight: 1, priority: 1 },
]);
// Запуск с closed port (port 1) — должен зафиксировать down после 2 проверок
await healthCheckService.runAllChecks(db, {
thresholds: {
degradedFailures: 1,
downFailures: 2,
latencyWarnMs: 1000,
},
});
let status = repos.getIpHealthStatusRow(
db,
"binding",
binding.id,
"127.0.0.1",
);
expect(status?.status).toBe("degraded");
await healthCheckService.runAllChecks(db, {
thresholds: {
degradedFailures: 1,
downFailures: 2,
latencyWarnMs: 1000,
},
});
status = repos.getIpHealthStatusRow(
db,
"binding",
binding.id,
"127.0.0.1",
);
expect(status?.status).toBe("down");
});
});
+98
View File
@@ -0,0 +1,98 @@
import { describe, expect, it } from "vitest";
import {
selectActiveIpsByMode,
type LbIpRow,
type LbTargetConfig,
} from "../src/services/service-config-service.js";
function row(
ip: string,
opts: Partial<Pick<LbIpRow, "weight" | "priority" | "health">> = {},
): LbIpRow {
return {
ip,
weight: opts.weight ?? 1,
priority: opts.priority ?? 1,
health: opts.health ?? "up",
};
}
describe("selectActiveIpsByMode", () => {
it("round_robin returns all healthy ips, falls back to all if none healthy", () => {
const config: LbTargetConfig = {
lb_mode: "round_robin",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { health: "up" }),
row("2.2.2.2", { health: "down" }),
row("3.3.3.3", { health: "up" }),
];
expect(selectActiveIpsByMode(config, rows).sort()).toEqual([
"1.1.1.1",
"3.3.3.3",
]);
});
it("round_robin returns all ips when none checked (unknown)", () => {
const config: LbTargetConfig = {
lb_mode: "round_robin",
health_check_enabled: true,
};
const rows = [row("1.1.1.1", { health: "unknown" }), row("2.2.2.2")];
expect(selectActiveIpsByMode(config, rows).sort()).toEqual([
"1.1.1.1",
"2.2.2.2",
]);
});
it("failover returns only min-priority healthy ips", () => {
const config: LbTargetConfig = {
lb_mode: "failover",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { priority: 1, health: "up" }),
row("2.2.2.2", { priority: 2, health: "up" }),
row("3.3.3.3", { priority: 1, health: "down" }),
];
expect(selectActiveIpsByMode(config, rows)).toEqual(["1.1.1.1"]);
});
it("failover falls back to min-priority ip among all when none healthy", () => {
const config: LbTargetConfig = {
lb_mode: "failover",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { priority: 3, health: "down" }),
row("2.2.2.2", { priority: 1, health: "down" }),
row("3.3.3.3", { priority: 2, health: "down" }),
];
expect(selectActiveIpsByMode(config, rows)).toEqual(["2.2.2.2"]);
});
it("weighted returns all healthy ips (one A per ip; weights stored for display)", () => {
const config: LbTargetConfig = {
lb_mode: "weighted",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { weight: 3, health: "up" }),
row("2.2.2.2", { weight: 1, health: "up" }),
row("3.3.3.3", { weight: 2, health: "down" }),
];
expect(selectActiveIpsByMode(config, rows).sort()).toEqual([
"1.1.1.1",
"2.2.2.2",
]);
});
it("returns empty array for no rows", () => {
const config: LbTargetConfig = {
lb_mode: "round_robin",
health_check_enabled: true,
};
expect(selectActiveIpsByMode(config, [])).toEqual([]);
});
});