Skip to content

[DevOps]: Add Chrysalis ingestion wrapper and deploy - #169

Open
tomvothecoder wants to merge 12 commits into
E3SM-Project:mainfrom
tomvothecoder:feature/154-ingestion-sites
Open

[DevOps]: Add Chrysalis ingestion wrapper and deploy #169
tomvothecoder wants to merge 12 commits into
E3SM-Project:mainfrom
tomvothecoder:feature/154-ingestion-sites

Conversation

@tomvothecoder

@tomvothecoder tomvothecoder commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

Description

This adds a scheduler-agnostic HPC archive ingestor entrypoint and a thin Chrysalis site wrapper for existing Jenkins-driven metadata ingestion.

  • Closes [DevOps]: Implement remote data collection job at Chrysalis for ingestion #154
  • Adds shared hpc_archive_ingestor module that delegates to existing NERSC ingestor
  • Adds sites/chrysalis.sh wrapper with Chrysalis archive and state defaults plus required API env vars
  • Documents shared ingestor and site-wrapper pattern in backend scripts README
  • Adds test coverage for the generic HPC module entrypoint

Task

Finish turning this branch into a deployable ingestion path for Chrysalis first, then use the same pattern for other sites once access is available.

  • Review the current branch implementation and keep ingestion logic in Python, not shell wrappers.
  • Validate the Chrysalis archive path and Jenkins runtime assumptions.
  • Confirm how SIMBOARD_API_BASE_URL and SIMBOARD_API_TOKEN should be stored and injected in the Chrysalis Jenkins job.
  • Run the Chrysalis wrapper in dry-run mode and verify archive access, network egress to SimBoard, and candidate counts.
  • Enable non-dry-run ingestion only after the dry-run output is validated.
  • Apply for or confirm accounts/access for Frontier, Aurora, and Compy.
  • After Chrysalis works, add equivalent thin wrappers for the remaining sites as access allows.

Checklist

  • Code follows project style guidelines
  • Self-reviewed code
  • No new warnings
  • Tests added or updated (if needed)
  • All tests pass (locally and CI/CD)
  • Documentation/comments updated (if needed)
  • Breaking change noted (if applicable)

Deployment Notes (if any)

No special deployment steps.

Local validation is currently blocked because PostgreSQL was unavailable at 127.0.0.1, so make backend-test and the targeted ingestion test file could not complete in this environment.

@tomvothecoder
tomvothecoder requested a review from TonyB9000 May 6, 2026 22:33
@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Once I am clear on the boundaries to the term "NERSC ingestion wrapper", I should be able to comprehent "Chrysalis ingestion wrapper". The term "scheduler-agnostic" refers to Jenkins? (I always considered cron to be universal...).

@TonyB9000 TonyB9000 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Configures a call to the hpc_archive_ingestor. Understandable.

What process sets "SIMBOARD_API_BASE_URL" and "SIMBOARD_API_TOKEN"?

@TonyB9000

Copy link
Copy Markdown
Collaborator

Hmmmm. The "Tom Requested your review" took me to the page with 7 files to examine, each with a "submit-review" option. As soon as I completed the first one, all 7 vanished...

@tomvothecoder

tomvothecoder commented May 13, 2026

Copy link
Copy Markdown
Collaborator Author

Configures a call to the hpc_archive_ingestor. Understandable.

What process sets "SIMBOARD_API_BASE_URL" and "SIMBOARD_API_TOKEN"?

Hmmmm. The "Tom Requested your review" took me to the page with 7 files to examine, each with a "submit-review" option. As soon as I completed the first one, all 7 vanished...

Accidentally tagged you for review. I meant to assign this PR you. It is fixed now.

