Coverage for scripts / example_job_validation / specs.py: 100.00%

57 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-10-28 16:19 +0000

1"""The per-example validation spec registry. 

2 

3One :class:`ExampleSpec` per file under ``examples/`` (pinned by symmetry 

4tests against both the directory and the ``gco_mcp`` ``EXAMPLE_METADATA`` 

5catalog). Each spec answers, for its example: 

6 

7* **how** it is submitted — the exact path the docs tell users to use 

8 (``submit-direct`` / ``submit-sqs`` / ``dag run`` / ``kubectl apply`` / 

9 companion artifact with no live path); 

10* **what** infrastructure it needs beyond the stock deploy (optional helm 

11 charts via ``helm_enabled_overrides``, optional features via 

12 ``feature_enabled_overrides``, EKS Capabilities via 

13 ``eks_capabilities_overrides``, GPU/Neuron capacity, special setup drivers); 

14* **when** it counts as passed (workload-specific success criteria); and 

15* **which** deliberate mutations the harness applies before submission 

16 (e.g. replacing a gated HuggingFace model with an ungated one) — every 

17 mutation is disclosed in the report. 

18 

19Keep this table boring and declarative: ``drivers.py`` interprets it and 

20``actions.py`` orchestrates it. 

21""" 

22 

23from __future__ import annotations 

24 

25from dataclasses import dataclass, field 

26 

27#: Documented submission paths the drivers know how to execute. 

28SUBMIT_DIRECT = "cli-submit-direct" # gco jobs submit-direct <file> -r <region> 

29SUBMIT_SQS = "cli-submit-sqs" # gco jobs submit-sqs <file> --region <region> 

30SUBMIT_API = "cli-submit-api" # gco jobs submit <file> --region <region> 

31DAG_RUN = "cli-dag-run" # gco dag run <file> -r <region> 

32KUBECTL_APPLY = "kubectl-apply" # kubectl apply -f <file> (over the SSM tunnel) 

33COMPANION = "companion-artifact" # not independently runnable (data/DAG-step file) 

34 

35SUBMISSION_PATHS = (SUBMIT_DIRECT, SUBMIT_SQS, SUBMIT_API, DAG_RUN, KUBECTL_APPLY, COMPANION) 

36 

37#: Success-criteria kinds the drivers implement. 

38JOB_COMPLETES = "job-completes" # batch/v1 Job reaches Complete 

39DEPLOYMENT_AVAILABLE = "deployment-available" # Deployment Available + Service endpoints 

40RAYCLUSTER_READY = "raycluster-ready" # RayCluster ready: head + minReplicas workers 

41VCJOB_COMPLETES = "vcjob-completes" # batch.volcano.sh Job phase Completed 

42SCALEDJOB_SCALES = "scaledjob-scales" # KEDA ScaledJob spawns >=1 Job from queue depth 

43TRAINJOB_COMPLETES = "trainjob-completes" # trainer.kubeflow.org TrainJob condition Complete 

44DAG_SUCCEEDS = "dag-succeeds" # gco dag run exits 0 with all steps completed 

45#: argoproj.io Application Synced + Healthy at the applied revision, and every 

46#: Job it syncs from Git Complete. 

47ARGOCD_APP_HEALTHY = "argocd-app-healthy" 

48#: A kro / Crossplane instance's composed Job (named after the instance, in the 

49#: instance's namespace) reaches Complete. 

50COMPOSED_JOB_COMPLETES = "composed-job-completes" 

51#: Every ACK resource reports ACK.ResourceSynced=True (the AWS resource exists). 

52ACK_RESOURCE_SYNCED = "ack-resource-synced" 

53NONE = "none" # companion artifacts: static checks only 

54 

55#: EKS Capability types an example may require (cdk.json ``eks_capabilities``). 

56CAPABILITY_TYPES = ("ack", "kro") 

57 

58 

59@dataclass(frozen=True) 

60class ExampleSpec: 

61 """Declarative validation contract for one example file.""" 

62 

63 #: File stem under ``examples/`` (e.g. ``simple-job`` for simple-job.yaml). 

