Skip to content

Commit c711ddd

Browse files
committed
Feat #99: add helm-unittest tests + README External Batch section
Tests (openstudio-server/tests/job-external-batch_test.yaml, 14 cases): - default-disabled renders nothing - enabled renders Job with correct spec (completions, parallelism, completionMode: Indexed, backoffLimit, activeDeadlineSeconds, ttlSecondsAfterFinished, restartPolicy, priorityClassName, terminationGracePeriodSeconds) - enabled renders ConfigMap carrying run_chunk.rb - container image renders as the pinned digest - JOB_COMPLETION_INDEX is projected via downward API - NFS PVC + runner-script ConfigMap mounts - storage.existingClaim override propagates - container args invoke run_chunk.rb with the correct flag set (snapshot) - resources block renders limits + requests - ConfigMap data.run_chunk.rb starts with #!/usr/bin/env ruby shebang and contains the correct Usage: line (matchRegex) - empty container.image fails the render with a clear error - cluster-autoscaler safe-to-evict annotation is set - pod uses worker node-group affinity (isSubset) helm-unittest gotchas worked through (worth recording): * 'err:' is not a valid field; the right field for failure assertions is 'failedTemplate:' with an errorMessage. * 'service:' under release: is not parsed. Only name/namespace/revision /upgrade are accepted. * documentSelector: only takes path/value/matchMany/skipEmptyTemplates. Use the test-job level 'template:' to scope a single template. * Map keys containing '.' in path expressions use bracket syntax: data["run_chunk.rb"], not data.run_chunk.rb. * 'contains' requires an array path; for string fields use matchRegex. * Go regexp matches ^ at start-of-line by default; use \A for start-of-string anchors. * 'isSubset' matches an object subset; 'contains' matches an array element. The affinity block is an object, so isSubset. Snapshot tests cover the full run_chunk.rb script (truncated on display but verified via diff against the upstream file) and the container args block. Snapshot files live under openstudio-server/tests/__snapshot__/ and are committed. Docs (README.md): - New 'External Batch (Indexed Jobs)' section explaining dual-mode operation, the digest pin rationale, the dispatch workflow (package -> render template -> kubectl apply), the parameter table, and explicit notes on what this is NOT (not a worker replacement, not a substitute for AWS Batch/SLURM/Nomad). Verification: - helm unittest: 14/14 pass - helm lint: clean - README renders correctly
1 parent 604bca9 commit c711ddd

3 files changed

Lines changed: 398 additions & 0 deletions

File tree