@tomvothecoder tomvothecoder added the type: enhancement New feature or request label May 13, 2026
@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder "Accidentally tagged you for review". OK, (I think colleges should offer a master's program in github).

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

Chrysalis and other non-NERSC sites require upload-based ingestion rather than path-based ingestion, so follow-up work is tracked in #207 for a state-first HPC upload flow with DB-backed dedupe parity.

@TonyB9000

Copy link
Copy Markdown
Collaborator

Using the "upload-based' vs "path-based" terminology, my thought was that when the NERSC upload-receiving system was deliverd an upload from a non-NERSC system, it could open it in the existing NERSC PA-directory under (say) "From_crysalis/<new_exec_ids>" and then process it with the existing "path-based" codes - assuming PACE would not interfere with it (and vice-versa). But on second thought, to avoid PACE crossing, it would be best to open it in a separate "PACE-unaware" directory.

@TonyB9000

TonyB9000 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder I am preparing to exercise "hpc_upload_archive_ingestor.py" on chrysalis, to see the logs and flow (in dry-run) in action, discover parameter faults, etc.

QUESTION: Although, on NERSC, the backend ingestion is "path-based" (returnsp paths for ingestion), it could in principle run the "https-transfer-based" codes just as easily. I might try a dryrun on NESRC/Perlmutter first, since that configuration is already a known item. Then, differences in behavior on chrysalis would stand out. Does that make sense?

@tomvothecoder
tomvothecoder force-pushed the feature/154-ingestion-sites branch from 22a1a88 to feae197 Compare June 4, 2026 20:26
@TonyB9000

TonyB9000 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder Apologies if I'm doing this wrong.

I attempted to test the "hpc_upload" on NESRC, thinking "--help" might be helpful. To get started, I needed an environment where I could install things, so:

After

    python3.11 -m venv ~/envs/test_simboard
    source ~/envs/test_simboard/bin/activate
    python3.11 -m pip install --upgrade pip

    python3.11 -m pip install python-dateutil
    pip install pydantic
    pip install fastapi_users
 
The (bash script) commands:

    REPO_ROOT="/global/homes/t/tonyb/gitrepo/simboard/backend"
    SCRIPT="$REPO_ROOT/app/scripts/ingestion/hpc_upload_archive_ingestor.py"

    PYTHONPATH="$REPO_ROOT"
    python3.11 "$SCRIPT" --help

Produces the following output:

2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464377+00:00 event=run_started archive_root=/performance_archive mode=ingest
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464638+00:00 event=startup_configuration_begin
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464749+00:00 event=summary_table row_count=10 rows="api.api_base_url=http://backend:8000 | api.endpoint_url=http://backend:8000/api/v1/ingestions/from-hpc-upload | api.state_endpoint_url=http://backend:8000/api/v1/ingestions/state | paths.archive_root=/performance_archive | runtime.machine_name=perlmutter | runtime.dry_run=false | runtime.max_cases_per_run=null | runtime.max_attempts=3 | runtime.request_timeout_seconds=60 | auth.has_api_token=false" title=startup_configuration
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464819+00:00 event=startup_configuration_end
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464876+00:00 event=archive_root_missing archive_root=/performance_archive
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464929+00:00 event=run_finished duration_seconds=0.001 exit_code=1 mode=ingest

I now see there is no commandline parsing.  I need to set “dry_run” as an environment variable so that the auto-generated config will pick it up.  I must have missed where the docs explain setting the environment variables.  I assume I can set them in my “run_script”.

Comment thread backend/app/scripts/ingestion/sites/chrysalis.sh Outdated
@tomvothecoder

tomvothecoder commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

Hey Tony, happy to help and no apologies needed.

I attempted to test the "hpc_upload" on NESRC, thinking "--help" might be helpful. To get started, I needed an environment where I could install things, so:

After

    python3.11 -m venv ~/envs/test_simboard
    source ~/envs/test_simboard/bin/activate
    python3.11 -m pip install --upgrade pip

    python3.11 -m pip install python-dateutil
    pip install pydantic
    pip install fastapi_users
 

SimBoard defines the Python backend dependencies in pyproject.toml and uses uv for dependency management.

You can run make backend-install if you only need a Python env (source).

The (bash script) commands:

REPO_ROOT="/global/homes/t/tonyb/gitrepo/simboard/backend"
SCRIPT="$REPO_ROOT/app/scripts/ingestion/hpc_upload_archive_ingestor.py"

PYTHONPATH="$REPO_ROOT"
python3.11 "$SCRIPT" --help

Produces the following output:

2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464377+00:00 event=run_started archive_root=/performance_archive mode=ingest
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464638+00:00 event=startup_configuration_begin
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464749+00:00 event=summary_table row_count=10 rows="api.api_base_url=http://backend:8000 | api.endpoint_url=http://backend:8000/api/v1/ingestions/from-hpc-upload | api.state_endpoint_url=http://backend:8000/api/v1/ingestions/state | paths.archive_root=/performance_archive | runtime.machine_name=perlmutter | runtime.dry_run=false | runtime.max_cases_per_run=null | runtime.max_attempts=3 | runtime.request_timeout_seconds=60 | auth.has_api_token=false" title=startup_configuration
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464819+00:00 event=startup_configuration_end
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464876+00:00 event=archive_root_missing archive_root=/performance_archive
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464929+00:00 event=run_finished duration_seconds=0.001 exit_code=1 mode=ingest

I now see there is no commandline parsing. I need to set “dry_run” as an environment variable so that the auto-generated config will pick it up. I must have missed where the docs explain setting the environment variables. I assume I can set them in my “run_script”.

I'd checkout this branch now that I've rebased it on the latest main commit.

The chrysalis.sh bash script exports environment variables and wraps hpc_upload_archive_ingestor.py. You can try experimenting with that script. More info here: https://github.com/tomvothecoder/simboard/tree/feature/154-ingestion-sites/backend/app/scripts#hpc-upload-archive-ingestor.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder I get the latest stuff - but I have made progress. My latest run_script (NERSC dry_run test) says:

REPO_ROOT="/global/homes/t/tonyb/gitrepo/simboard/backend"
WORKDIR="/global/homes/t/tonyb/test/simboard"
SCRIPT="$REPO_ROOT/app/scripts/ingestion/hpc_upload_archive_ingestor.py"

export PYTHONPATH="$REPO_ROOT"
export DRY_RUN=True
python3.11 "$SCRIPT"

The output indicates that I am missing "archive_root” and “has_api_token”.

By examining the "nersc" "_build_config" function, I can see what variables exist to push into the environment.

I'll checkout branch #169 on both NERSC and Chrysalis to do comparisons in outputs.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder git gets me again:

You wrote: "I'd checkout this branch now that I've rebased it on the latest main commit."

is "this branch" off of main, as you had advised? Or is it off of a fork??

((test_simboard) ) (base) [ac.bartoletti1@chrlogin1 simboard]$ git branch -a

  • main
    remotes/origin/HEAD -> origin/main
    remotes/origin/copilot/analyze-simboard-devops-issues
    remotes/origin/copilot/check-copilot-agent-tokens
    remotes/origin/copilot/enhance-simulation-details-page
    remotes/origin/copilot/fix-hpc-filepaths-issue
    remotes/origin/dev-ai
    remotes/origin/fix/181-archive-path-substitution
    remotes/origin/main

When I get too confused, I do a clean "git clone". Then I can do one of these:

To pull a remote branch down from remote:

    git fetch --all --prune
    git checkout -b newbranchname origin/newbranchname

To checkout a remote branch pushed but not merged to main/master

    git fetch origin <the_remote_branch_name>
    git checkout -b <any_new_local_name> origin/<the_remote_branch_name>

to fetch a branch from a remote fork: (example)

    git remote add tomvothecoder https://github.com/tomvothecoder/simboard.git
    git fetch tomvothecoder
    git checkout -b feature/154-ingestion-sites tomvothecoder/feature/154-ingestion-sites

Which is appropriate in this case?

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder If I pull down a branch off of someone's fork, am I in that fork, or can I pull that into a new branch of my local main? The persistence of branches and forks, between local and remote, is a bit of a mystery.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder If I pull down a branch off of someone's fork, am I in that fork, or can I pull that into a new branch of my local main? The persistence of branches and forks, between local and remote, is a bit of a mystery.

  • Upstream -> E3SM-Project/simboard
  • Fork -> tomvothecoder/simboard

This branch (tomvothecoder:feature/154-ingestion-sites) is on my fork (tomvothecoder/simboard), not on upstream (E3SM-Project/simboard) You need to add my fork as a remote git source to git checkout branches from my fork.

Something like this (I did not verify correctness):

git remote add tomvothecoder https://github.com/tomvothecoder/simboard
git checkout tomvothecoder feature/154-ingestion-ites 

I usually work directly on upstream and not fork when possible, but in this case I use a fork for separate testing purposes.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Sorry, I guess I must pull from your fork.

Quick test: I cd to ``/home/ac.bartoletti1/gitrepo/simboard/backend" and issue

python3.12 -m app.scripts.ingestion.nersc_archive_ingestor --api-base-url http://backend:8000 --machine-name chrysalis

The result:

2026-06-05 16:23:29,964 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.964677+00:00 event=run_started archive_root=/performance_archive mode=ingest
2026-06-05 16:23:29,964 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.964921+00:00 event=startup_configuration_begin
2026-06-05 16:23:29,965 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.965032+00:00 event=summary_table row_count=10 rows="api.api_base_url=_fake_url_ | api.endpoint_url=_fake_url_/api/v1/ingestions/from-path | api.state_endpoint_url=_fake_url_/api/v1/ingestions/state | paths.archive_root=/performance_archive | runtime.machine_name=perlmutter | runtime.dry_run=false | runtime.max_cases_per_run=null | runtime.max_attempts=3 | runtime.request_timeout_seconds=60 | auth.has_api_token=true" title=startup_configuration
2026-06-05 16:23:29,965 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.965082+00:00 event=startup_configuration_end
2026-06-05 16:23:29,965 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.965118+00:00 event=archive_root_missing archive_root=/performance_archive
2026-06-05 16:23:29,965 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.965168+00:00 event=run_finished duration_seconds=0.0 exit_code=1 mode=ingest

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder The line in "chrysalis.sh"

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"; echo $script_dir
(prints "/home/ac.bartoletti1/test/simboard")

clearly wont work for defining "backend_root" as backend_root="$(cd "${script_dir}/../../../.." && pwd)"

I will modify chrysalis.sh to provide a "backend_root" that does not depend upon the user location., at least for test purposes.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Works better now that it can find "backentd/apps"

When I use this for "chrysalis.sh":

GITREPO="/home/ac.bartoletti1/gitrepo"

: "${SIMBOARD_API_BASE_URL:?SIMBOARD_API_BASE_URL is required}"
: "${SIMBOARD_API_TOKEN:?SIMBOARD_API_TOKEN is required}"

export MACHINE_NAME="${MACHINE_NAME:-chrysalis}"
export PERF_ARCHIVE_ROOT="${PERF_ARCHIVE_ROOT:-/lcrc/group/e3sm/PERF_Chrysalis/performance_archive}"
export STATE_PATH="${STATE_PATH:-${PERF_ARCHIVE_ROOT}/../simboard-ingestion-state.json}"
export DRY_RUN="${DRY_RUN:-true}"

backend_root="$GITREPO/simboard/backend"
python_bin="${PYTHON_BIN:-python}"

cd "${backend_root}"
exec "${python_bin}" -m app.scripts.ingestion.hpc_upload_archive_ingestor

and issue these exports:

export SIMBOARD_API_BASE_URL=" http://backend:8000"
export SIMBOARD_API_TOKEN="_fake_token_"

I get:

2026-06-05 17:17:50,763 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.763649+00:00 event=run_started archive_root=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive mode=dry-run
2026-06-05 17:17:50,763 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.763967+00:00 event=startup_configuration_begin
2026-06-05 17:17:50,764 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.764094+00:00 event=summary_table row_count=10 rows="api.api_base_url=\" http://backend:8000\" | api.endpoint_url=\" http://backend:8000/api/v1/ingestions/from-hpc-upload\" | api.stat
e_endpoint_url=\" http://backend:8000/api/v1/ingestions/state\" | paths.archive_root=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive | runtime.machine_name=chrysalis | runtime.dry_run=true | runtime.max_cases_per_run=null | runtime.max_attempts=3 | runtime.request_timeout_second
s=60 | auth.has_api_token=true" title=startup_configuration
2026-06-05 17:17:50,764 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.764155+00:00 event=startup_configuration_end
2026-06-05 17:17:50,816 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.816097+00:00 event=state_fetch_failed error="URL error: [Errno -2] Name or service not known" machine_name=chrysalis status_code=null
2026-06-05 17:17:50,816 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.816211+00:00 event=run_finished duration_seconds=0.053 exit_code=1 mode=dry-run

I guess, even "dry_run" requires real URLs and API_tokens. That is because we need "state" up front.

@TonyB9000

TonyB9000 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Hi @tomvothecoder The document also says:

One-case-per-request rule:

  • Each upload request contains exactly one case directory.
  • case_path is sent alongside the archive and becomes the stable dedupe key in the ingestion audit table.
  • Browser/manual uploads still use /api/v1/ingestions/from-upload; this runner does not call that endpoint.

The term "alongside the archive" is a bit ambiguous. Would this be accurate?

  • Each upload request contains exactly one case directory, and one or more newly-completed jlid archives.
  • case_path is sent alongside the archives, and (case_id + jlid) becomes the stable dedupe key in the ingestion audit table.
  • Browser/manual uploads still use /api/v1/ingestions/from-upload; this runner does not call that endpoint.

Or am I misunderstanding the intent?

@tomvothecoder

tomvothecoder commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

I guess, even "dry_run" requires real URLs and API_tokens. That is because we need "state" up front.

Great to see the progress!

Yes, the dry run needs to query the SimBoard database via the REST API. I will send the API_TOKEN over encrypted email to you.

Hi @tomvothecoder The document also says:

One-case-per-request rule:

* Each upload request contains exactly one case directory.

* case_path is sent alongside the archive and becomes the stable dedupe key in the ingestion audit table.

* Browser/manual uploads still use /api/v1/ingestions/from-upload; this runner does not call that endpoint.

The term "alongside the archive" is a bit ambiguous. Would this be accurate?

* Each upload request contains exactly one case directory, and one or more newly-completed jlid archives.

* case_path is sent alongside the archives, and (case_id + jlid) becomes the stable dedupe key in the ingestion audit table.

* Browser/manual uploads still use /api/v1/ingestions/from-upload; this runner does not call that endpoint.

Or am I misunderstanding the intent?

Your info sounds more accurate, thanks for the suggestion. Can you point me to the source document with this info? I will update it.

@TonyB9000

TonyB9000 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder Running "chrysalis.sh" with the full (DRY_RUN) parameters yieded the following summary (folded for readability):

event=summary_table
    row_count=9
    rows="mode=dry-run 
        | discovered_cases=746 
        | candidate_cases=746 
        | execution_dirs_scanned=3155 
        | execution_dirs_accepted=1649 
        | skipped_incomplete=1506
        | skipped_invalid=0 
        | candidate_logs_emitted=20 
        | candidate_logs_suppressed=726" 
    title=dry_run_summary
event=run_finished
    duration_seconds=374.342
    exit_code=0
    mode=dry-run

Questions that arise:

  • What distinguishes "discovered cases" from "candidate cases"?
  • What distinguishes "skipped_incomplete" from "skipped_invalid"?
  • Where is "skipped_already_accepted = 0"? Perhaps this test is unrealistic, as no "state" of previous accepted submissions exists,.
  • Why is there no count of "state" returned from the database? Was the query restricted to Chrysalis-only? Why is the DB query not indicated?
  • What is "candidate_logs_emitted/suppressed"?

Observation: The bulk of work getting to this point involved stuffing the right ENV VARS and having created an environment where misc modules like "dateutils" could be installed. On Chrysalis, I performed

    python3.12 -m venv ~/envs/test_simboard
    source ~/envs/test_simboard/bin/activate
    python3.12 -m pip install --upgrade pip

    python3.12 -m pip install python-dateutil
    pip install pydantic
    pip install fastapi_users

On NERSC/Perlmutter, I simply replaced "python3.12" with "python3.11". I intend to perform the same test on Perlmutter, just to exercise the mechanisms of networking.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder For comparison, running the equivalent commands on perlmutter (swapping our parameters where necessary), we obtain the summary:

event=summary_table
    row_count=9 
    rows="mode=dry-run 
        | discovered_cases=1289 
        | candidate_cases=1289 
        | execution_dirs_scanned=2514 
        | execution_dirs_accepted=1648 
        | skipped_incomplete=866 
        | skipped_invalid=0
        | candidate_logs_emitted=20
        | candidate_logs_suppressed=1269"
    title=dry_run_summary
event=run_finished
    duration_seconds=23.689
    exit_code=0
    mode=dry-run

I suppose I should re-run the chrysalis test, using "OLD_PERF" as the root_PA directory. It is HUGE.

@tomvothecoder

tomvothecoder commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

What distinguishes "discovered cases" from "candidate cases"?

discovered_casesare everything the archive scan finds that looks like a case.
candidate_cases are the subset that SimBoard does not already know about and may ingest.

Since this is a first-time dry-run on the Chrysalis performance_archive directory, it is expected that discovered_cases and candidates_cases are the same.

What distinguishes "skipped_incomplete" from "skipped_invalid"?

skipped_incomplete means required metadata was missing.
skipped_invalid means the metadata or path looked wrong, unreadable, or unusable.

Where is "skipped_already_accepted = 0"? Perhaps this test is unrealistic, as no "state" of previous accepted submissions exists.

That exact counter is not in nersc_archive_ingestor.py. The script checks existing SimBoard ingestion state and filters out already-known execution IDs, but it does not use the term “accepted” or expose a skipped_already_accepted count.

So yes: a test expecting that exact field is probably unrealistic or stale.

Why is there no count of "state" returned from the database? Was the query restricted to Chrysalis-only? Why is the DB query not indicated?

The ingestor script only asks SimBoard for enough existing ingestion state to decide which archive cases and their executions are new and may be candidates for ingestion. It does not fetch, return, or summarize the full database state. It also does not show the database query because the query is behind the SimBoard API, not inside the ingestor script. So this is not a Chrysalis-specific DB query in the ingestor. It is an API request filtered by the configured machine_name.

If more detail is needed, the API response or ingestor summary would need to be expanded to include counts like total known cases, known execution IDs, skipped known cases, and machine filter used.

Happy for you to open a new GitHub issue to expand logging in https://github.com/E3SM-Project/simboard/blob/main/backend/app/scripts/ingestion/nersc_archive_ingestor.py and https://github.com/E3SM-Project/simboard/blob/main/backend/app/scripts/ingestion/hpc_upload_archive_ingestor.py.

What is "candidate_logs_emitted/suppressed"?

What is candidate_logs_emitted/suppressed?

They are dry-run logging counters.

candidate_logs_emitted = how many candidate case details were actually printed to the log.

candidate_logs_suppressed = how many candidate case details were not printed because the script hit its logging limit.

The point is to avoid massive logs when many candidate cases are found. It does not change which cases are candidates or which cases would be ingested.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

I suppose I should re-run the chrysalis test, using "OLD_PERF" as the root_PA directory. It is HUGE.

I don't think this is going to work yet as the directory structure of "OLD_PERF" is different from "performance_archive".
We need to expand ingestion support for "OLD_PERF" in #209.

We might also want to be targeted in what we ingest from "OLD_PERF". This will require guidance Rob/Jill.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Certainly. It should not take long. I should grab the latest from main, then (not your fork, wherein I pushed the generic "simboard_pa_collection.sh" script the the .config files, I assume.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder After a fresh git-clone, it no longer finds "backend/.venv/bin/python". Must "make install" (why?)

@TonyB9000

TonyB9000 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder So far, so good:

2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=run_started mode=dry-run scan_mode=archive archive_root=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF
2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=startup_configuration_begin
2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=startup_configuration_api api_base_url=https://simboard-dev-api.e3sm.org endpoint_url=https://simboard-dev-api.e3sm.org/api/v1/ingestions/from-hpc-upload state_endpoint_url=https://simboard-dev-api.e3sm.org/api/v1/ingestions/state
2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=startup_configuration_paths scan_mode=archive archive_root=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF archive_year_start=2025-01 archive_year_end=null
2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=startup_configuration_runtime machine_name=perlmutter dry_run=true max_cases_per_run=50 max_attempts=3 request_timeout_seconds=60
2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=startup_configuration_auth has_api_token=true
2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=startup_configuration_end

(Why does it say "machine name = perlmutter, when I am running it on chrysalis? . . .)

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder FAILED to obtain state over API:

2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=startup_configuration_end
2026-08-04 15:53:21,837 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=state_fetch_failed error="Request timed out" machine_name=perlmutter status_code=null
2026-08-04 15:53:21,837 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=run_finished mode=dry-run scan_mode=archive exit_code=1 duration_seconds=112.313

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

(Why does it say "machine name = perlmutter, when I am running it on chrysalis? . . .)

You're using chrysalis.sh right?

Check your MACHINE_NAME config to make sure it is set to "chrysalis" beforehand. Otherwise it defaults to "perlmutter".

@tomvothecoder FAILED to obtain state over API:

2026-08-04 15:51:29,525 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=startup_configuration_end
2026-08-04 15:53:21,837 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=state_fetch_failed error="Request timed out" machine_name=perlmutter status_code=null
2026-08-04 15:53:21,837 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=run_finished mode=dry-run scan_mode=archive exit_code=1 duration_seconds=112.313

Could be due to the attempt in fetching state for perlmutter rather than chrysalis. Not sure.

@TonyB9000

TonyB9000 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder It's ok now. I issued "export MACHINE_NAME=chrysalis" manually, then re-ran, and now it is advancing normally. Never had to do that before.

Curiously, the hostname is actually "chrlogin1.lcrc.anl.gov", so how and why we would issue "chrysalis" is somewhat of a mystery.

I was hoping some standard command would return "chrysalis" as the machine-name. But I will instead add

export MACHINE_NAME="chrysalis"

to the chrysalis.config file.

@TonyB9000

Copy link
Copy Markdown
Collaborator

Completed:

2026-08-04 16:58:12,472 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=dry_run_completed accepted_execution_ids=56 deferred_execution_ids=5742 discovered_cases=3515 execution_dirs_accepted=5798 execution_dirs_scanned=10513 rejected_existing_execution_ids=0 rejected_incomplete_execution_ids=4715 rejected_invalid_execution
_ids=0 selected_submission_cases=50 skipped_incomplete=4715 skipped_invalid=0 skipped_transient=0 submission_qualified_cases=3515 transient_execution_ids=0
2026-08-04 16:58:12,472 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=dry_run_summary_counts mode=dry-run discovered_cases=3515 submission_qualified_cases=3515 selected_submission_cases=50 execution_dirs_scanned=10513 execution_dirs_accepted=5798 skipped_incomplete=4715 skipped_invalid=0 skipped_transient=0
2026-08-04 16:58:12,472 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=dry_run_summary_candidates accepted_execution_ids=56 rejected_existing_execution_ids=0 rejected_incomplete_execution_ids=4715 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=5742 candidate_logs_emitted=20 candidate_lo
gs_suppressed=30
2026-08-04 16:58:12,474 [INFO]: nersc_archive_ingestor.py(_log_event:3342) >> event=run_finished mode=dry-run scan_mode=archive exit_code=0 duration_seconds=2236.983

@tomvothecoder

tomvothecoder commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder It's ok now. I issued "export MACHINE_NAME=chrysalis" manually, then re-ran, and now it is advancing normally. Never had to do that before.

Curiously, the hostname is actually "chrlogin1.lcrc.anl.gov", so how and why we would issue "chrysalis" is somewhat of a mystery.

I was hoping some standard command would return "chrysalis" as the machine-name. But I will instead add

export MACHINE_NAME="chrysalis"

to the chrysalis.config file.

The ingestion scripts don't have a machine_name mapping based on the filesystem. It is explicitly set in the config before running the workflow.

machine_name is attached to case/execution/ingestion state objects and recorded in the SimBoard database.

I think the issue is that I set the default machine_name to perlmutter when it should be a required variable with no default. If this argument is not set, it should raise an error.

@tomvothecoder
tomvothecoder force-pushed the feature/154-ingestion-sites branch from fb76259 to 289a693 Compare August 4, 2026 23:55
@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Side Note: What are the pros and cons of supplying a parameter via command line argument:

call_application <param=value>

versus through environment variable

export VARNAME=<value>; call_application

Which is more self-documenting? The method of ENV VARS has greater scope (any subsequent application in a chain can inherit the variables) but is this always desirable?

@tomvothecoder

tomvothecoder commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder Side Note: What are the pros and cons of supplying a parameter via command line argument:

call_application <param=value>

versus through environment variable

export VARNAME=<value>; call_application

Which is more self-documenting? The method of ENV VARS has greater scope (any subsequent application in a chain can inherit the variables) but is this always desirable?

I generally prefer supplying env variables through .env files or config files if possible as it is more composable and maintainable. I think the export vars before the call is more for debugging or quick runs directly with the Python module. For example: SIMBOARD_API_BASE_URL=http://backend:8000 MACHINE_NAME=perlmutter uv run python -m app.scripts.ingestion.nersc_archive_ingestor

The reason why SIMBOARD_API_TOKEN is not saved in the bash wrappers like chrysalis.sh is because we don't want to add it to version control and it is safer to add the token at the runner level (e.g., set in cron before running bash script).

@tomvothecoder tomvothecoder left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TonyB9000, I am reviewing commit 289a693 in this PR.

My understanding is:

  1. The site-specific .sh wrappers are being replaced by .config files.
  2. simboard_pa_collection.sh becomes the shared entry point for all sites.

This seems like a reasonable direction since it reduces duplicated workflow logic. Could you clarify how the site config is expected to be selected and deployed? The script currently always sources $HOME/.simboard.config, while the repository contains separate chrysalis.config and nersc.config files.

I understand that the cron job itself still needs to be user-owned because there is no sudo access for a system-level job. My related question is whether the shared SimBoard scripts, configs, logs, or other operational files should live in a project directory rather than that user’s home directory. The user-specific cron entry could then invoke the shared project-owned workflow while keeping only genuinely user-specific settings in the home directory.

Comment on lines +26 to +27
source $SIMBOARD_API_TOKEN_CMD
: "${SIMBOARD_API_TOKEN:?SIMBOARD_API_TOKEN failed to be set.}"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this SIMBOARD_API_TOKEN_CMD at?

Comment on lines +1 to +3
export SIMBOARD_WORKDIR="${HOME}/Ops/simboard"
export SIMBOARD_REPODIR="${HOME}/gitrepo/simboard/backend"
export SIMBOARD_ENV_CMD="source $HOME/envs/test_simboard/bin/activate"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to keep SimBoard operational files in a shared project-owned directory rather than an individual user’s home directory? That may help avoid ownership, permission, continuity, and offboarding issues, while still leaving user home directories for personal overrides, credentials (E.g., SIMBOARD_API_TOKEN), debugging files, etc.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

Note, I have two runs on Chrysalis with the latest commit of this branch using chrysalis.sh. Both look like they are working.

Staging Dir -- completed in 940 seconds

2026-08-05 12:18:50,791 [INFO]: archive_ingestor_core.py(_log_event:839) >> event=dry_run_summary_counts mode=dry-run discovered_cases=932 submission_qualified_cases=932 selected_submission_cases=932 execution_dirs_scanned=4040 execution_dirs_accepted=1910 skipped_incomplete=2130 skipped_invalid=0 skipped_transient=0
2026-08-05 12:18:50,791 [INFO]: archive_ingestor_core.py(_log_event:839) >> event=dry_run_summary_candidates accepted_execution_ids=1910 rejected_existing_execution_ids=0 rejected_incomplete_execution_ids=2130 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=0 candidate_logs_emitted=20 candidate_logs_suppressed=912
2026-08-05 12:18:50,793 [INFO]: archive_ingestor_core.py(_log_event:839) >> event=run_finished mode=dry-run scan_mode=staging exit_code=0 duration_seconds=940.386

Archive Dir -- In progress (currently at 671 seconds):

2026-08-05 12:19:49,967 [INFO]: archive_ingestor_core.py(_log_event:839) >> event=archive_scan_progress scan_mode=archive archive_root=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF current_dir=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-08/performance_archive_chrysalis_e3sm_2025_08_07_00_46_50/ac.dalei.hao/20250805_SSP245_ZATM_BGC_ne30pg2_f09_oEC60to30v3_FULL_FDBK_40years_restart_9_13months/832641.250806-004304/CaseDocs.832641.250806-004304 directories_visited=12250 discovered_cases=1211 execution_dirs_scanned=3105 execution_dirs_accepted=1758 skipped_transient=0 rejected_existing_execution_ids=0 duration_seconds=671.614

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@TonyB9000 I pushed a few commits that improves the usability of dry-run, documents validation operations, and updates the bash wrapper script. Please review when you can and pull the latest commit in your repo (while addressing any merge conflicts).

  • Allow offline ingestion dry runs

    • DRY_RUN=true now runs fully offline for both ingestion runners.
    • No API URL/token required; no API, state, checkpoint, or persistence calls.
    • Added tests and updated docs.
  • Document ingestion validation modes

    • Documents offline dry-run, bounded real ingestion via MAX_CASES_PER_RUN, and normal uncapped ingestion.
    • No numbered DRY_RUN_1 / _2 / _3 modes needed.
  • Unify site ingestion launcher

    • Renames simboard_pa_collection.sh to site_ingestion_launcher.sh.
    • Makes launcher config-driven: selects site config, runner, and archive lower bound.
    • Adds site-config environment variables:
      • SIMBOARD_INGESTOR_MODULE: Python module launched for site; lets one generic launcher support NERSC path ingestion and remote HPC-upload ingestion.
      • SIMBOARD_ENV_FILE: protected shell environment file sourced only for real ingestion; keeps host-specific environment setup out of launcher and avoids it for offline dry-runs.
      • SIMBOARD_API_TOKEN_FILE: protected file that exports SIMBOARD_API_TOKEN, sourced only for real ingestion; avoids storing token in committed config or loading it for dry-runs.
      • SIMBOARD_DEFAULT_ARCHIVE_YEAR_START: site default archive lower bound; preserves site-specific archive scope while allowing a caller to override it with ARCHIVE_YEAR_START.
    • Removes superseded nersc.sh and chrysalis.sh.
    • Updates configs, cron example, docs, and tests.
    • May overlap with your shared-directory / SIMBOARD_ROOT work.
      • Please check launcher/config path variables (SIMBOARD_REPODIR, SIMBOARD_WORKDIR) and working-directory assumptions during integration.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder I'll pull down the changes and review.

@TonyB9000

TonyB9000 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder I've at least confimed: When you simply cron a script to run (giving the full path), it ALWAYS treats the crontab user's home directory as the (initial) working directory. So, one must issue a "cd location" and "&& command" in order for the command to have "location" as the initial working directory.

As I would like our nascent "simboard/operations" to be that base of operations, I will cron with

    export SIMBOARD_ROOT=/lcrc/group/e3sm2/simboard && cd ${SIMBOARD_ROOT}/operations && ./site_ingestion_launcher.sh chrysalis staging

More testing tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: devops DevOps task (e.g., DOE site ingestion, CI/CD, Docker)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DevOps]: Implement remote data collection job at Chrysalis for ingestion

2 participants