Skip to content

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

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

[DevOps]: Add Chrysalis ingestion wrapper and deploy #169
tomvothecoder wants to merge 20 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.
  • Perform a full-run in progress
  • Follow-up Work (Open new GitHub issue)
    • 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.

F

@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.

@tomvothecoder
tomvothecoder force-pushed the feature/154-ingestion-sites branch from 1cef6f7 to 3bc4d45 Compare August 25, 2026 18:06
@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

My reply to your comment:

#269 (comment)

It occurs to me there are three objects of note: The launch script, the site (chrysalis).config, and the crontab command line - the last of which is not really captured anywhere. I submit it here as a placeholder:

# 5 20 35 50 * * * * export SIMBOARD_ROOT=/lcrc/group/e3sm2/simboard && cd ${SIMBOARD_ROOT}/operations && ${SIMBOARD_ROOT}/repository/simboard/backend/app/scripts/ingestion/sites/site_ingestion_launcher.sh chrysalis staging
# 10 15 * * * export SIMBOARD_ROOT=/lcrc/group/e3sm2/simboard && cd ${SIMBOARD_ROOT}/operations && ${SIMBOARD_ROOT}/repository/simboard/backend/app/scripts/ingestion/sites/site_ingestion_launcher.sh chrysalis archive

Can you update the existing nersc.crontab.example? Afterwards, can you document (very simply, step-by-step) the setup process for all of the operations scripts and crontab here? I'll use AI to review.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

Also make sure to pull the latest commit. I fixed rebase conflicts.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder I pulled the latest commits to your fork/branch, and confirmed that all of my changes are included.

As far as "the setup process for all of the operations scripts and crontab here", I'm not sure if you want me to update the README, or to establish a separate "Setup for Scripts and Crontab" document. I will assume the latter.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder I added a tentative markdown doc to the "sites" directory, detailing setup instructions. It now occurs to me that the site-config file has lines that are NOT site-specific, and should be maintained in the launch script itself. At least one of these (the module-path to the site launch script) should be eliminated from the site config and coded into the script.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder I pulled the latest commits to your fork/branch, and confirmed that all of my changes are included.

As far as "the setup process for all of the operations scripts and crontab here", I'm not sure if you want me to update the README, or to establish a separate "Setup for Scripts and Crontab" document. I will assume the latter.

@tomvothecoder I added a tentative markdown doc to the "sites" directory, detailing setup instructions. It now occurs to me that the site-config file has lines that are NOT site-specific, and should be maintained in the launch script itself. At least one of these (the module-path to the site launch script) should be eliminated from the site config and coded into the script.

Actually keeping that information in the README makes sense. We can update the top-level documentation (the one I originally linked) to that README instead.

Comment on lines +22 to +27
# I DON'T KNOW ABOUT THESE
# Diagnostics provenance scan: start dry-run, inspect logs, then set DRY_RUN=false.
# 20 * * * * cd ${REPO_DIR} && ${REPO_DIR}/backend/app/scripts/ingestion/sites/nersc-diagnostics-scanner.sh >> ${REPO_DIR}/backend/app/scripts/ingestion/sites/nersc-diagnostics-scanner.log 2>&1

# Chrysalis diagnostics provenance scan; use its local checkout path for REPO_DIR.
# 25 * * * * cd ${REPO_DIR} && MACHINE_NAME=chrysalis ${REPO_DIR}/backend/app/scripts/ingestion/sites/lcrc-diagnostics-scanner.sh >> ${REPO_DIR}/backend/app/scripts/ingestion/sites/lcrc-diagnostics-scanner.log 2>&1

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.

This is a separate cronjob that I implemented in #293. I think it should be a single line rather than two and renamed to diagnostics-scanner.sh. I'll fix this in another pull request.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder I was testing last night and this morning, and failing because my freshly-cloned simboard repo no longer had a "backend/.venv". The site launch script simply (and silently) fails at that point because the original test exits when ".venv" is absent, before it gets a chance to test for "-x executable". I just pushed an update to correct this.

I plan to run a "MAX_CASES_PER_RUN=1" with DRY_RUN=false. We should have results in time for today's sync.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder The configuration output (job is still running):