64 name: str 

65 #: One of :data:`SUBMISSION_PATHS` — must match the documented usage. 

66 submission: str 

67 #: One of the success-criteria kinds above. 

68 criteria: str 

69 #: Optional helm charts that must be force-enabled for this example 

70 #: (threaded to CDK as ``helm_enabled_overrides``). 

71 helm_overrides: tuple[str, ...] = () 

72 #: Optional infrastructure features that must be force-enabled 

73 #: (threaded to CDK as ``feature_enabled_overrides``). 

74 feature_overrides: tuple[str, ...] = () 

75 #: EKS Capabilities (one of :data:`CAPABILITY_TYPES`) that must be enabled 

76 #: (threaded to CDK as ``eks_capabilities_overrides``). 

77 capability_overrides: tuple[str, ...] = () 

78 #: Managed IAM policies the ACK capability role must hold for this 

79 #: example (merged into ``eks_capabilities.ack.iam_policy_arns``); only 

80 #: valid together with ``capability_overrides=("ack",)``. 

81 ack_iam_policy_arns: tuple[str, ...] = () 

82 #: Companion example (file stem, a :data:`COMPANION` spec) the setup driver 

83 #: applies before submission and deletes after cleanup — the API 

84 #: definition an instance example needs (kro RGD, Crossplane XRD). 

85 companion: str = "" 

86 #: Accelerator requirement: "" (none), "nvidia", "neuron", or "efa". 

87 accelerator: str = "" 

88 #: Named setup/teardown driver hook, implemented in ``drivers.py`` and 

89 #: listed in its ``KNOWN_SETUP_DRIVERS`` registry, e.g. "keda-demo-queue". 

90 setup_driver: str = "" 

91 #: Deliberate, report-disclosed manifest mutations applied before 

92 #: submission, as (json-path-ish description, replacement) pairs. 

93 mutations: dict[str, str] = field(default_factory=dict) 

94 #: Per-example completion timeout. GPU examples get longer defaults to 

95 #: absorb node provisioning. 

96 timeout_seconds: int = 900 

97 #: Skip unless the account has usable capacity for this instance family 

98 #: (checked via service quotas before submission); empty = never skipped. 

99 capacity_quota_code: str = "" 

100 #: Human rationale for anything unusual above. 

101 notes: str = "" 

102 

103 

104#: Sentinel mutation value: remove the targeted entry instead of replacing it. 

105REMOVE_VALUE = "__REMOVE__" 

106 

