Container family: multi-architecture container deployment (port from rift_O4d) - #164
Conversation
…rift_O4d)
Point SINGULARITY_RIFT_IMAGE at a .yaml/.yml *manifest* describing several
images that target different GPU compute capabilities, and let HTCondor pick
the right one per matched machine. A plain .sif path or single osdf:// URL
keeps the exact legacy single-image behavior -- a manifest is recognized purely
by its file extension, and pyyaml is only needed when one is used.
Ported from origin/rift_O4d, where the wiring lives in the (master-absent)
dag_utils_generic.py rewrite; here it is applied to dag_utils.py directly.
* RIFT/misc/container_manifest.py -- manifest parse/validate plus the
expression builders: capability ifThenElse image selection, comma-free
$$() selective-transfer token, require_gpus capability floor,
container-universe container_image selector, capability-defined
Requirements clause, CPU-safe single-fallback resolution, and the
runtime-selection wrapper script.
* dag_utils.write_ILE_sub_simple / write_CIP_sub -- emit the above. GPU ILE
gets the per-machine selection, the floor composed (not replaced) with
RIFT_REQUIRE_GPUS, and TARGET.<attr> =!= undefined so it never matches a
slot where the selection cannot expand. CPU-only CIP collapses to the
single CPU-safe fallback image (a $$() would hold it).
* Two opt-in OSG delivery modes, since OSPool pilots read SingularityImage as
a literal string: RIFT_CONTAINER_UNIVERSE=1 (universe=container +
container_image = $$([...]), schedd-side match-time substitution --
recommended) and RIFT_CONTAINER_RUNTIME_SELECT=1 (ILE-only wrapper that
detects the real GPU and fetches just that image).
* create_event_parameter_pipeline_BasicIteration -- enable the osdf transfer
credential by inspecting the manifest's image URLs; the single-image checks
only see a .yaml path and would leave every job held.
* util_RIFT_pseudo_pipe.py -- accept singularity_rift_image /
singularity_base_exe_dir from the ini (env still wins).
* containers/ build kit (multi-target build_family.sh + template + example
manifest) and docs/source/containers.rst.
* test/test_container_manifest.py -- 25 tests; the integration cases inspect
the generated condor commands, no pool needed.
Not ported (separate features on rift_O4d): containers/survey_scan/ and the
GitHub Actions dependency canaries (this branch has no .github/).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rse; record condor_submit finding
- create_event_parameter_pipeline_BasicIteration: container universe deliberately
drops MY.SingularityBindCVMFS, so the hardcoded /cvmfs lalapps_path2cache that
ile_pre.sh calls may not exist inside the image. Under RIFT_CONTAINER_UNIVERSE,
use the container's own SINGULARITY_BASE_EXE_DIR/lal_path2cache instead. The
legacy single-image path is untouched.
- docs + containers/README: record what condor_submit 25.11 actually does with
container_image = $$([...]). It derives ContainerImage as the text after the
last '/' BEFORE any $$ expansion, so the selection is truncated
("...default.sif\") ])"). ContainerImageFullPath and transfer_input_files keep
the intact $$ token and expand at match time, but ContainerImage no longer
contains a $$ and nothing repairs it unless the schedd re-derives it -- which
is unverified against a live GPU match. Flagged as an open item to check on one
real ILE job before a production OSPool campaign; the runtime-select mode is
unaffected.
Evidence and a reproducer live in ~/LVK/IR1/demo_multi_container.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Isolated review of current head
Because
Focused results: |
… on a live OSPool match)
The container-universe mode inherited from rift_O4d does not work on OSPool.
condor_submit parses container_image BEFORE any $$ expansion and derives the job
ad's ContainerImage -- the name the image gets in the job scratch dir -- as the
text after the LAST '/'. rift_O4d put full osdf:// URLs in the $$([...]) selector,
so it was cut in half and the surviving fragment is not a valid image name:
ContainerImage="rift_o4d_cc60-90_cuda118_20260717.sif\") ])"
ContainerImageFullPath keeps the $$ and expands correctly at match time;
ContainerImage does not, and the OSG glidein PREPARE_JOB prepare-hook is what
reads it. Three trivial jobs on the IGWN pool (3 MB sif images staged on OSDF,
keyed on TARGET.Memory so they match a CPU slot; condor_submit derives
ContainerImage identically either way):
5926098 plain single osdf image -> ran, exit 0
5926099 rift_O4d form (full URLs) -> HELD: "PREPARE_JOB (prepare-hook)
failed (reported status 001): Unable to download or build
singularity image cutest_busybox_20260810.sif\") ])"
5926100 this fix -> ran, exit 0,
MATCH_EXP_ContainerImage = "cutest_alpine_20260810.sif"
Fix:
- build_container_image_select() emits BASENAMES, so the selector holds no '/',
condor_submit's derivation is a no-op, the whole $$ token reaches the job ad,
and the schedd expands it at match time.
- The matched image is delivered by the comma-free $$() transfer token, which
container universe previously skipped (it assumed container_image would fetch).
- MY.TransferInput is pinned to the same list so condor_submit does not append
the basename selector to TransferInput as a bogus extra input file.
- A family containing an in-place (CVMFS/local) image now raises
ContainerManifestError under container universe: such an image can only be
named by its full path, which reintroduces the truncation. Stage it at a URL,
or use RIFT_CONTAINER_RUNTIME_SELECT=1. The shipped example manifest is
updated to all-URL accordingly.
CIP is untouched -- CPU-only, already a single plain image, which condor_submit
handles correctly.
Tests, docs/source/containers.rst and containers/README.md updated to the
corrected contract and the live evidence. Reproducer: ~/LVK/IR1/demo_multi_container
(validate_build.sh, live_check/).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Update: the container-universe mode was genuinely broken; now fixed and verified liveThe open item flagged above was not a dry-run artifact. I submitted real jobs to the IGWN pool to settle it, using two 3 MB
The hold, from the execute point: The
Fix
CIP is untouched: CPU-only, already a single plain image, which condor_submit handles correctly. Generated Tests 26/26; the demo's submit-file assertions 28/28. Note for 🤖 Generated with Claude Code |
…me selector The row still said container universe delivers the image via container_image with no transfer token. It does not: the selector names basenames, so the matched image arrives via the $$() transfer token with MY.TransferInput pinned, and every image in the family must be a transferable URL. (An earlier edit missed this line -- it matched on '--' where the file has an em dash.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ports the container family feature (multi-architecture container deployment) from
rift_O4dtomaster.Point
SINGULARITY_RIFT_IMAGEat a.yaml/.ymlmanifest describing several images that target different GPU compute capabilities, and let HTCondor pick the right one per matched machine. A plain.sifpath or singleosdf://URL keeps the exact legacy single-image behaviour — a manifest is recognised purely by its file extension, andpyyamlis only required when one is actually used.On
rift_O4dthe wiring lives indag_utils_generic.py, part of a backend-neutral rewrite thatmasterdoes not have. Here the same wiring is applied todag_utils.pydirectly;container_manifest.pyand thecontainers/build kit are ported unchanged.What is in it
RIFT/misc/container_manifest.py— manifest parse/validate plus the expression builders: capabilityifThenElseimage selection, comma-free$$()selective-transfer token,require_gpuscapability floor, container-universecontainer_imageselector, capability-definedRequirementsclause, CPU-safe single-fallback resolution, and the runtime-selection wrapper script.dag_utils.write_ILE_sub_simple/write_CIP_sub— emit the above. The GPU ILE job gets the per-machine selection, the family floor composed (not replaced) withRIFT_REQUIRE_GPUS, andTARGET.<attr> =!= undefinedso it never matches a slot where the selection cannot expand. CPU-only CIP collapses to the single CPU-safe fallback image, because a$$()capability expression on a slot with no GPU would hold the job.SingularityImageas a literal string:RIFT_CONTAINER_UNIVERSE=1(universe=container+container_image = $$([...]), schedd-side match-time substitution — the intended OSG mode) andRIFT_CONTAINER_RUNTIME_SELECT=1(ILE-only wrapper that detects the real GPU at job start and fetches just that image).create_event_parameter_pipeline_BasicIteration— enable theosdftransfer credential by inspecting the manifest's image URLs. Without this the existing substring checks only see a.yamlpath and every worker job is held for a missing credential. Also, under container universe (which deliberately dropsMY.SingularityBindCVMFS)LALAPPS_PATH2CACHEnow points at the container's ownlal_path2cacherather than a hardcoded/cvmfspath.util_RIFT_pseudo_pipe.py— acceptsingularity_rift_image/singularity_base_exe_dirfrom the ini (environment still wins), matching how accounting andile_require_gpusalready work.containers/build kit (multi-targetbuild_family.sh+ one sharedrift_container.def.intemplate + example manifest) anddocs/source/containers.rst.test/test_container_manifest.py— 25 tests, all passing. The integration cases inspect the condor commands on the job object returned bywrite_ILE_sub_simple/write_CIP_sub, so no pool is needed.No asimov change is required:
RIFT/asimov/rift.pyonmasteralready exportsscheduler: singularity image:andscheduler: environment variables:into the pipeline environment.Not ported (separate features that live on
rift_O4d):containers/survey_scan/and the GitHub Actions dependency canaries — this branch has no.github/. Thecontainers/README.mdsays so explicitly rather than leaving dangling links.Validation
An OSG-style asimov build for S240426s was rebuilt with the ported code and compared against the already-built single-container run in
ir1_prod_testing_personal, using that project's own inputs and the pseudo_pipe command line recorded in itsasimov.log. Workspace, scripts and captured logs:~/LVK/IR1/demo_multi_container.24/24 assertions pass. Generated
ILE.sub:CIP.subgets the single fallback image, no$$(), no floor, no capability requirement. Every non-container submit command is byte-identical to the reference build.Open item — please read before a production OSPool campaign
condor_submit -dry-runshows thatcondor_submitparsescontainer_imagebefore any$$expansion, and derives the job ad'sContainerImage(the name the image gets in the job scratch dir) as the text after the last/. A$$([...])selection contains slashes, so that derivation truncates it:ContainerImageFullPathandtransfer_input_fileskeep the$$token and expand correctly at match time;ContainerImagehas no$$left, so nothing repairs it unless the schedd re-derives it after expansion — which I could not verify without a live GPU match. Confirmed on$CondorVersion: 25.11.1.This is inherited from
rift_O4d, not introduced here — the port reproducesrift_O4d's output. It is recorded indocs/source/containers.rstandcontainers/README.md, with a reproducer (check_condor_expansion.sh) in the demo workspace.RIFT_CONTAINER_RUNTIME_SELECT=1emits no container-universe attributes at all and is unaffected.🤖 Generated with Claude Code