2026-08-26 12:12:08,344 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_started mode=ingest scan_mode=staging archive_root=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive
2026-08-26 12:12:08,345 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=startup_configuration_begin
2026-08-26 12:12:08,345 [INFO]: archive_ingestor_core.py(_log_event:893) >> 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-26 12:12:08,345 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=startup_configuration_paths scan_mode=staging archive_root=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive archive_year_start=null archive_year_end=null
2026-08-26 12:12:08,345 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=startup_configuration_runtime machine_name=chrysalis dry_run=false dry_run_use_remote_state=true max_cases_per_run=1 max_attempts=3 request_timeout_seconds=60
2026-08-26 12:12:08,345 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=startup_configuration_auth has_api_token=true
2026-08-26 12:12:08,345 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=startup_configuration_end

@TonyB9000

TonyB9000 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder and the tail of the output:

2026-08-26 12:26:16,189 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_counts mode=ingest scanned_cases=942 submission_qualified_cases=942 selected_submission_cases=1 execution_dirs_scanned=4064 execution_dirs_accepted=1921 skipped_incomplete=2143 skipped_invalid=0 skipped_transient=0
2026-08-26 12:26:16,189 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_outcomes success_count=1 failure_count=0 accepted_execution_ids=1 rejected_existing_execution_ids=0 rejected_incomplete_execution_ids=2143 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=1920
2026-08-26 12:26:16,191 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=staging exit_code=0 duration_seconds=847.846

(I have a hard time distinguishing at what point in the output we have transitioned beyond "dry_run" to packaging and transmission. These phases should perhaps be made more clearly distinguished).

The full output log is available for examination here:

/lcrc/group/e3sm2/simboard/operations/SBCS-20260826_171201.log

@tomvothecoder

tomvothecoder commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder I was testing last night and this morning, and failing because my freshly-cloned simboard repo no longer had a "backend/.venv". The site launch script simply (and silently) fails at that point because the original test exits when ".venv" is absent, before it gets a chance to test for "-x executable". I just pushed an update to correct this.

I plan to run a "MAX_CASES_PER_RUN=1" with DRY_RUN=false. We should have results in time for today's sync.

I think the site launch script should have a check to see if the backend/.venv dir exists, since you delete the simboard repo. I would suggest not deleting entire repos and cloning from scratch, but rather keeping them in sync with git pull, git rebase, etc. as that's the typical git workflow.

The .venv is intentionally saved within the backend dir to co-locate the environment next to the related code. You also don't need to worry about dependency drift within the .venv because there is a version controlled uv.lock file that pins exact dependencies (which conda should also have for reproducible, collaborative development).

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder "suggest not deleting entire repos ". Yes, I understand. Still, some user may do this, and the "existence" test should not fail to output a message in that case. See you soon!

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder and the tail of the output:

2026-08-26 12:26:16,189 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_counts mode=ingest scanned_cases=942 submission_qualified_cases=942 selected_submission_cases=1 execution_dirs_scanned=4064 execution_dirs_accepted=1921 skipped_incomplete=2143 skipped_invalid=0 skipped_transient=0
2026-08-26 12:26:16,189 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_outcomes success_count=1 failure_count=0 accepted_execution_ids=1 rejected_existing_execution_ids=0 rejected_incomplete_execution_ids=2143 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=1920
2026-08-26 12:26:16,191 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=staging exit_code=0 duration_seconds=847.846

(I have a hard time distinguishing at what point in the output we have transitioned beyond "dry_run" to packaging and transmission. These phases should perhaps be made more clearly distinguished).

The full output log is available for examination here:

/lcrc/group/e3sm2/simboard/operations/SBCS-20260826_171201.log

It looks like your single case upload worked: https://simboard-dev.e3sm.org/cases/79a4e11e-9d09-4c36-af08-61e07634d051

@tomvothecoder

tomvothecoder commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Replying to your comment quoted below from another GitHub issue.

@tomvothecoder I have had the chrysalis collection running continuously for the past 20 hours, launching every 15 minutes.

In the listing below, the first two runs were "MAX=1" and can be ignored. The next launch (MAX=unlimited) took just over two hours (log size 3996606) and you can see that every 15 minutes cron would try to start up collection and abort with SKIP launch simboard collection, lock already held, pid 3630681

