Skip to content

Feat #99: Add optional Kubernetes Indexed Job template for external_batch simulation execution - #103

Open
anchapin wants to merge 2 commits into
chore/ci-helper-namespacefrom
feat/issue-99
Open

Feat #99: Add optional Kubernetes Indexed Job template for external_batch simulation execution#103
anchapin wants to merge 2 commits into
chore/ci-helper-namespacefrom
feat/issue-99

Conversation

@anchapin

Copy link
Copy Markdown
Collaborator

Closes #99

Stacked on #102 (chore/ci-helper-namespace) — PR 1's commit history doesn't appear in this PR's diff.

What

Adds an opt-in batch/v1 Job (templates/job-external-batch.yaml, completionMode: Indexed) that runs external_batch_run analyses as native Kubernetes pods on the existing cluster, reusing the chart's NFS PVC. Standard Resque workers stay intact — both modes coexist.

The runner script (run_chunk.rb) ships via ConfigMap (templates/runner-cm.yaml) sourced from configmaps/runner/run_chunk.rb. Each pod gets a unique JOB_COMPLETION_INDEX via the downward API and processes one slice of manifest.json's chunks array.

Image

external_batch.container.image defaults to a digest-pinned nrel/openstudio-server@sha256:9b0871fc... (tag :3.10.0-179D-test, commit d63c8eb of NatLabRockies/OpenStudio-server). Intentionally decoupled from the chart's appVersion (3.8.0-1) which predates the runner. Temporary pin per upstream issue #864.

Files

  • openstudio-server/configmaps/runner/run_chunk.rb — upstream runner (15.9 KB, fetched verbatim from d63c8eb)
  • openstudio-server/templates/runner-cm.yaml — ConfigMap sourced from the script via .Files.Get
  • openstudio-server/templates/job-external-batch.yaml — the Job
  • openstudio-server/tests/job-external-batch_test.yaml — 14 helm-unittest cases
  • openstudio-server/tests/__snapshot__/job-external-batch_test.yaml.snap — snapshot baseline
  • openstudio-server/values.yamlexternal_batch: block
  • openstudio-server/Chart.yaml — 0.6.1 → 0.7.0
  • README.md — new "External Batch (Indexed Jobs)" section

Spec drift from issue body (recorded for reviewers)

The issue proposed --analysis-id/--chunk-id/--storage-dir flags at /openstudio-server/external_batch/runner/run_chunk.rb. The actual upstream runner uses --package/--results/--chunk/--openstudio, and isn't in the Docker image at all (fetched from the OpenStudio-server repo). This PR uses the verified actual interface; full diff in the first commit message.

Verified

  • helm lint clean
  • helm unittest 14/14 pass
  • helm template at default values: 0 external_batch refs (opt-in verified)
  • helm template --set external_batch.enabled=true ...: 1 Job + 1 ConfigMap render correctly
  • Image pull + CLI verification: ruby 3.2.2, openstudio 3.10.0+86d7e215a1, bundle all present

Stacking note

This PR's base is chore/ci-helper-namespace (PR #102's branch), not develop. After #102 merges, retarget this PR's base to develop (or rebase) before merging. The diff against chore/ci-helper-namespace shows only PR 2 changes.

CI behavior on stacked PRs

PR #102's CI workflow triggers on on.pull_request.branches: [develop, main]. This PR's base is chore/ci-helper-namespace, which is not in that list — so the CI workflow may NOT fire on this PR's open. If that's the case, either:

Adds an opt-in batch/v1 Job (templates/job-external-batch.yaml) that
runs external_batch_run analyses as native Kubernetes Indexed Jobs on
the existing cluster, reusing the chart's NFS PVC. Each pod gets a
JOB_COMPLETION_INDEX via the downward API and processes one slice of
the manifest.json chunks array.

Files added:

- templates/job-external-batch.yaml -- rendered only when
  external_batch.enabled=true. batch/v1 Job, completionMode: Indexed,
  parameterized completions/parallelism/backoffLimit/activeDeadlineSeconds
  /ttlSecondsAfterFinished. Pod uses worker node-group affinity
  (openstudio-server.nodeGroupAffinity from # PR1), low priority, 5200s
  termination grace (matches worker pods). Two volume mounts: the
  chart's NFS PVC at storage.mountPath and a runner-script ConfigMap
  at /scripts/run_chunk.rb (defaultMode 0o755). Container args invoke
  the runner with --package, --results, --chunk JOB_COMPLETION_INDEX,
  --openstudio /usr/local/bin/openstudio.

- templates/runner-cm.yaml -- ConfigMap sourced from
  configmaps/runner/run_chunk.rb via .Files.Get. Carries the upstream
  runner script (plain stdlib Ruby + OpenStudio CLI) so chunk pods
  don't depend on the script being baked into the worker image.

- configmaps/runner/run_chunk.rb -- the upstream external_batch runner,
  fetched verbatim from commit d63c8eb2e3b37bca2494e66cab3c4457cf5c4e61
  of NatLabRockies/OpenStudio-server (the same commit that produced the
  digest pinned in external_batch.container.image).

Files modified:

- openstudio-server/values.yaml -- append external_batch block. Image
  default is pinned by digest to the runner-supporting upstream tag
  (intentionally decoupled from chart appVersion 3.8.0-1, which
  predates the runner). All other parameters mirror the issue spec.

- openstudio-server/Chart.yaml -- 0.6.1 -> 0.7.0 (minor). Opt-in
  feature; default values render exactly as before.

Notable spec drift from the issue body (commit message for posterity):

The issue body proposed the runner as:

  ruby /openstudio-server/external_batch/runner/run_chunk.rb \
    --analysis-id <ID> \
    --chunk-id <N> \
    --storage-dir <DIR>

The actual upstream runner uses (verified by pulling the digest-pinned
image and inspecting /opt/openstudio/server/app/lib/external_batch/* +
raw.githubusercontent.com/NatLabRockies/OpenStudio-server/d63c8eb/
external_batch/runner/run_chunk.rb):

  ruby run_chunk.rb \
    --package <DIR> --results <DIR> --chunk <N> --openstudio <CMD>

Differences:

  * run_chunk.rb is NOT in the nrel/openstudio-server image. It is
    fetched from the OpenStudio-server repo at runtime. This PR ships
    it via ConfigMap.
  * Flags are --package/--results/--chunk/--openstudio (no
    --analysis-id, no --storage-dir, --chunk is the chunk index).
  * analysis_id is read from manifest.json, not from a CLI flag.
  * The runner expects the package at <mountPath>/external_batch/
    analysis_<id>/package/ and writes results to <mountPath>/
    external_batch/analysis_<id>/results/. The server's
    ExternalBatch::Packager writes that layout when an analysis is
    submitted with --batch-run-method external_batch_run.

Verified:

  * helm lint clean
  * helm template at default values: 0 external_batch references
  * helm template --set external_batch.enabled=true ...: 1 Job +
    1 ConfigMap rendered
  * Pod-args template content matches the actual runner CLI
  * Storage PVC override (storage.existingClaim) propagates correctly
  * Downward API for JOB_COMPLETION_INDEX uses
    batch.kubernetes.io/job-completion-index (k8s 1.21+)

Closes #99.
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant