@norskvideo/norsk-auto-manager 0.1.0
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/README.md +344 -0
- package/lib/src/automanager.d.ts +1070 -0
- package/lib/src/automanager.js +2746 -0
- package/lib/src/clock.d.ts +14 -0
- package/lib/src/clock.js +21 -0
- package/lib/src/conversions.d.ts +59 -0
- package/lib/src/conversions.js +416 -0
- package/lib/src/index.d.ts +9 -0
- package/lib/src/index.js +37 -0
- package/lib/src/inventory.d.ts +34 -0
- package/lib/src/inventory.js +64 -0
- package/lib/src/knownCapabilities.d.ts +18 -0
- package/lib/src/knownCapabilities.js +22 -0
- package/lib/src/placement.d.ts +268 -0
- package/lib/src/placement.js +468 -0
- package/lib/src/settingsValidation.d.ts +34 -0
- package/lib/src/settingsValidation.js +232 -0
- package/lib/src/shared/utils.d.ts +17 -0
- package/lib/src/shared/utils.js +220 -0
- package/lib/src/types.d.ts +295 -0
- package/lib/src/types.js +20 -0
- package/lib/src/validation.d.ts +68 -0
- package/lib/src/validation.js +198 -0
- package/package.json +66 -0
- package/src/automanager.ts +3570 -0
- package/src/clock.ts +29 -0
- package/src/conversions.ts +458 -0
- package/src/index.ts +27 -0
- package/src/inventory.ts +64 -0
- package/src/knownCapabilities.ts +23 -0
- package/src/placement.ts +804 -0
- package/src/settingsValidation.ts +386 -0
- package/src/types.ts +346 -0
- package/src/validation.ts +297 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Standard capability names shipped with Norsk. Use these constants
|
|
3
|
+
// rather than literal strings — typos in user code surface at compile
|
|
4
|
+
// time rather than as silent placement misses.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KnownCapabilities = void 0;
|
|
7
|
+
/** @public */
|
|
8
|
+
exports.KnownCapabilities = {
|
|
9
|
+
/** Whole NVIDIA GPU, exclusive. For fractional, use requiredGpuCapacity. */
|
|
10
|
+
NvidiaGpu: "norsk.io/nvidia-gpu",
|
|
11
|
+
/** Intel Quick Sync Video — fixed-function media accelerator. */
|
|
12
|
+
IntelQsv: "norsk.io/intel-qsv",
|
|
13
|
+
/** Netint Quadra T1U accelerator. */
|
|
14
|
+
Quadra: "norsk.io/quadra",
|
|
15
|
+
/** Blackmagic DeckLink capture/playout card. */
|
|
16
|
+
Decklink: "norsk.io/decklink",
|
|
17
|
+
/** SMPTE ST 2110 capable NIC. */
|
|
18
|
+
St2110Nic: "norsk.io/st2110-nic",
|
|
19
|
+
/** Media eXchange Layer slot, provided by an MXL bridge NodeService. */
|
|
20
|
+
Mxl: "norsk.io/mxl",
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=knownCapabilities.js.map
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Bundle, BundleId, BundleJobSpec, Capability, NodeId, NodeInventory } from "./types";
|
|
2
|
+
/** @public */
|
|
3
|
+
export interface NodeView {
|
|
4
|
+
nodeId: NodeId;
|
|
5
|
+
poolName: string;
|
|
6
|
+
/**
|
|
7
|
+
* Which tier within the pool this node belongs to. Set from
|
|
8
|
+
* `tags["tier"]` at provision time for elastic tiers. Pre-registered
|
|
9
|
+
* cluster nodes carry no tier tag (undefined) and are treated as
|
|
10
|
+
* belonging to their pool's `fixed` tier — see `nodeMatchesTier`.
|
|
11
|
+
*/
|
|
12
|
+
tierName?: string;
|
|
13
|
+
inventory: NodeInventory;
|
|
14
|
+
/** Jobs currently running on this node (or pending placement onto it). */
|
|
15
|
+
runningJobs: RunningJob[];
|
|
16
|
+
/** Topology key for the resilience sameAz rule. Cluster pools can leave undefined. */
|
|
17
|
+
az?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Cloud / failure-domain key for the resilience sameCloud rule — the
|
|
20
|
+
* node's provider (e.g. "aws" / "oci" / "cluster"). Undefined when
|
|
21
|
+
* unknown; sameCloud then can't be evaluated against this node.
|
|
22
|
+
*/
|
|
23
|
+
cloud?: string;
|
|
24
|
+
/** Hot spare flag (Phase C). Currently unused by hard-constraint filters. */
|
|
25
|
+
isHotSpare: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** @public */
|
|
28
|
+
export interface RunningJob {
|
|
29
|
+
bundleId: BundleId;
|
|
30
|
+
replicaIndex: number;
|
|
31
|
+
jobName: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A pool is an ordered list of capacity tiers. Placement walks the tiers
|
|
35
|
+
* in order (cheapest first) and takes the first that can satisfy the job
|
|
36
|
+
* — the tier order *is* the cost cascade (no cost weight in v1). The
|
|
37
|
+
* homogeneous single-cloud pool of before is just a one-tier pool.
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
export interface PlacementPool {
|
|
42
|
+
name: string;
|
|
43
|
+
/** Ordered cheapest → most expensive. Placement tries them in order. */
|
|
44
|
+
tiers: PlacementTier[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One homogeneous capacity segment within a pool: a single cloud/cluster
|
|
48
|
+
* kind + region with its own scale-out behaviour and candidate shapes.
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export interface PlacementTier {
|
|
53
|
+
/** Unique within the pool. Tagged onto provisioned nodes as `tags["tier"]`. */
|
|
54
|
+
name: string;
|
|
55
|
+
/**
|
|
56
|
+
* Cloud / local-servers kind. Used by AutoManager to decide which gRPC
|
|
57
|
+
* call to make when provisioning (createAwsNode vs createOciNode vs
|
|
58
|
+
* local-servers-tier placement, which uses startJob against an existing
|
|
59
|
+
* registered server).
|
|
60
|
+
*/
|
|
61
|
+
kind: "aws" | "oci" | "local-servers";
|
|
62
|
+
/**
|
|
63
|
+
* For aws/oci elastic tiers: which region/availability-domain to
|
|
64
|
+
* provision into. Cluster tiers leave undefined.
|
|
65
|
+
*/
|
|
66
|
+
region?: string;
|
|
67
|
+
packingStrategy: "binpack" | "spread";
|
|
68
|
+
scaleOut: "elastic" | "fixed";
|
|
69
|
+
/**
|
|
70
|
+
* For elastic tiers: shapes that can be provisioned on demand. For
|
|
71
|
+
* fixed tiers: typically empty (the tier consists of registered
|
|
72
|
+
* cluster nodes already in `inventory`).
|
|
73
|
+
*/
|
|
74
|
+
candidateInstanceTypes: InstanceTypeOption[];
|
|
75
|
+
/**
|
|
76
|
+
* How long ahead of a scheduled job's `startDateTime` AutoManager
|
|
77
|
+
* should kick off placement, so the worker is ready to run by the
|
|
78
|
+
* requested time. Cluster tiers place against an existing running
|
|
79
|
+
* node (~instant) so the default is 0. Cloud tiers have to boot a
|
|
80
|
+
* fresh instance — defaults to 5 minutes; operators set per-tier
|
|
81
|
+
* when their boot time differs. AutoManager picks the maximum across
|
|
82
|
+
* the bundle pool's tiers (worst case — guarantees readiness
|
|
83
|
+
* regardless of which tier the placement engine settles on).
|
|
84
|
+
*/
|
|
85
|
+
placementLeadMs?: number;
|
|
86
|
+
/**
|
|
87
|
+
* How nodes in this tier are purchased. `"spot"` requests interruptible
|
|
88
|
+
* spot-market capacity; `"reserved"` is an on-demand launch the operator
|
|
89
|
+
* knows is covered by a standing reservation (same AWS call as on-demand,
|
|
90
|
+
* but capped — see `maxNodes`). Defaults to `"on-demand"`.
|
|
91
|
+
*/
|
|
92
|
+
launchMode?: LaunchMode;
|
|
93
|
+
/**
|
|
94
|
+
* Failure-domain reliability of this tier's capacity. Defaults to
|
|
95
|
+
* `"interruptible"` for spot, `"durable"` otherwise. Read by the
|
|
96
|
+
* resilience engine — an interruptible-tier primary with no durable
|
|
97
|
+
* backup is flagged degraded.
|
|
98
|
+
*/
|
|
99
|
+
reliability?: Reliability;
|
|
100
|
+
/**
|
|
101
|
+
* Cap on the number of nodes this tier may hold (tier-wide, across
|
|
102
|
+
* bundles). The cost cascade spills to the next tier once the cap is
|
|
103
|
+
* reached. Used mainly for reserved tiers — exceeding the reservation
|
|
104
|
+
* count just bills as on-demand, defeating the point. Unset ⇒ no cap.
|
|
105
|
+
*/
|
|
106
|
+
maxNodes?: number;
|
|
107
|
+
}
|
|
108
|
+
/** @public */
|
|
109
|
+
export type LaunchMode = "on-demand" | "spot" | "reserved";
|
|
110
|
+
/** @public */
|
|
111
|
+
export type Reliability = "durable" | "interruptible";
|
|
112
|
+
/** The tier's launch mode, defaulting to on-demand. @public */
|
|
113
|
+
export declare function tierLaunchMode(tier: PlacementTier): LaunchMode;
|
|
114
|
+
/** The tier's reliability — explicit, else interruptible for spot. @public */
|
|
115
|
+
export declare function tierReliability(tier: PlacementTier): Reliability;
|
|
116
|
+
/** Default placement lead time by tier kind. Cluster tiers place
|
|
117
|
+
* against existing running nodes so we don't need lead. Cloud tiers
|
|
118
|
+
* default to 5 minutes — typical EC2/OCI instance boot + image pull
|
|
119
|
+
* budget; operators can override per-tier via PlacementTier. */
|
|
120
|
+
export declare function defaultPlacementLeadMs(kind: PlacementTier["kind"]): number;
|
|
121
|
+
/**
|
|
122
|
+
* Whether a node belongs to (pool, tier). Elastic nodes carry an explicit
|
|
123
|
+
* `tierName` (set from `tags["tier"]` at provision). Pre-registered cluster
|
|
124
|
+
* nodes carry no tier tag and are taken to belong to the pool's `fixed`
|
|
125
|
+
* tier — so a pool must have at most one fixed tier (enforced in
|
|
126
|
+
* settingsValidation) for this to be unambiguous.
|
|
127
|
+
*
|
|
128
|
+
* @public
|
|
129
|
+
*/
|
|
130
|
+
export declare function nodeMatchesTier(n: NodeView, pool: PlacementPool, tier: PlacementTier): boolean;
|
|
131
|
+
/** @public */
|
|
132
|
+
export interface InstanceTypeOption {
|
|
133
|
+
instanceType: string;
|
|
134
|
+
totalCapacity: number;
|
|
135
|
+
totalCores: number;
|
|
136
|
+
capabilities: Capability[];
|
|
137
|
+
/** GPUs we could provision a node with — for feasibility check. */
|
|
138
|
+
gpus?: {
|
|
139
|
+
totalCapacity: number;
|
|
140
|
+
model?: string;
|
|
141
|
+
}[];
|
|
142
|
+
}
|
|
143
|
+
/** @public */
|
|
144
|
+
export interface RecoveryContext {
|
|
145
|
+
/** NodeIds the placement engine must skip (e.g., recently failed). */
|
|
146
|
+
excludeNodes: Set<NodeId>;
|
|
147
|
+
/** Reserved for Phase C — biases scoring toward hot spares. */
|
|
148
|
+
preferHotSpares: boolean;
|
|
149
|
+
}
|
|
150
|
+
/** @public */
|
|
151
|
+
export interface PlacementInput {
|
|
152
|
+
bundle: Bundle;
|
|
153
|
+
job: BundleJobSpec;
|
|
154
|
+
replicaIndex: number;
|
|
155
|
+
inventory: NodeView[];
|
|
156
|
+
pools: Map<string, PlacementPool>;
|
|
157
|
+
recoveryContext?: RecoveryContext;
|
|
158
|
+
}
|
|
159
|
+
/** @public */
|
|
160
|
+
/** Which anti-affinity flag(s) a backup placement couldn't honour. */
|
|
161
|
+
export type ResilienceFlag = "sameNode" | "sameAz" | "sameCloud";
|
|
162
|
+
/**
|
|
163
|
+
* Set on a backup placement that landed inside a failure domain its
|
|
164
|
+
* resilience policy asked to avoid (best-effort: no compliant node had
|
|
165
|
+
* capacity). The engine places it anyway and surfaces this so the
|
|
166
|
+
* operator knows the bundle's DR posture is currently reduced.
|
|
167
|
+
*
|
|
168
|
+
* @public
|
|
169
|
+
*/
|
|
170
|
+
export interface ResilienceDegradation {
|
|
171
|
+
violated: ResilienceFlag[];
|
|
172
|
+
/**
|
|
173
|
+
* Set when the primary landed on interruptible (spot) capacity with no
|
|
174
|
+
* durable backup — a single interruption takes the workload down. Surfaced
|
|
175
|
+
* so the operator can add a durable backup.
|
|
176
|
+
*/
|
|
177
|
+
interruptiblePrimary?: boolean;
|
|
178
|
+
}
|
|
179
|
+
/** @public */
|
|
180
|
+
export type PlacementResult = {
|
|
181
|
+
kind: "place";
|
|
182
|
+
nodeId: NodeId;
|
|
183
|
+
gpuIndex?: number;
|
|
184
|
+
pool: string;
|
|
185
|
+
tier: string;
|
|
186
|
+
degraded?: ResilienceDegradation;
|
|
187
|
+
} | {
|
|
188
|
+
kind: "provision";
|
|
189
|
+
pool: string;
|
|
190
|
+
tier: string;
|
|
191
|
+
instanceType: string;
|
|
192
|
+
launchMode: LaunchMode;
|
|
193
|
+
degraded?: ResilienceDegradation;
|
|
194
|
+
} | {
|
|
195
|
+
kind: "failure";
|
|
196
|
+
reason: PlacementFailureReason;
|
|
197
|
+
};
|
|
198
|
+
/** @public */
|
|
199
|
+
export interface PlacementFailureReason {
|
|
200
|
+
code: "noCapacityInAnyPool" | "noConfiguredPools";
|
|
201
|
+
triedPools: string[];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Structured trace of a placement decision — one entry per pool
|
|
205
|
+
* attempted, with per-node filter outcomes and resulting scores. Used
|
|
206
|
+
* for "why did my job land here?" debugging.
|
|
207
|
+
*
|
|
208
|
+
* @public
|
|
209
|
+
*/
|
|
210
|
+
export interface PlacementTrace {
|
|
211
|
+
attempts: PoolAttempt[];
|
|
212
|
+
/** Final decision kind, mirroring PlacementResult.kind. */
|
|
213
|
+
decision: "place" | "provision" | "failure";
|
|
214
|
+
}
|
|
215
|
+
/** @public */
|
|
216
|
+
export interface PoolAttempt {
|
|
217
|
+
pool: string;
|
|
218
|
+
/** Which tier of the pool this attempt covers. */
|
|
219
|
+
tier: string;
|
|
220
|
+
/** Total nodes considered in this pool before any filtering. */
|
|
221
|
+
initialCandidates: number;
|
|
222
|
+
/** Why each non-matching node was filtered out. Indexed by nodeId. */
|
|
223
|
+
filtered: {
|
|
224
|
+
nodeId: NodeId;
|
|
225
|
+
reason: FilterReason;
|
|
226
|
+
}[];
|
|
227
|
+
/** Nodes that survived all filters, with their assigned scores. */
|
|
228
|
+
scored: {
|
|
229
|
+
nodeId: NodeId;
|
|
230
|
+
score: number;
|
|
231
|
+
}[];
|
|
232
|
+
/** What this pool attempt produced. */
|
|
233
|
+
outcome: {
|
|
234
|
+
kind: "placed";
|
|
235
|
+
nodeId: NodeId;
|
|
236
|
+
gpuIndex?: number;
|
|
237
|
+
} | {
|
|
238
|
+
kind: "provisioned";
|
|
239
|
+
instanceType: string;
|
|
240
|
+
} | {
|
|
241
|
+
kind: "exhausted";
|
|
242
|
+
} | {
|
|
243
|
+
kind: "no-instance-type";
|
|
244
|
+
} | {
|
|
245
|
+
kind: "missing-pool";
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/** @public */
|
|
249
|
+
export type FilterReason = "wrong-pool" | "wrong-tier" | "excluded-recovery" | "unreachable" | "cordoned" | "missing-capability" | "insufficient-capacity" | "insufficient-cores" | "no-fitting-gpu" | "fails-intra-replica" | "fails-intra-az";
|
|
250
|
+
/**
|
|
251
|
+
* Core placement function. Pure — no side effects, no I/O.
|
|
252
|
+
*
|
|
253
|
+
* @public
|
|
254
|
+
*/
|
|
255
|
+
export declare function place(input: PlacementInput): PlacementResult;
|
|
256
|
+
/**
|
|
257
|
+
* Placement with a structured trace of every pool attempted and every
|
|
258
|
+
* node's filter outcome. AutoManager logs the trace via debuglog when
|
|
259
|
+
* placing; tests use it to assert on the precise reason a candidate
|
|
260
|
+
* was rejected.
|
|
261
|
+
*
|
|
262
|
+
* @public
|
|
263
|
+
*/
|
|
264
|
+
export declare function placeWithTrace(input: PlacementInput): {
|
|
265
|
+
result: PlacementResult;
|
|
266
|
+
trace: PlacementTrace;
|
|
267
|
+
};
|
|
268
|
+
//# sourceMappingURL=placement.d.ts.map
|