-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2725257 Aug 26 12:26 SBCS-20260826_171201.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2362408 Aug 26 13:14 SBCS-20260826_181201.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 14:05 SBCS-20260826_190504.log    (skipping)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 14:20 SBCS-20260826_192002.log    (skipping)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 14:35 SBCS-20260826_193502.log    (skipping)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 14:50 SBCS-20260826_195003.log    (skipping)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 15:05 SBCS-20260826_200503.log    (skipping)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 15:20 SBCS-20260826_202004.log    (skipping)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 15:35 SBCS-20260826_203502.log    (skipping)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 15:50 SBCS-20260826_205001.log    (skipping)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 3996606 Aug 26 16:01 SBCS-20260826_185001.log  (finally completed, et 7883.398)
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 16:20 SBCS-20260826_212001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2389245 Aug 26 16:24 SBCS-20260826_210501.log

Here are the last lines of the 2 hr run. I am unsure how to interpret these numbers:

2026-08-26 16:01:32,774 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_completed accepted_execution_ids=1921 deferred_execution_ids=0 execution_dirs_accepted=1921 execution_dirs_scanned=1 failure_count=11 rejected_existing_execution_ids=2 rejected_incomplete_execution_ids=2144 rejected_invalid_execution_ids=0 scanned_cases=942 selected_submission_cases=942 skipped_incomplete=2144 skipped_invalid=0 skipped_transient=0 submission_qualified_cases=942 success_count=931 transient_execution_ids=0
2026-08-26 16:01:32,774 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_counts mode=ingest scanned_cases=942 submission_qualified_cases=942 selected_submission_cases=942 execution_dirs_scanned=1 execution_dirs_accepted=1921 skipped_incomplete=2144 skipped_invalid=0 skipped_transient=0
2026-08-26 16:01:32,774 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_outcomes success_count=931 failure_count=11 accepted_execution_ids=1921 rejected_existing_execution_ids=2 rejected_incomplete_execution_ids=2144 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=0
2026-08-26 16:01:32,776 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=staging exit_code=1 duration_seconds=7883.398

I am thinking of issuing n "archive" run - but wanted feedback on this first, in case something is amiss.

Originally posted by @TonyB9000 in #281 (comment)

The run completed most of the expected work, but 11 case submissions failed and still need investigation.

The field definitions are documented in the SimBoard Runner counter and log field terms.

Log entry Value Meaning
scanned_cases 942 Case directories examined.
selected_submission_cases 942 All eligible cases selected.
success_count 931 Upload requests with a successful HTTP response.
failure_count 11 Request-level submission failures.
accepted_execution_ids 1,921 New valid execution IDs selected.
rejected_existing_execution_ids 2 Already processed executions skipped.
rejected_incomplete_execution_ids 2,144 Incomplete executions skipped.
rejected_invalid_execution_ids 0 No invalid executions.
transient_execution_ids 0 No temporary filesystem failures.
deferred_execution_ids 0 No executions deferred.

Chrysalis catalog check

The website and database both show 791 Chrysalis cases.

Database check Result
Cases before the general run 39
Created during the general run 749
Created afterward 3
Current Chrysalis cases 791
Upload requests in the SQL window 934
Distinct Cases receiving executions 754
Executions created 1,516
Duplicate executions 0
Database ingestions with status=failed 31
Execution IDs in those failed ingestions 91

The 39 pre-existing cases include 37 created on August 24, consistent with the v3 ingestion, and 2 created before the general run on August 26.

The 934 records are upload requests, not Case records; multiple uploads can add executions to the same Case. The SQL window is not an exact run-level match: it contains three more records than the runner’s 931 successful HTTP submissions.

One item remains to reconcile:

1,921 selected execution IDs
− 1,509 created by this runner invocation
−    91 database-recorded ingestion failures
−   321 request-level failed submissions
=     0

Next steps

  1. Review the 11 case_ingestion_failed entries and their status_code, error, and archive_bytes.
  2. Use that result to reconcile the remaining 321 execution IDs and decide whether to proceed with the archive run.