README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,87 @@ and a smoke `helm template` render on every push and PR.
222222
- [ ] If you changed `values.yaml`, the chart still renders with default values
223223
- [ ] If you added a new `*.yaml` file under `templates/`, run `helm template`
224224
with several value combos and visually verify the output
225+
226+
## External Batch (Indexed Jobs)
227+
228+
When enabled, this chart can run `external_batch_run` analyses as native
229+
Kubernetes `batch/v1` Jobs (`completionMode: Indexed`) on the existing
230+
cluster, reusing the chart's NFS PVC. This complements the standard
231+
Resque worker deployment — workers handle iterative analyses
232+
(`batch_run`, `ga`, `pso`); indexed Jobs handle batched runs without
233+
the `web-background` Resque bottleneck or end-of-analysis compute valley.
234+
235+
The runner is plain stdlib Ruby plus the OpenStudio CLI; the CLI is in
236+
the `nrel/openstudio-server` image, and the runner script (`run_chunk.rb`)
237+
ships via ConfigMap at `/scripts/run_chunk.rb` inside each chunk pod.
238+
239+
### Image version (important)
240+
241+
The `external_batch` runner predates the chart's pinned `appVersion`
242+
(`3.8.0-1`), so `external_batch.container.image` defaults to a digest
243+
**newer than appVersion**. The default pin
244+
(`nrel/openstudio-server@sha256:9b0871fc...`, tag `:3.10.0-179D-test`,
245+
commit `d63c8eb` of `NatLabRockies/OpenStudio-server`) is **temporary**
246+
per upstream issue `#864` — bump both the digest and
247+
`openstudio-server/configmaps/runner/run_chunk.rb` together from the
248+
matching upstream commit when a stable release tag is published.
249+
250+
Pointing this value at the chart's main worker image tag will fail at
251+
runtime with "file not found" for `run_chunk.rb`.
252+
253+
### Dispatch workflow
254+
255+
The package is written by the server's `ExternalBatch::Packager` job
256+
when an analysis is submitted with `--batch-run-method external_batch_run`,
257+
under `<NFS mount>/external_batch/analysis_<id>/package/manifest.json`.
258+
The Indexed Job picks up that package and writes per-datapoint results
259+
to `<NFS mount>/external_batch/analysis_<id>/results/`; the server's
260+
`ExternalBatch::Ingester` polls and ingests.
261+
262+
To dispatch a packaged analysis on demand without modifying the running
263+
release:
264+
265+
```bash
266+
# 1. Submit the analysis via the server's existing endpoint with
267+
# --batch-run-method external_batch_run. The server will package
268+
# datapoints under <NFS mount>/external_batch/analysis_<id>/package.
269+
270+
# 2. Read the number of chunks from manifest.json's "chunks" array, then:
271+
helm template openstudio-server ./openstudio-server-helm \
272+
-s templates/job-external-batch.yaml \
273+
-s templates/runner-cm.yaml \
274+
--set external_batch.enabled=true \
275+
--set external_batch.analysis_id="<ANALYSIS_UUID>" \
276+
--set external_batch.completions=$(jq '.chunks | length' manifest.json) \
277+
--set external_batch.parallelism=8 \
278+
| kubectl apply -f -
279+
```
280+
281+
### Parameters
282+
283+
| Key | Default | Description |
284+
|---|---|---|
285+
| `external_batch.enabled` | `false` | Master switch. Set `true` to render the Job + ConfigMap. |
286+
| `external_batch.analysis_id` | `""` | The analysis whose package the runner will execute. Becomes part of the Job name and locates the package/results dirs on the shared NFS. |
287+
| `external_batch.completions` | `1` | Total chunks; each pod gets a unique `JOB_COMPLETION_INDEX` in `[0, completions)`. |
288+
| `external_batch.parallelism` | `1` | Concurrent chunk pods. Must be `<= completions`. |
289+
| `external_batch.backoff_limit` | `3` | K8s Job `backoffLimit`. |
290+
| `external_batch.active_deadline_seconds` | `86400` | 24h — matches the longest annual OpenStudio simulation. |
291+
| `external_batch.ttl_seconds_after_finished` | `3600` | Clean up completed pods after this many seconds. |
292+
| `external_batch.container.image` | digest-pinned `:3.10.0-179D-test` | MUST point at a tag that ships the runner script. See "Image version" above. |
293+
| `external_batch.container.imagePullPolicy` | `IfNotPresent` | |
294+
| `external_batch.storage.mountPath` | `/mnt/openstudio` | Where the chart already mounts the NFS PVC. |
295+
| `external_batch.storage.existingClaim` | `""` | Override the PVC claim name. Defaults to `.Values.nfs_pvc.name`. |
296+
| `external_batch.resources.{requests,limits}` | see `values.yaml` | Per-pod CPU/memory. |
297+
298+
### What this is NOT
299+
300+
- Not a replacement for the standard Resque workers. Iterative analyses
301+
(`batch_run`, `ga`, `pso`) still use `web-background` workers.
302+
- Not a substitute for a proper external batch cluster (AWS Batch, SLURM
303+
via Kestrel, Nomad). This chart simply runs the existing local-mode
304+
runner (`run_chunk.rb`) on K8s pods, on the existing cluster, reusing
305+
the existing NFS PVC.
306+
- Not bundled with a one-shot "submit and dispatch" tool. The user is
307+
expected to render the templates via `helm template ... | kubectl apply -f -`
308+
after the server has packaged the analysis.

0 commit comments

Comments
 (0)