@aristanetworks/eos-mcp-server 0.8.1-beta → 0.9.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +17 -14
- package/dist/commands/probe.js +2 -1
- package/dist/commands/probe.js.map +1 -1
- package/dist/config/diagnostics.d.ts +5 -0
- package/dist/config/diagnostics.js +5 -1
- package/dist/config/diagnostics.js.map +1 -1
- package/dist/config/loadConfig.js +1 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.d.ts +95 -1
- package/dist/config/schema.js +14 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/connection/eosDeviceReader.d.ts +6 -1
- package/dist/connection/eosDeviceReader.js +14 -3
- package/dist/connection/eosDeviceReader.js.map +1 -1
- package/dist/diagnostics/executor.d.ts +7 -3
- package/dist/diagnostics/executor.js +16 -13
- package/dist/diagnostics/executor.js.map +1 -1
- package/dist/diagnostics/types.d.ts +19 -2
- package/dist/mcp/createServer.js +20 -3
- package/dist/mcp/createServer.js.map +1 -1
- package/dist/mcp/tools/ping.d.ts +2 -2
- package/dist/operations/eapiSlots.d.ts +68 -0
- package/dist/operations/eapiSlots.js +237 -0
- package/dist/operations/eapiSlots.js.map +1 -0
- package/dist/operations/readExecution.js +1 -1
- package/dist/operations/readExecution.js.map +1 -1
- package/dist/serverInfo/buildServerInfo.js +2 -0
- package/dist/serverInfo/buildServerInfo.js.map +1 -1
- package/dist/serverInfo/types.d.ts +2 -0
- package/docs/CODE_WALKTHROUGH.md +3 -3
- package/docs/DEMO_SKILLS_YOUTUBE.md +346 -0
- package/docs/DESIGN.md +3 -3
- package/docs/DIAGNOSTICS.md +14 -11
- package/docs/adr/0005-multi-destination-ping.md +4 -0
- package/docs/adr/0006-in-memory-timed-diagnostic-capacity.md +2 -0
- package/docs/adr/0007-per-device-diagnostic-concurrency.md +16 -0
- package/docs/adr/0008-read-only-tool-annotations.md +16 -0
- package/docs/adr/0009-shared-per-device-eapi-slots.md +40 -0
- package/docs/research/eos-linter-integration.md +70 -0
- package/package.json +1 -1
- package/dist/diagnostics/concurrency.d.ts +0 -29
- package/dist/diagnostics/concurrency.js +0 -115
- package/dist/diagnostics/concurrency.js.map +0 -1
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
import { AppError, toErrorCode } from "../core/errors.js";
|
|
2
2
|
import { resolveInventoryTarget } from "../inventory/resolveTarget.js";
|
|
3
|
-
import { buildDeviceResultSummary, buildReadDeviceFailure, buildReadDeviceSuccess, buildReadOperationResultEnvelope, enforceReadTargetLimit,
|
|
4
|
-
import {
|
|
3
|
+
import { buildDeviceResultSummary, buildReadDeviceFailure, buildReadDeviceSuccess, buildReadOperationResultEnvelope, enforceReadTargetLimit, runWithOperationTimeout } from "../operations/readExecution.js";
|
|
4
|
+
import { EapiSlots, eapiSlotLimits } from "../operations/eapiSlots.js";
|
|
5
5
|
import { DiagnosticExecutionError } from "./executionError.js";
|
|
6
6
|
import { pingDiagnostic } from "./ping.js";
|
|
7
7
|
const diagnosticDefinitions = {
|
|
8
8
|
ping: pingDiagnostic
|
|
9
9
|
};
|
|
10
|
-
/** Owns policy, budgets, fan-out,
|
|
10
|
+
/** Owns policy, budgets, fan-out, cancellation, and result normalization. Admission, per device and
|
|
11
|
+
* server-wide, belongs to the EapiSlots it shares with read tools.
|
|
12
|
+
*/
|
|
11
13
|
export class DiagnosticExecutor {
|
|
12
14
|
model;
|
|
13
15
|
config;
|
|
14
16
|
runner;
|
|
15
|
-
|
|
17
|
+
slots;
|
|
16
18
|
shutdown = new AbortController();
|
|
17
|
-
|
|
19
|
+
/** A running server passes its shared `slots`; tests may omit it to get a private instance. */
|
|
20
|
+
constructor(model, config, runner, slots = new EapiSlots(eapiSlotLimits(config), config.diagnostics.commandTimeoutMs)) {
|
|
18
21
|
this.model = model;
|
|
19
22
|
this.config = config;
|
|
20
23
|
this.runner = runner;
|
|
21
|
-
this.
|
|
24
|
+
this.slots = slots;
|
|
22
25
|
}
|
|
23
26
|
close() {
|
|
24
27
|
this.shutdown.abort(new AppError("diagnostic_server_stopping", "Diagnostic executor is stopping"));
|
|
25
|
-
this.
|
|
28
|
+
this.slots.close();
|
|
26
29
|
}
|
|
27
30
|
getCapacity() {
|
|
28
|
-
return this.
|
|
31
|
+
return this.slots.snapshot();
|
|
29
32
|
}
|
|
30
33
|
async execute(options) {
|
|
31
34
|
const signal = options.signal === undefined ? this.shutdown.signal : AbortSignal.any([options.signal, this.shutdown.signal]);
|
|
@@ -34,7 +37,6 @@ export class DiagnosticExecutor {
|
|
|
34
37
|
const operationName = `eos_${options.request.kind}`;
|
|
35
38
|
const operationConfig = {
|
|
36
39
|
...this.config,
|
|
37
|
-
deviceConcurrency: this.config.diagnostics.deviceConcurrency,
|
|
38
40
|
overallOperationTimeoutMs: this.config.diagnostics.overallOperationTimeoutMs
|
|
39
41
|
};
|
|
40
42
|
const resolvedTarget = resolveInventoryTarget(this.model, { target: options.target });
|
|
@@ -47,9 +49,10 @@ export class DiagnosticExecutor {
|
|
|
47
49
|
if (resolvedTarget.resolvedHosts.length * probesPerHost > this.config.diagnostics.maxProbesPerRequest) {
|
|
48
50
|
throw new AppError("diagnostic_probe_limit_exceeded", `Request exceeds maxProbesPerRequest ${this.config.diagnostics.maxProbesPerRequest}`);
|
|
49
51
|
}
|
|
50
|
-
// Each host/execution pair takes its own reservation
|
|
52
|
+
// Each host/execution pair takes its own reservation, and all are queued at once so the admission
|
|
53
|
+
// controller, not fan-out order, decides which device runs next. Results stay in host-then-execution order.
|
|
51
54
|
const hostExecutions = resolvedTarget.resolvedHosts.flatMap((host) => prepared.executions.map((execution) => ({ host, execution })));
|
|
52
|
-
const results = await runWithOperationTimeout(operationConfig, operationName, (operationSignal) =>
|
|
55
|
+
const results = await runWithOperationTimeout(operationConfig, operationName, (operationSignal) => Promise.all(hostExecutions.map(async ({ host, execution }) => {
|
|
53
56
|
try {
|
|
54
57
|
return await this.runExecution(definition, host, execution, operationSignal, options.includeRaw === true);
|
|
55
58
|
}
|
|
@@ -62,7 +65,7 @@ export class DiagnosticExecutor {
|
|
|
62
65
|
: {})
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
|
-
}
|
|
68
|
+
})), signal);
|
|
66
69
|
return buildReadOperationResultEnvelope(options.target, { resolvedTarget, results, summary: buildDeviceResultSummary(results) }, {
|
|
67
70
|
extra: { request: prepared.request },
|
|
68
71
|
responseSizeLimit: { config: this.config, operationName }
|
|
@@ -85,7 +88,7 @@ export class DiagnosticExecutor {
|
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
async executeOnDevice(host, request, signal) {
|
|
88
|
-
const reservation = await this.
|
|
91
|
+
const reservation = await this.slots.acquireDiagnostic(signal, { inventoryHostname: host.inventoryHostname, diagnostic: request.kind });
|
|
89
92
|
if (signal.aborted) {
|
|
90
93
|
reservation.complete();
|
|
91
94
|
throw new AppError("operation_aborted", "Diagnostic was cancelled before dispatch");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/diagnostics/executor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,gCAAgC,EAChC,sBAAsB,EACtB,
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/diagnostics/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -61,12 +61,28 @@ export interface DiagnosticReservationInfo {
|
|
|
61
61
|
releases_at?: string;
|
|
62
62
|
reason_code?: string;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
limit: number;
|
|
64
|
+
interface DiagnosticSlotCounts {
|
|
66
65
|
available_count: number;
|
|
67
66
|
active_count: number;
|
|
68
67
|
uncertain_count: number;
|
|
69
68
|
queued_count: number;
|
|
69
|
+
}
|
|
70
|
+
/** Slot usage of one inventory host; only hosts holding or awaiting a slot are listed.
|
|
71
|
+
* The four shared counts are diagnostic slots; `available_count` is the diagnostic headroom,
|
|
72
|
+
* capped by the host's shared eAPI headroom and server-wide availability.
|
|
73
|
+
*/
|
|
74
|
+
export interface DiagnosticDeviceCapacity extends DiagnosticSlotCounts {
|
|
75
|
+
inventory_hostname: string;
|
|
76
|
+
read_active_count: number;
|
|
77
|
+
read_queued_count: number;
|
|
78
|
+
}
|
|
79
|
+
/** Top-level counts are server-wide diagnostics; `devices` breaks them down per host, with reads. */
|
|
80
|
+
export interface DiagnosticCapacitySnapshot extends DiagnosticSlotCounts {
|
|
81
|
+
limit: number;
|
|
82
|
+
device_limit: number;
|
|
83
|
+
/** eAPI slots one host may hold across reads and diagnostics. */
|
|
84
|
+
eapi_device_limit: number;
|
|
85
|
+
devices: DiagnosticDeviceCapacity[];
|
|
70
86
|
reservations: DiagnosticReservationInfo[];
|
|
71
87
|
}
|
|
72
88
|
export interface DiagnosticExecutionContext {
|
|
@@ -79,3 +95,4 @@ export interface DiagnosticExecutionContext {
|
|
|
79
95
|
export interface DiagnosticDeviceRunner {
|
|
80
96
|
execute(host: InventoryHostModel, request: PreparedDiagnostic, context: DiagnosticExecutionContext): Promise<DiagnosticObservation>;
|
|
81
97
|
}
|
|
98
|
+
export {};
|
package/dist/mcp/createServer.js
CHANGED
|
@@ -7,6 +7,7 @@ import { toErrorMessage } from "../core/errors.js";
|
|
|
7
7
|
import { EapiClient } from "../eapi/client.js";
|
|
8
8
|
import { EapiDiagnosticRunner } from "../diagnostics/eapiRunner.js";
|
|
9
9
|
import { DiagnosticExecutor } from "../diagnostics/executor.js";
|
|
10
|
+
import { EapiSlots, eapiSlotLimits } from "../operations/eapiSlots.js";
|
|
10
11
|
import { buildPingToolResult, pingInputSchema } from "./tools/ping.js";
|
|
11
12
|
import { buildGetFactsToolResult, getFactsInputSchema } from "./tools/getFacts.js";
|
|
12
13
|
import { buildGetRunningConfigToolResult, getRunningConfigInputSchema } from "./tools/getRunningConfig.js";
|
|
@@ -15,54 +16,68 @@ import { buildListInventoryToolResult, listInventoryInputSchema } from "./tools/
|
|
|
15
16
|
import { buildProbeDevicesToolResult, probeDevicesInputSchema } from "./tools/probeDevices.js";
|
|
16
17
|
import { buildRunShowToolResult, runShowInputSchema } from "./tools/runShow.js";
|
|
17
18
|
import { buildShowLoggingToolResult, showLoggingInputSchema } from "./tools/showLogging.js";
|
|
19
|
+
// Every tool leaves device state unchanged, which lets clients run independent
|
|
20
|
+
// calls concurrently; server-side limits still bound diagnostics (ADR 0008).
|
|
21
|
+
// Most tools reach only inventory devices.
|
|
22
|
+
const closedWorldReadOnly = { readOnlyHint: true, openWorldHint: false };
|
|
23
|
+
// Ping destinations need not be in inventory.
|
|
24
|
+
const openWorldReadOnly = { readOnlyHint: true, openWorldHint: true };
|
|
18
25
|
function buildToolDefinitions(deps) {
|
|
19
26
|
return [
|
|
20
27
|
{
|
|
21
28
|
name: "eos_get_server_info",
|
|
22
29
|
description: "Return a sanitized summary of current server capabilities, limits, runtime mode, and inventory summary.",
|
|
23
30
|
inputSchema: getServerInfoInputSchema,
|
|
31
|
+
annotations: closedWorldReadOnly,
|
|
24
32
|
handler: async () => buildGetServerInfoToolResult(deps.runtimeContext, deps.diagnostics.getCapacity())
|
|
25
33
|
},
|
|
26
34
|
{
|
|
27
35
|
name: "eos_list_inventory",
|
|
28
36
|
description: "Return a sanitized operational view of the loaded inventory. By default only EOS-eligible hosts are shown, while all groups remain visible with actionable status.",
|
|
29
37
|
inputSchema: listInventoryInputSchema,
|
|
38
|
+
annotations: closedWorldReadOnly,
|
|
30
39
|
handler: async (args) => buildListInventoryToolResult(deps.inventoryModel, args)
|
|
31
40
|
},
|
|
32
41
|
{
|
|
33
42
|
name: "eos_probe_devices",
|
|
34
43
|
description: "Probe EOS device readiness for a host or group target by validating connectivity, authentication, and harmless command execution.",
|
|
35
44
|
inputSchema: probeDevicesInputSchema,
|
|
45
|
+
annotations: closedWorldReadOnly,
|
|
36
46
|
handler: async (args) => buildProbeDevicesToolResult(deps.inventoryModel, deps.config, args, deps.deviceReader)
|
|
37
47
|
},
|
|
38
48
|
{
|
|
39
49
|
name: "eos_ping",
|
|
40
50
|
description: "Run bounded, non-configuring ping diagnostics from an inventory host or group to one or more destinations concurrently. Requires explicit diagnostic permission. Destinations are unicast IP literals. Returns one result per host and destination; execution success is separate from reachability, and no replies do not identify a cause.",
|
|
41
51
|
inputSchema: pingInputSchema,
|
|
52
|
+
annotations: openWorldReadOnly,
|
|
42
53
|
handler: async (args, extra) => buildPingToolResult(args, deps.diagnostics, extra.signal)
|
|
43
54
|
},
|
|
44
55
|
{
|
|
45
56
|
name: "eos_run_show",
|
|
46
57
|
description: "Run one or more EOS show commands against a host or group target. Supports auto/json/text output modes with strict show-only validation.",
|
|
47
58
|
inputSchema: runShowInputSchema,
|
|
59
|
+
annotations: closedWorldReadOnly,
|
|
48
60
|
handler: async (args) => buildRunShowToolResult(deps.inventoryModel, deps.config, args, deps.deviceReader)
|
|
49
61
|
},
|
|
50
62
|
{
|
|
51
63
|
name: "eos_show_logging",
|
|
52
64
|
description: "Retrieve bounded EOS logging output for a host or group target using a minimum severity threshold and message-count limit.",
|
|
53
65
|
inputSchema: showLoggingInputSchema,
|
|
66
|
+
annotations: closedWorldReadOnly,
|
|
54
67
|
handler: async (args) => buildShowLoggingToolResult(deps.inventoryModel, deps.config, args, deps.deviceReader)
|
|
55
68
|
},
|
|
56
69
|
{
|
|
57
70
|
name: "eos_get_facts",
|
|
58
71
|
description: "Collect a fixed core set of EOS device facts for a host or group target.",
|
|
59
72
|
inputSchema: getFactsInputSchema,
|
|
73
|
+
annotations: closedWorldReadOnly,
|
|
60
74
|
handler: async (args) => buildGetFactsToolResult(deps.inventoryModel, deps.config, args, deps.deviceReader)
|
|
61
75
|
},
|
|
62
76
|
{
|
|
63
77
|
name: "eos_get_running_config",
|
|
64
78
|
description: "Retrieve running configuration text for a host target, or for a group target when a raw EOS section selector is provided.",
|
|
65
79
|
inputSchema: getRunningConfigInputSchema,
|
|
80
|
+
annotations: closedWorldReadOnly,
|
|
66
81
|
handler: async (args) => buildGetRunningConfigToolResult(deps.inventoryModel, deps.config, args, deps.deviceReader)
|
|
67
82
|
}
|
|
68
83
|
];
|
|
@@ -75,8 +90,9 @@ export async function startMcpServer(config, inventoryModel) {
|
|
|
75
90
|
inventorySummary: inventoryModel.summary
|
|
76
91
|
};
|
|
77
92
|
const client = new EapiClient();
|
|
78
|
-
const
|
|
79
|
-
const
|
|
93
|
+
const slots = new EapiSlots(eapiSlotLimits(config), config.diagnostics.commandTimeoutMs);
|
|
94
|
+
const deviceReader = new EapiDeviceReader(config, client, slots);
|
|
95
|
+
const diagnostics = new DiagnosticExecutor(inventoryModel, config, new EapiDiagnosticRunner(config, client), slots);
|
|
80
96
|
const server = new McpServer({
|
|
81
97
|
name: APP_NAME,
|
|
82
98
|
version: APP_VERSION
|
|
@@ -85,7 +101,8 @@ export async function startMcpServer(config, inventoryModel) {
|
|
|
85
101
|
for (const tool of buildToolDefinitions({ runtimeContext, inventoryModel, config, deviceReader, diagnostics })) {
|
|
86
102
|
registerTool(tool.name, {
|
|
87
103
|
description: tool.description,
|
|
88
|
-
inputSchema: tool.inputSchema
|
|
104
|
+
inputSchema: tool.inputSchema,
|
|
105
|
+
annotations: tool.annotations
|
|
89
106
|
}, tool.handler);
|
|
90
107
|
}
|
|
91
108
|
const stop = () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createServer.js","sourceRoot":"","sources":["../../src/mcp/createServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"createServer.js","sourceRoot":"","sources":["../../src/mcp/createServer.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/tools/ping.d.ts
CHANGED
|
@@ -8,15 +8,15 @@ export declare const pingInputSchema: z.ZodObject<{
|
|
|
8
8
|
source: z.ZodOptional<z.ZodString>;
|
|
9
9
|
include_raw: z.ZodDefault<z.ZodBoolean>;
|
|
10
10
|
}, "strict", z.ZodTypeAny, {
|
|
11
|
-
target: string;
|
|
12
11
|
destinations: string[];
|
|
12
|
+
target: string;
|
|
13
13
|
include_raw: boolean;
|
|
14
14
|
count?: number | undefined;
|
|
15
15
|
vrf?: string | undefined;
|
|
16
16
|
source?: string | undefined;
|
|
17
17
|
}, {
|
|
18
|
-
target: string;
|
|
19
18
|
destinations: string[];
|
|
19
|
+
target: string;
|
|
20
20
|
count?: number | undefined;
|
|
21
21
|
vrf?: string | undefined;
|
|
22
22
|
source?: string | undefined;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { DiagnosticCapacitySnapshot, DiagnosticRequest } from "../diagnostics/types.js";
|
|
2
|
+
interface DiagnosticReservationIntent {
|
|
3
|
+
inventoryHostname: string;
|
|
4
|
+
diagnostic: DiagnosticRequest["kind"];
|
|
5
|
+
}
|
|
6
|
+
export interface DiagnosticReservation {
|
|
7
|
+
readonly requestId: string;
|
|
8
|
+
complete(): void;
|
|
9
|
+
markUncertain(reasonCode: string): void;
|
|
10
|
+
}
|
|
11
|
+
export interface EapiSlotLimits {
|
|
12
|
+
/** Slots one inventory host may hold across reads and diagnostics. */
|
|
13
|
+
deviceLimit: number;
|
|
14
|
+
/** Of those, the slots diagnostics may hold on one host, so the rest stay free for reads. */
|
|
15
|
+
diagnosticDeviceLimit: number;
|
|
16
|
+
/** Diagnostic slots shared by every device in the server process. Reads have no server-wide limit. */
|
|
17
|
+
diagnosticServerLimit: number;
|
|
18
|
+
}
|
|
19
|
+
/** One instance per server process, shared by every eAPI request (ADR 0009). A slot is granted only
|
|
20
|
+
* while its device has room and, for a diagnostic, its device's diagnostic share and the server-wide
|
|
21
|
+
* diagnostic ceiling have room; waiters are served oldest first, skipping those that cannot run yet
|
|
22
|
+
* (ADR 0007). A read frees its slot when its request settles. A diagnostic slot with unknown remote
|
|
23
|
+
* completion stays held for the execution budget, then frees itself; a late confirmed response frees
|
|
24
|
+
* it sooner (ADR 0006).
|
|
25
|
+
*/
|
|
26
|
+
export declare class EapiSlots {
|
|
27
|
+
private readonly limits;
|
|
28
|
+
private readonly uncertainHoldMs;
|
|
29
|
+
/** Held diagnostic slots; an uncertain slot carries the timer that frees it. */
|
|
30
|
+
private readonly reservations;
|
|
31
|
+
private readonly readsHeldByDevice;
|
|
32
|
+
private readonly waiting;
|
|
33
|
+
private closed;
|
|
34
|
+
constructor(limits: EapiSlotLimits, uncertainHoldMs: number);
|
|
35
|
+
close(): void;
|
|
36
|
+
/** Top-level counts cover diagnostics; `devices` also shows each host's read slots. */
|
|
37
|
+
snapshot(): DiagnosticCapacitySnapshot;
|
|
38
|
+
/** Per-host counts for every host holding or awaiting a slot, sorted by hostname. */
|
|
39
|
+
private deviceBreakdown;
|
|
40
|
+
/** Waits for a read slot on the host; the returned function frees it. */
|
|
41
|
+
acquireRead(signal: AbortSignal | undefined, inventoryHostname: string): Promise<() => void>;
|
|
42
|
+
acquireDiagnostic(signal: AbortSignal | undefined, intent: DiagnosticReservationIntent): Promise<DiagnosticReservation>;
|
|
43
|
+
private enqueue;
|
|
44
|
+
private hasRoom;
|
|
45
|
+
/** Diagnostic slots a host could take now: the tightest of its diagnostic share, its shared device
|
|
46
|
+
* slots, and the server-wide diagnostic slots still free. Admission and the snapshot both use it.
|
|
47
|
+
*/
|
|
48
|
+
private diagnosticHeadroom;
|
|
49
|
+
/** Counted from the held reservations, which never exceed the server-wide diagnostic limit. */
|
|
50
|
+
private diagnosticSlotsHeldBy;
|
|
51
|
+
private reserveRead;
|
|
52
|
+
/** Keeps only hosts that hold reads, so the snapshot lists no idle hosts. */
|
|
53
|
+
private adjustReadsHeld;
|
|
54
|
+
private reserveDiagnostic;
|
|
55
|
+
private freeDiagnostic;
|
|
56
|
+
/** Grant the oldest waiters that can run now; a waiter that cannot run yet keeps its place. */
|
|
57
|
+
private grantWaiting;
|
|
58
|
+
private markUncertain;
|
|
59
|
+
}
|
|
60
|
+
/** Limits from config: top-level `deviceConcurrency` per device, `diagnostics.*` for the diagnostic share. */
|
|
61
|
+
export declare function eapiSlotLimits(config: {
|
|
62
|
+
deviceConcurrency: number;
|
|
63
|
+
diagnostics: {
|
|
64
|
+
deviceConcurrency: number;
|
|
65
|
+
maxConcurrentDiagnostics: number;
|
|
66
|
+
};
|
|
67
|
+
}): EapiSlotLimits;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { AppError } from "../core/errors.js";
|
|
3
|
+
/** One instance per server process, shared by every eAPI request (ADR 0009). A slot is granted only
|
|
4
|
+
* while its device has room and, for a diagnostic, its device's diagnostic share and the server-wide
|
|
5
|
+
* diagnostic ceiling have room; waiters are served oldest first, skipping those that cannot run yet
|
|
6
|
+
* (ADR 0007). A read frees its slot when its request settles. A diagnostic slot with unknown remote
|
|
7
|
+
* completion stays held for the execution budget, then frees itself; a late confirmed response frees
|
|
8
|
+
* it sooner (ADR 0006).
|
|
9
|
+
*/
|
|
10
|
+
export class EapiSlots {
|
|
11
|
+
limits;
|
|
12
|
+
uncertainHoldMs;
|
|
13
|
+
/** Held diagnostic slots; an uncertain slot carries the timer that frees it. */
|
|
14
|
+
reservations = new Map();
|
|
15
|
+
readsHeldByDevice = new Map();
|
|
16
|
+
waiting = [];
|
|
17
|
+
closed = false;
|
|
18
|
+
constructor(limits, uncertainHoldMs) {
|
|
19
|
+
this.limits = limits;
|
|
20
|
+
this.uncertainHoldMs = uncertainHoldMs;
|
|
21
|
+
}
|
|
22
|
+
close() {
|
|
23
|
+
if (this.closed)
|
|
24
|
+
return;
|
|
25
|
+
this.closed = true;
|
|
26
|
+
for (const timer of this.reservations.values())
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
const error = closedError();
|
|
29
|
+
for (const waiter of this.waiting.splice(0))
|
|
30
|
+
waiter.reject(error);
|
|
31
|
+
}
|
|
32
|
+
/** Top-level counts cover diagnostics; `devices` also shows each host's read slots. */
|
|
33
|
+
snapshot() {
|
|
34
|
+
const reservations = Array.from(this.reservations.keys(), (reservation) => ({ ...reservation }));
|
|
35
|
+
const uncertain = reservations.filter((reservation) => reservation.state === "uncertain").length;
|
|
36
|
+
const serverAvailable = this.closed ? 0 : Math.max(0, this.limits.diagnosticServerLimit - reservations.length);
|
|
37
|
+
const queuedDiagnostics = this.waiting.filter((waiter) => waiter.kind === "diagnostic").length;
|
|
38
|
+
return {
|
|
39
|
+
limit: this.limits.diagnosticServerLimit,
|
|
40
|
+
device_limit: this.limits.diagnosticDeviceLimit,
|
|
41
|
+
eapi_device_limit: this.limits.deviceLimit,
|
|
42
|
+
available_count: serverAvailable,
|
|
43
|
+
active_count: reservations.length - uncertain,
|
|
44
|
+
uncertain_count: uncertain,
|
|
45
|
+
queued_count: queuedDiagnostics,
|
|
46
|
+
devices: this.deviceBreakdown(reservations, serverAvailable),
|
|
47
|
+
reservations
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Per-host counts for every host holding or awaiting a slot, sorted by hostname. */
|
|
51
|
+
deviceBreakdown(reservations, serverAvailable) {
|
|
52
|
+
const devices = new Map();
|
|
53
|
+
const deviceEntry = (inventoryHostname) => {
|
|
54
|
+
let entry = devices.get(inventoryHostname);
|
|
55
|
+
if (entry === undefined) {
|
|
56
|
+
entry = {
|
|
57
|
+
inventory_hostname: inventoryHostname,
|
|
58
|
+
available_count: 0, active_count: 0, uncertain_count: 0, queued_count: 0,
|
|
59
|
+
read_active_count: 0, read_queued_count: 0
|
|
60
|
+
};
|
|
61
|
+
devices.set(inventoryHostname, entry);
|
|
62
|
+
}
|
|
63
|
+
return entry;
|
|
64
|
+
};
|
|
65
|
+
for (const reservation of reservations) {
|
|
66
|
+
const entry = deviceEntry(reservation.inventory_hostname);
|
|
67
|
+
if (reservation.state === "uncertain")
|
|
68
|
+
entry.uncertain_count += 1;
|
|
69
|
+
else
|
|
70
|
+
entry.active_count += 1;
|
|
71
|
+
}
|
|
72
|
+
for (const [inventoryHostname, readsHeld] of this.readsHeldByDevice)
|
|
73
|
+
deviceEntry(inventoryHostname).read_active_count = readsHeld;
|
|
74
|
+
for (const waiter of this.waiting) {
|
|
75
|
+
const entry = deviceEntry(waiter.inventoryHostname);
|
|
76
|
+
if (waiter.kind === "diagnostic")
|
|
77
|
+
entry.queued_count += 1;
|
|
78
|
+
else
|
|
79
|
+
entry.read_queued_count += 1;
|
|
80
|
+
}
|
|
81
|
+
for (const entry of devices.values()) {
|
|
82
|
+
entry.available_count = this.diagnosticHeadroom(entry.active_count + entry.uncertain_count, entry.read_active_count, serverAvailable);
|
|
83
|
+
}
|
|
84
|
+
return Array.from(devices.values()).sort((left, right) => left.inventory_hostname.localeCompare(right.inventory_hostname));
|
|
85
|
+
}
|
|
86
|
+
/** Waits for a read slot on the host; the returned function frees it. */
|
|
87
|
+
acquireRead(signal, inventoryHostname) {
|
|
88
|
+
return this.enqueue(signal, inventoryHostname, "read", () => this.reserveRead(inventoryHostname));
|
|
89
|
+
}
|
|
90
|
+
acquireDiagnostic(signal, intent) {
|
|
91
|
+
return this.enqueue(signal, intent.inventoryHostname, "diagnostic", () => this.reserveDiagnostic(intent));
|
|
92
|
+
}
|
|
93
|
+
enqueue(signal, inventoryHostname, kind, reserve) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
if (signal?.aborted === true) {
|
|
96
|
+
reject(abortedError());
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (this.closed) {
|
|
100
|
+
reject(closedError());
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const cancelled = () => {
|
|
104
|
+
const index = this.waiting.indexOf(waiter);
|
|
105
|
+
if (index >= 0)
|
|
106
|
+
this.waiting.splice(index, 1);
|
|
107
|
+
waiter.reject(abortedError());
|
|
108
|
+
};
|
|
109
|
+
const stopListening = () => signal?.removeEventListener("abort", cancelled);
|
|
110
|
+
const waiter = {
|
|
111
|
+
inventoryHostname,
|
|
112
|
+
kind,
|
|
113
|
+
reject: (error) => {
|
|
114
|
+
stopListening();
|
|
115
|
+
reject(error);
|
|
116
|
+
},
|
|
117
|
+
grant: () => {
|
|
118
|
+
stopListening();
|
|
119
|
+
resolve(reserve());
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
if (this.hasRoom(waiter)) {
|
|
123
|
+
waiter.grant();
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this.waiting.push(waiter);
|
|
127
|
+
signal?.addEventListener("abort", cancelled, { once: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
hasRoom(waiter) {
|
|
132
|
+
const diagnosticsHeld = this.diagnosticSlotsHeldBy(waiter.inventoryHostname);
|
|
133
|
+
const readsHeld = this.readsHeldByDevice.get(waiter.inventoryHostname) ?? 0;
|
|
134
|
+
if (waiter.kind === "read")
|
|
135
|
+
return diagnosticsHeld + readsHeld < this.limits.deviceLimit;
|
|
136
|
+
return this.diagnosticHeadroom(diagnosticsHeld, readsHeld, this.limits.diagnosticServerLimit - this.reservations.size) > 0;
|
|
137
|
+
}
|
|
138
|
+
/** Diagnostic slots a host could take now: the tightest of its diagnostic share, its shared device
|
|
139
|
+
* slots, and the server-wide diagnostic slots still free. Admission and the snapshot both use it.
|
|
140
|
+
*/
|
|
141
|
+
diagnosticHeadroom(diagnosticsHeld, readsHeld, serverAvailable) {
|
|
142
|
+
const diagnosticShareLeft = this.limits.diagnosticDeviceLimit - diagnosticsHeld;
|
|
143
|
+
const deviceSlotsLeft = this.limits.deviceLimit - diagnosticsHeld - readsHeld;
|
|
144
|
+
return Math.max(0, Math.min(diagnosticShareLeft, deviceSlotsLeft, serverAvailable));
|
|
145
|
+
}
|
|
146
|
+
/** Counted from the held reservations, which never exceed the server-wide diagnostic limit. */
|
|
147
|
+
diagnosticSlotsHeldBy(inventoryHostname) {
|
|
148
|
+
let held = 0;
|
|
149
|
+
for (const reservation of this.reservations.keys()) {
|
|
150
|
+
if (reservation.inventory_hostname === inventoryHostname)
|
|
151
|
+
held += 1;
|
|
152
|
+
}
|
|
153
|
+
return held;
|
|
154
|
+
}
|
|
155
|
+
reserveRead(inventoryHostname) {
|
|
156
|
+
this.adjustReadsHeld(inventoryHostname, 1);
|
|
157
|
+
let released = false;
|
|
158
|
+
return () => {
|
|
159
|
+
if (released)
|
|
160
|
+
return;
|
|
161
|
+
released = true;
|
|
162
|
+
this.adjustReadsHeld(inventoryHostname, -1);
|
|
163
|
+
this.grantWaiting();
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** Keeps only hosts that hold reads, so the snapshot lists no idle hosts. */
|
|
167
|
+
adjustReadsHeld(inventoryHostname, change) {
|
|
168
|
+
const readsHeld = (this.readsHeldByDevice.get(inventoryHostname) ?? 0) + change;
|
|
169
|
+
if (readsHeld > 0)
|
|
170
|
+
this.readsHeldByDevice.set(inventoryHostname, readsHeld);
|
|
171
|
+
else
|
|
172
|
+
this.readsHeldByDevice.delete(inventoryHostname);
|
|
173
|
+
}
|
|
174
|
+
reserveDiagnostic(intent) {
|
|
175
|
+
const requestId = randomUUID();
|
|
176
|
+
const state = {
|
|
177
|
+
reservation_id: requestId,
|
|
178
|
+
inventory_hostname: intent.inventoryHostname,
|
|
179
|
+
diagnostic: intent.diagnostic,
|
|
180
|
+
state: "active",
|
|
181
|
+
reserved_at: new Date().toISOString()
|
|
182
|
+
};
|
|
183
|
+
this.reservations.set(state, undefined);
|
|
184
|
+
return {
|
|
185
|
+
requestId,
|
|
186
|
+
complete: () => this.freeDiagnostic(state),
|
|
187
|
+
markUncertain: (reasonCode) => this.markUncertain(state, reasonCode)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
freeDiagnostic(state) {
|
|
191
|
+
if (!this.reservations.has(state))
|
|
192
|
+
return;
|
|
193
|
+
clearTimeout(this.reservations.get(state));
|
|
194
|
+
this.reservations.delete(state);
|
|
195
|
+
this.grantWaiting();
|
|
196
|
+
}
|
|
197
|
+
/** Grant the oldest waiters that can run now; a waiter that cannot run yet keeps its place. */
|
|
198
|
+
grantWaiting() {
|
|
199
|
+
let index = 0;
|
|
200
|
+
while (index < this.waiting.length) {
|
|
201
|
+
const waiter = this.waiting[index];
|
|
202
|
+
if (waiter !== undefined && this.hasRoom(waiter)) {
|
|
203
|
+
this.waiting.splice(index, 1);
|
|
204
|
+
waiter.grant();
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
index += 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
markUncertain(state, reasonCode) {
|
|
212
|
+
if (this.closed || !this.reservations.has(state) || state.state === "uncertain")
|
|
213
|
+
return;
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
state.state = "uncertain";
|
|
216
|
+
state.uncertain_since = new Date(now).toISOString();
|
|
217
|
+
state.releases_at = new Date(now + this.uncertainHoldMs).toISOString();
|
|
218
|
+
state.reason_code = reasonCode;
|
|
219
|
+
// Unref so a held slot never keeps a stopping server alive.
|
|
220
|
+
this.reservations.set(state, setTimeout(() => this.freeDiagnostic(state), this.uncertainHoldMs).unref());
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/** Limits from config: top-level `deviceConcurrency` per device, `diagnostics.*` for the diagnostic share. */
|
|
224
|
+
export function eapiSlotLimits(config) {
|
|
225
|
+
return {
|
|
226
|
+
deviceLimit: config.deviceConcurrency,
|
|
227
|
+
diagnosticDeviceLimit: config.diagnostics.deviceConcurrency,
|
|
228
|
+
diagnosticServerLimit: config.diagnostics.maxConcurrentDiagnostics
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function closedError() {
|
|
232
|
+
return new AppError("diagnostic_server_stopping", "eAPI execution is shutting down");
|
|
233
|
+
}
|
|
234
|
+
function abortedError() {
|
|
235
|
+
return new AppError("operation_aborted", "Request was cancelled while waiting for device capacity");
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=eapiSlots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eapiSlots.js","sourceRoot":"","sources":["../../src/operations/eapiSlots.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ export async function executeReadOperation(model, config, options) {
|
|
|
4
4
|
const resolvedTarget = resolveInventoryTarget(model, { target: options.target });
|
|
5
5
|
enforceReadTargetLimit(config, resolvedTarget.resolvedHosts.length);
|
|
6
6
|
options.validateTarget?.(resolvedTarget);
|
|
7
|
-
const results = await runWithOperationTimeout(config, options.operationName, (signal) => mapWithConcurrency(resolvedTarget.resolvedHosts, config.
|
|
7
|
+
const results = await runWithOperationTimeout(config, options.operationName, (signal) => mapWithConcurrency(resolvedTarget.resolvedHosts, config.readFanOut, async (host) => {
|
|
8
8
|
try {
|
|
9
9
|
return await options.run(host, signal);
|
|
10
10
|
}
|