SQL query: Chrysalis catalog reconciliation
WITH run_parameters AS (
    SELECT
        (TIMESTAMP '2026-08-26 16:01:32.774'
            AT TIME ZONE 'America/Chicago')
            - INTERVAL '7883.398 seconds' AS started_at,
        TIMESTAMP '2026-08-26 16:01:32.774'
            AT TIME ZONE 'America/Chicago' AS finished_at
),
chrysalis AS (
    SELECT id FROM machines WHERE LOWER(name) = 'chrysalis'
),
case_counts AS (
    SELECT
        COUNT(*) AS chrysalis_cases_currently,
        COUNT(*) FILTER (WHERE c.created_at < p.started_at)
            AS cases_existing_before_run,
        COUNT(*) FILTER (
            WHERE c.created_at >= p.started_at
              AND c.created_at < p.finished_at
        ) AS cases_created_during_run,
        COUNT(*) FILTER (WHERE c.created_at >= p.finished_at)
            AS cases_created_after_run,
        COUNT(*) FILTER (
            WHERE c.created_at < p.started_at
              AND (c.created_at AT TIME ZONE 'America/Chicago')::date
                  = DATE '2026-08-24'
        ) AS cases_created_august_24,
        COUNT(*) FILTER (
            WHERE c.created_at < p.started_at
              AND (c.created_at AT TIME ZONE 'America/Chicago')::date
                  = DATE '2026-08-26'
        ) AS cases_created_august_26_before_run
    FROM cases c
    JOIN chrysalis m ON m.id = c.machine_id
    CROSS JOIN run_parameters p
),
run_ingestions AS (
    SELECT i.*
    FROM ingestions i
    JOIN chrysalis m ON m.id = i.machine_id
    CROSS JOIN run_parameters p
    WHERE i.source_type = 'hpc_upload'
      AND i.created_at >= p.started_at
      AND i.created_at < p.finished_at
),
ingestion_counts AS (
    SELECT
        COUNT(*) AS ingestion_records,
        COUNT(DISTINCT source_reference) AS uploaded_case_paths,
        COALESCE(SUM(created_count), 0) AS executions_created,
        COALESCE(SUM(duplicate_count), 0) AS executions_skipped_as_duplicates,
        COALESCE(SUM(error_count), 0) AS ingestion_errors,
        COUNT(*) FILTER (WHERE status = 'failed') AS failed_ingestion_records,
        COALESCE(SUM(
            CASE WHEN status = 'failed'
            THEN COALESCE(jsonb_array_length(processed_execution_ids), 0)
            ELSE 0 END
        ), 0) AS failed_selected_execution_ids
    FROM run_ingestions
),
execution_counts AS (
    SELECT COUNT(DISTINCT e.case_id) AS chrysalis_cases_represented
    FROM executions e
    JOIN run_ingestions i ON i.id = e.ingestion_id
)
SELECT cc.*, ic.*, ec.*
FROM case_counts cc
CROSS JOIN ingestion_counts ic
CROSS JOIN execution_counts ec;

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@TonyB9000 Can you provide the log file, either attached here or the path on LCRC?

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder The paths to each of these log files is [SIMBOARD_ROOT/operations/]. The one BIG logfile is

/lcrc/group/e3sm2/simboard/operations/SBCS-20260826_185001.log

(Note: I have temporarily suspended all cronjobs). If I list the contents of operations by last modification date (ls -lrt), you can see that after a while, it takes about 20 minutes to complete a "nothing to add" run, so we see alternation between large and very small logfiles (small being aborted runs). But after a while I see several large logs listed in sequence - not sure why.

Output of ls -lrt:

-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2725257 Aug 26 12:26 SBCS-20260826_171201.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2362408 Aug 26 13:14 SBCS-20260826_181201.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 14:05 SBCS-20260826_190504.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 14:20 SBCS-20260826_192002.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 14:35 SBCS-20260826_193502.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 14:50 SBCS-20260826_195003.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 15:05 SBCS-20260826_200503.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 15:20 SBCS-20260826_202004.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 15:35 SBCS-20260826_203502.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 15:50 SBCS-20260826_205001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 3996606 Aug 26 16:01 SBCS-20260826_185001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 16:20 SBCS-20260826_212001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2389245 Aug 26 16:24 SBCS-20260826_210501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 16:50 SBCS-20260826_215001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2388799 Aug 26 16:53 SBCS-20260826_213501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 17:20 SBCS-20260826_222001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2387650 Aug 26 17:22 SBCS-20260826_220501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 17:50 SBCS-20260826_225001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2387890 Aug 26 17:50 SBCS-20260826_223501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2383842 Aug 26 18:19 SBCS-20260826_230501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2    2497 Aug 26 18:20 SBCS-20260826_232001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2383949 Aug 26 18:49 SBCS-20260826_233502.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 19:05 SBCS-20260827_000501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2388416 Aug 26 19:06 SBCS-20260826_235001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 19:35 SBCS-20260827_003501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2386281 Aug 26 19:35 SBCS-20260827_002001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2385311 Aug 26 20:04 SBCS-20260827_005001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2388441 Aug 26 20:19 SBCS-20260827_010501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     649 Aug 26 20:35 SBCS-20260827_013501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2388264 Aug 26 20:36 SBCS-20260827_012001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     649 Aug 26 21:05 SBCS-20260827_020501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2390206 Aug 26 21:05 SBCS-20260827_015001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2389102 Aug 26 21:33 SBCS-20260827_022002.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     649 Aug 26 21:50 SBCS-20260827_025001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2387911 Aug 26 21:51 SBCS-20260827_023501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     649 Aug 26 22:20 SBCS-20260827_032001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2388463 Aug 26 22:27 SBCS-20260827_030501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     649 Aug 26 22:50 SBCS-20260827_035001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2387046 Aug 26 22:56 SBCS-20260827_033501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 23:20 SBCS-20260827_042001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2396223 Aug 26 23:26 SBCS-20260827_040501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 26 23:50 SBCS-20260827_045001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2388745 Aug 26 23:56 SBCS-20260827_043501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 00:20 SBCS-20260827_052001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2395335 Aug 27 00:35 SBCS-20260827_050502.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 00:50 SBCS-20260827_055002.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2389988 Aug 27 00:57 SBCS-20260827_053501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 01:20 SBCS-20260827_062001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2395606 Aug 27 01:29 SBCS-20260827_060501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 01:50 SBCS-20260827_065001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2396384 Aug 27 01:55 SBCS-20260827_063501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 02:20 SBCS-20260827_072001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2391941 Aug 27 02:21 SBCS-20260827_070501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 02:50 SBCS-20260827_075001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2375487 Aug 27 02:54 SBCS-20260827_073501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 03:20 SBCS-20260827_082001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2375419 Aug 27 03:24 SBCS-20260827_080501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 03:50 SBCS-20260827_085001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2376318 Aug 27 03:55 SBCS-20260827_083501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 04:20 SBCS-20260827_092001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2375329 Aug 27 04:30 SBCS-20260827_090501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     649 Aug 27 04:50 SBCS-20260827_095001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2377062 Aug 27 05:00 SBCS-20260827_093502.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     649 Aug 27 05:20 SBCS-20260827_102001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2376552 Aug 27 05:27 SBCS-20260827_100501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     649 Aug 27 05:50 SBCS-20260827_105001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2375402 Aug 27 05:53 SBCS-20260827_103501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 06:20 SBCS-20260827_112001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2374101 Aug 27 06:21 SBCS-20260827_110501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 06:50 SBCS-20260827_115001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2376977 Aug 27 06:52 SBCS-20260827_113501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 07:20 SBCS-20260827_122001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2375840 Aug 27 07:23 SBCS-20260827_120501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 07:50 SBCS-20260827_125001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2373738 Aug 27 07:51 SBCS-20260827_123501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2371857 Aug 27 08:18 SBCS-20260827_130501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2373758 Aug 27 08:34 SBCS-20260827_132001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2372920 Aug 27 08:49 SBCS-20260827_133501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 09:05 SBCS-20260827_140501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2377209 Aug 27 09:06 SBCS-20260827_135001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 09:35 SBCS-20260827_143501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2376991 Aug 27 09:36 SBCS-20260827_142001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 10:05 SBCS-20260827_150501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2379604 Aug 27 10:14 SBCS-20260827_145002.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 10:35 SBCS-20260827_153501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2383478 Aug 27 10:42 SBCS-20260827_152001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 11:05 SBCS-20260827_160501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2377534 Aug 27 11:08 SBCS-20260827_155001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 11:35 SBCS-20260827_163501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2374836 Aug 27 11:36 SBCS-20260827_162001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 12:05 SBCS-20260827_170501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2377556 Aug 27 12:06 SBCS-20260827_165001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 12:35 SBCS-20260827_173501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2380219 Aug 27 12:37 SBCS-20260827_172001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 13:05 SBCS-20260827_180501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2382996 Aug 27 13:07 SBCS-20260827_175001.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2  110840 Aug 27 13:35 TMPLOG
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2       0 Aug 27 13:35 SBCS.lock
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2     650 Aug 27 13:35 SBCS-20260827_183501.log
-rw-rw-r--+ 1 ac.bartoletti1 E3SM2 2385493 Aug 27 13:35 SBCS-20260827_182001.log