107EXAMPLE_SPECS: dict[str, ExampleSpec] = { 

108 spec.name: spec 

109 for spec in ( 

110 # --- plain batch jobs over documented CLI paths ------------------- 

111 ExampleSpec("simple-job", SUBMIT_SQS, JOB_COMPLETES), 

112 ExampleSpec( 

113 "sqs-job-submission", 

114 SUBMIT_SQS, 

115 JOB_COMPLETES, 

116 accelerator="nvidia", 

117 timeout_seconds=1800, 

118 notes="two Jobs in one file; the GPU one waits for node provisioning", 

119 ), 

120 ExampleSpec("model-download-job", KUBECTL_APPLY, JOB_COMPLETES, timeout_seconds=1200), 

121 ExampleSpec("efs-output-job", SUBMIT_DIRECT, JOB_COMPLETES), 

122 ExampleSpec( 

123 "fsx-lustre-job", 

124 SUBMIT_DIRECT, 

125 JOB_COMPLETES, 

126 feature_overrides=("fsx_lustre",), 

127 ), 

128 ExampleSpec( 

129 "aurora-pgvector-job", 

130 SUBMIT_DIRECT, 

131 JOB_COMPLETES, 

132 feature_overrides=("aurora_pgvector",), 

133 ), 

134 ExampleSpec( 

135 "vector-store-search-job", 

136 SUBMIT_DIRECT, 

137 JOB_COMPLETES, 

138 feature_overrides=("vector_store",), 

139 setup_driver="vector-demo-corpus", 

140 notes=( 

141 "read-only search; the setup driver ingests the bundled demo " 

142 "corpus (gco vector ingest --demo --wait) so the >=1-hit " 

143 "self-assert has something to find" 

144 ), 

145 ), 

146 ExampleSpec( 

147 "mlflow-tracking-job", 

148 SUBMIT_DIRECT, 

149 JOB_COMPLETES, 

150 setup_driver="mlflow-ready", 

151 notes=( 

152 "tracking server ships with the default-on observability " 

153 "bundle (no override key needed); the setup driver waits for " 

154 "the mlflow Deployment and the gco-jobs CA bundle before the " 

155 "client job submits" 

156 ), 

157 ), 

158 ExampleSpec( 

159 "valkey-cache-job", 

160 SUBMIT_DIRECT, 

161 JOB_COMPLETES, 

162 feature_overrides=("valkey",), 

163 ), 

164 ExampleSpec("cluster-shared-bucket-upload-job", SUBMIT_DIRECT, JOB_COMPLETES), 

165 ExampleSpec( 

166 "regional-shared-bucket-upload-job", 

167 SUBMIT_DIRECT, 

168 JOB_COMPLETES, 

169 notes=( 

170 "no overrides needed: the regional bucket, its RW grant, and " 

171 "the gco-regional-shared-bucket ConfigMap are all unconditional" 

172 ), 

173 ), 

174 ExampleSpec( 

175 "analytics-s3-upload-job", 

176 SUBMIT_DIRECT, 

177 JOB_COMPLETES, 

178 notes=( 

179 "validates the cluster-side half only; the Studio-notebook " 

180 "reader documented as a prerequisite is out of scope" 

181 ), 

182 ), 

183 ExampleSpec( 

184 "analytics-database-export-job", 

185 SUBMIT_DIRECT, 

186 JOB_COMPLETES, 

187 feature_overrides=("aurora_pgvector",), 

188 notes="with Aurora forced on, the full export path runs (not the no-op branch)", 

189 ), 

190 # --- GPU / accelerator jobs --------------------------------------- 

191 ExampleSpec( 

192 "gpu-job", SUBMIT_SQS, JOB_COMPLETES, accelerator="nvidia", timeout_seconds=1800 

193 ), 

194 ExampleSpec( 

195 "multi-gpu-training", 

196 KUBECTL_APPLY, 

197 JOB_COMPLETES, 

198 accelerator="nvidia", 

199 timeout_seconds=2400, 

200 notes="indexed Job + headless Service; completion requires all indexes", 

201 ), 

202 ExampleSpec( 

203 "efa-distributed-training", 

204 SUBMIT_DIRECT, 

205 JOB_COMPLETES, 

206 accelerator="efa", 

207 capacity_quota_code="L-417A185B", 

208 timeout_seconds=2400, 

209 notes=( 

210 "requires P-family EFA-capable capacity; skipped with evidence " 

211 "when the account's Running On-Demand P quota is zero" 

212 ), 

213 ), 

214 ExampleSpec( 

215 "inferentia-job", 

216 SUBMIT_API, 

217 JOB_COMPLETES, 

218 accelerator="neuron", 

219 capacity_quota_code="L-1945791B", 

220 timeout_seconds=1800, 

221 ), 

222 ExampleSpec( 

223 "trainium-job", 

224 SUBMIT_API, 

225 JOB_COMPLETES, 

226 accelerator="neuron", 

227 capacity_quota_code="L-2C3B7624", 

228 timeout_seconds=1800, 

229 ), 

230 # --- inference Deployment+Service pairs --------------------------- 

231 ExampleSpec( 

232 "inference-vllm", 

233 SUBMIT_DIRECT, 

234 DEPLOYMENT_AVAILABLE, 

235 accelerator="nvidia", 

236 timeout_seconds=2400, 

237 notes="default model (Qwen/Qwen2.5-1.5B-Instruct) is ungated; runs verbatim", 

238 ), 

239 ExampleSpec( 

240 "inference-sglang", 

241 SUBMIT_DIRECT, 

242 DEPLOYMENT_AVAILABLE, 

243 accelerator="nvidia", 

244 timeout_seconds=2400, 

245 notes="default model (Qwen/Qwen2.5-1.5B-Instruct) is ungated; runs verbatim", 

246 ), 

247 ExampleSpec( 

248 "inference-triton", 

249 SUBMIT_DIRECT, 

250 DEPLOYMENT_AVAILABLE, 

251 accelerator="nvidia", 

252 timeout_seconds=2400, 

253 notes="empty --model-repository is a valid, live initial state", 

254 ), 

255 # --- scheduler CRD examples (documented kubectl paths) ------------ 

256 ExampleSpec( 

257 "kueue-job", 

258 KUBECTL_APPLY, 

259 JOB_COMPLETES, 

260 accelerator="nvidia", 

261 timeout_seconds=2400, 

262 notes=( 

263 "applies its own ResourceFlavors/ClusterQueue/LocalQueue plus a " 

264 "CPU and a GPU Job; both Jobs must complete and the queue " 

265 "objects are deleted afterwards" 

266 ), 

267 ), 

268 ExampleSpec( 

269 "volcano-gang-job", 

270 KUBECTL_APPLY, 

271 VCJOB_COMPLETES, 

272 timeout_seconds=1200, 

273 ), 

274 ExampleSpec( 

275 "yunikorn-job", 

276 KUBECTL_APPLY, 

277 JOB_COMPLETES, 

278 helm_overrides=("yunikorn",), 

279 accelerator="nvidia", 

280 timeout_seconds=2400, 

281 notes="three Jobs incl. one GPU and one gang-annotated; all must complete", 

282 ), 

283 ExampleSpec( 

284 "slurm-cluster-job", 

285 KUBECTL_APPLY, 

286 JOB_COMPLETES, 

287 helm_overrides=("slurm",), 

288 timeout_seconds=1200, 

289 ), 

290 ExampleSpec( 

291 "ray-cluster", 

292 KUBECTL_APPLY, 

293 RAYCLUSTER_READY, 

294 timeout_seconds=1200, 

295 ), 

296 ExampleSpec( 

297 "kubeflow-trainjob", 

298 SUBMIT_SQS, 

299 TRAINJOB_COMPLETES, 

300 setup_driver="trainer-runtime-ready", 

301 timeout_seconds=1800, 

302 notes=( 

303 "CPU-sized 2-node torchrun all-reduce; the trainer chart is " 

304 "on by default (no override key), the setup driver waits for " 

305 "the TrainJob CRD and the torch-distributed runtime, and the " 

306 "timeout absorbs the multi-GB pytorch image pull on both nodes" 

307 ), 

308 ), 

309 ExampleSpec( 

310 "keda-scaled-job", 

311 KUBECTL_APPLY, 

312 SCALEDJOB_SCALES, 

313 setup_driver="keda-demo-queue", 

314 timeout_seconds=1200, 

315 notes=( 

316 "creates a disposable demo queue, grants the KEDA operator " 

317 "read-only metric access via a queue policy (the documented " 

318 "prerequisite), substitutes the placeholder queueURL, and " 

319 "deletes the queue afterwards" 

320 ), 

321 ), 

322 # --- self-managed platform add-ons (helm.argocd, helm.crossplane) -- 

323 ExampleSpec( 

324 "argocd-gitops-job", 

325 KUBECTL_APPLY, 

326 ARGOCD_APP_HEALTHY, 

327 helm_overrides=("argocd",), 

328 setup_driver="argocd-revision-pin", 

329 notes=( 

330 "an Application syncing examples/gitops/hello-job from this " 

331 "repository into gco-jobs through the fenced gco-tenants project; " 

332 "the setup driver waits for Argo CD and pins targetRevision to the " 

333 "commit under validation, so the run syncs exactly the code it tests" 

334 ), 

335 ), 

336 ExampleSpec( 

337 "crossplane-batch-api", 

338 COMPANION, 

339 NONE, 

340 notes="XRD + Composition applied by crossplane-batch-job's setup driver", 

341 ), 

342 ExampleSpec( 

343 "crossplane-batch-job", 

344 KUBECTL_APPLY, 

345 COMPOSED_JOB_COMPLETES, 

346 helm_overrides=("crossplane",), 

347 setup_driver="crossplane-api", 

348 companion="crossplane-batch-api", 

349 notes=( 

350 "a namespaced BatchJob XR; the setup driver waits for the " 

351 "go-templating function, applies the companion XRD + Composition " 

352 "and waits for the API to be served; Crossplane composes the Job" 

353 ), 

354 ), 

355 # --- AWS-managed EKS Capabilities (eks_capabilities) --------------- 

356 ExampleSpec( 

357 "kro-batch-api", 

358 COMPANION, 

359 NONE, 

360 notes="ResourceGraphDefinition applied by kro-batch-job's setup driver", 

361 ), 

362 ExampleSpec( 

363 "kro-batch-job", 

364 KUBECTL_APPLY, 

365 COMPOSED_JOB_COMPLETES, 

366 capability_overrides=("kro",), 

367 setup_driver="kro-api", 

368 companion="kro-batch-api", 

369 notes=( 

370 "a BatchJob instance; the setup driver applies the companion " 

371 "ResourceGraphDefinition and waits for it to be Active and its API " 

372 "served; the kro capability composes the Job" 

373 ), 

374 ), 

375 ExampleSpec( 

376 "ack-sqs-queue", 

377 KUBECTL_APPLY, 

378 ACK_RESOURCE_SYNCED, 

379 capability_overrides=("ack",), 

380 ack_iam_policy_arns=("arn:aws:iam::aws:policy/AmazonSQSFullAccess",), 

381 setup_driver="ack-sqs", 

382 notes=( 

383 "the ACK capability role gets AmazonSQSFullAccess for this run " 

384 "(the documented simple setup); the driver confirms the queue in " 

385 "SQS after sync and its deletion after cleanup" 

386 ), 

387 ), 

388 # --- DAG pipeline -------------------------------------------------- 

389 ExampleSpec("pipeline-dag", DAG_RUN, DAG_SUCCEEDS, timeout_seconds=1800), 

390 ExampleSpec( 

391 "dag-step-preprocess", 

392 COMPANION, 

393 NONE, 

394 notes="step manifest executed via pipeline-dag", 

395 ), 

396 ExampleSpec( 

397 "dag-step-train", COMPANION, NONE, notes="step manifest executed via pipeline-dag" 

398 ), 

399 ) 

400} 