The three "subsequent" additions may have appeared overnight, and been processed by later collection runs.

The 11 "failed" and other such items should be derivable by filtering the log file appropriately, and extracting both CASE and EXEC_ID for each. If that is not easy to do filtering the logs, we need to revise the structure of the log files. I can look for "failed" and see what turns up and find other things to filter upon (e.g. status_code, error, and archive_bytes).

Likewise, we really need the ability to provide (say) a file containing a list of CASE/EXEC_ID pairs that can be used to isolate and ingest (or assess) exactly those runs.

Short of this, I don't know quite how to debug these failed case/execs.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Let me try this again:
This command gets me close to the 11 case/execs:

cat SBCS-20260826_185001.log | cut -c77- | grep fail | grep -v "case_ingestion_request_failed attempt" | grep fail

result:

event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2025_03_30_00_47_18/ac.sfeng1/v2.LR.BGC-LNDATM.FireUpdate attempts=2 status_code=413 error="HTTP 413: <html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2025_04_08_00_47_41/ac.mkelleher/20250401.F2010.ne4_oQU240.dtcl_O1_n0030 attempts=2 status_code=413 error="HTTP 413: <html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2025_04_08_00_47_41/ac.mkelleher/20250401.F2010.ne4_oQU240.dtcl_O1_n0030_31_60 attempts=2 status_code=413 error="HTTP 413: <html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.jwolfe/20251107.v3.HR.piControl-Spinup attempts=3 status_code=null error="URL error: [Errno 104] Connection reset by peer"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.smahajan/v3.LR.ssp370_0261 attempts=3 status_code=503 error="HTTP 503: <html>\r\n<head><title>503 Service Temporarily Unavailable</title></head>\r\n<body>\r\n<center><h1>503 Service Temporarily Unavailable</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251203.BGWCYCL1850.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis attempts=3 status_code=503 error="HTTP 503: <html>\r\n<head><title>503 Service Temporarily Unavailable</title></head>\r\n<body>\r\n<center><h1>503 Service Temporarily Unavailable</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251208.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis attempts=3 status_code=503 error="HTTP 503: <html>\r\n<head><title>503 Service Temporarily Unavailable</title></head>\r\n<body>\r\n<center><h1>503 Service Temporarily Unavailable</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251209.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis4to40.chrysalis attempts=3 status_code=500 error="HTTP 500: {\"detail\":\"(psycopg.errors.UniqueViolation) duplicate key value violates unique constraint \\\"uq_cases_name_machine_id_hpc_username\\\"\\nDETAIL:  Key (name, machine_id, hpc_username)=(20251209.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis4to40.chrysalis, 6a90b924-b3ed-4860-ae30-beaa86d14ac8, ac.sprice) already exists.\\n[SQL: INSERT INTO cases (name, machine_id, hpc_username, case_group, description, key_features, known_issues, notes_markdown, id) VALUES (%(name)s::VARCHAR, %(machine_id)s::UUID, %(hpc_username)s::VARCHAR, %(case_group)s::VARCHAR, %(description)s::VARCHAR, %(key_features)s::VARCHAR, %(known_issues)s::VARCHAR, %(notes_markdown)s::VARCHAR, %(id)s::UUID) RETURNING cases.created_at, cases.updated_at]\\n[parameters: {'name': '20251209.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis4to40.chrysalis', 'machine_id': UUID('6a90b924-b3ed-4860-ae30-beaa86d14ac8'), 'hpc_username': 'ac.sprice', 'case_group': None, 'description': None, 'key_features': None, 'known_issues': None, 'notes_markdown': None, 'id': UUID('d31ed211-d0b8-449e-b7bd-5dfd568180a3')}]\\n(Background on this error at: https://sqlalche.me/e/20/gkpj)\"}"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreading150e300 attempts=1 status_code=413 error="HTTP 413: <html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreading50e100 attempts=3 status_code=413 error="HTTP 413: <html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=case_ingestion_failed case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreadingBaseline attempts=1 status_code=413 error="HTTP 413: <html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
event=run_completed accepted_execution_ids=1921 deferred_execution_ids=0 execution_dirs_accepted=1921 execution_dirs_scanned=1 failure_count=11 rejected_existing_execution_ids=2 rejected_incomplete_execution_ids=2144 rejected_invalid_execution_ids=0 scanned_cases=942 selected_submission_cases=942 skipped_incomplete=2144 skipped_invalid=0 skipped_transient=0 submission_qualified_cases=942 success_count=931 transient_execution_ids=0
event=run_summary_outcomes success_count=931 failure_count=11 accepted_execution_ids=1921 rejected_existing_execution_ids=2 rejected_incomplete_execution_ids=2144 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=0

@TonyB9000

Copy link
Copy Markdown
Collaborator

I'm seeing "413 Request Entity Too Large" and "503 Service Temporarily Unavailable".

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder These 6 seemed to trigger the "too large" error:

/ac.sfeng1/v2.LR.BGC-LNDATM.FireUpdate
/ac.mkelleher/20250401.F2010.ne4_oQU240.dtcl_O1_n0030
/ac.mkelleher/20250401.F2010.ne4_oQU240.dtcl_O1_n0030_31_60
/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreading150e300
/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreading50e100
/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreadingBaseline

and these 3 triggered the "503 service unavailable":

ac.smahajan/v3.LR.ssp370_0261
ac.sprice/20251203.BGWCYCL1850.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis
ac.sprice/20251208.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis

The last two are

ac.jwolfe/20251107.v3.HR.piControl-Spinup attempts=3 status_code=null error="URL error: [Errno 104] Connection reset by peer"
ac.sprice/20251209.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis4to40.chrysalis attempts=3 status_code=500 error="HTTP 500: {\"detail\":\"(psycopg.errors.UniqueViolation) duplicate key value violates unique constraint

@tomvothecoder

tomvothecoder commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

@TonyB9000 Good news, the log contains the information needed to classify all 11 failed submissions and reconcile their selected execution IDs. I will let you know when to invoke another ingestion job manually to see if these get picked up.

Request-level failure breakdown

Total: The 11 failed submissions contained 321 selected execution IDs.

NGINX 413 Request Entity Too Large

Case Selected IDs Archive size
v2.LR.BGC-LNDATM.FireUpdate 41 59.60 MiB
20250401...n0030 1 54.56 MiB
20250401...n0030_31_60 1 56.51 MiB
runoffSpreading150e300 62 161.98 MiB
runoffSpreading50e100 61 160.41 MiB
runoffSpreadingBaseline 62 159.77 MiB

Root cause: All six archives exceed the current 52m upload limit.

URL error: [Errno 104] Connection reset by peer

Case Selected IDs Archive size
20251107.v3.HR.piControl-Spinup 84 300.95 MiB

Root cause: The connection was reset; this archive is also much larger than the current upload limits.

NGINX 503 Service Temporarily Unavailable

Case Selected IDs Archive size
v3.LR.ssp370_0261 2 8.98 MiB
20251203...chrysalis 4 4.78 MiB
20251208...chrysalis 2 0.66 MiB

Root cause: NGINX could not reach a ready backend.

  • Next step: Confirm backend availability, then retry these cases. -- The backend is up, the next ingestion job should pick these up automatically (if not already). Need to verify if it's been ingested.

psycopg.errors.UniqueViolation

Case Selected IDs Archive size
20251209...gis4to40... 1 0.33 MiB

Root cause: Another request created the same Case identity concurrently.

  • Next step: Retry the case; it may now succeed because the Case exists. -- This should be done on the next ingestion job (if not already). Need to verify if it's been ingested.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@TonyB9000 Good news, the log contains the information needed to classify all 11 failed submissions and reconcile their selected execution IDs. I will let you know when to invoke another ingestion job manually to see if these get picked up.

I am ready for you to run another Chrysalis staging ingestion job (if cron is not setup right now). Then we can see if those cases are still failing to upload.

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