401 

402 

403def required_helm_overrides(names: list[str]) -> tuple[str, ...]: 

404 """Union of helm overrides needed by the selected examples (sorted).""" 

405 needed: set[str] = set() 

406 for name in names: 

407 needed.update(EXAMPLE_SPECS[name].helm_overrides) 

408 return tuple(sorted(needed)) 

409 

410 

411def required_feature_overrides(names: list[str]) -> tuple[str, ...]: 

412 """Union of feature overrides needed by the selected examples (sorted).""" 

413 needed: set[str] = set() 

414 for name in names: 

415 needed.update(EXAMPLE_SPECS[name].feature_overrides) 

416 return tuple(sorted(needed)) 

417 

418 

419def required_capability_overrides(names: list[str]) -> tuple[str, ...]: 

420 """Union of EKS Capability types needed by the selected examples (sorted).""" 

421 needed: set[str] = set() 

422 for name in names: 

423 needed.update(EXAMPLE_SPECS[name].capability_overrides) 

424 return tuple(sorted(needed)) 

425 

426 

427def required_capability_settings(names: list[str]) -> dict[str, dict[str, list[str]]]: 

428 """Per-type capability settings the selected examples need. 

429 

430 Today only ACK carries settings: the union of the managed policies the 

431 selected examples name, attached to the ACK capability role. 

432 """ 

433 policies = sorted({arn for name in names for arn in EXAMPLE_SPECS[name].ack_iam_policy_arns}) 

434 return {"ack": {"iam_policy_arns": policies}} if policies else {}