Skip to content

Repository files navigation

IRI API reference implementation in Python 3

Python reference implementation of the IRI facility API, standardizing endpoints, parameters, and return values across DOE computational facilities.

See it live:

Prerequisites

Start the dev server

make

This will set up a virtual environment, install the dependencies and run the fastApi dev server. Code changes will automatically reload in the server. To exit, press ctrl+C. This will stop the server and deactivate the virtual environment.

On Windows, see the Makefile and run the commands manually.

Visit the dev server

http://127.0.0.1:8000/

Customizing the API for your facility

The reference implementation is meant to be customized for your facility's IRI implementation. Running the IRI api unmodified will show only fake, test data. The paragraphs below describe how to customize the business logic and appearance of the API for your facility.

Customizing the business logic for your facility

The IRI API handles the "boilerplate" of setting up the rest API. It delegates to the per-facility business logic via interface definitions. These interfaces are implemented as abstract classes, one per api group (status, account, etc.). Each router directory defines a FacilityAdapter class (eg. the status adapter) that is expected to be implemented by the facility who is exposing an IRI API instance.

The specific implementations can be specified via the IRI_API_ADAPTER_* environment variables. For example the adapter for the status api would be given by setting IRI_API_ADAPTER_status to the full python module and class implementing app.routers.status.facility_adapter.FacilityAdapter. (eg. IRI_API_ADAPTER_status=myfacility.MyFacilityStatusAdapter)

A reference implementation that fakes every facility adapter is provided by the separate iri-facility-api-demo-adapter repo, included here as a git submodule under examples/demo-adapter. make dev installs it and wires it up automatically. This repo itself ships no adapter -- it is a pure framework.

Customizing the API meta-data

You can optionally override the FastAPI metadata, such as name, description, terms_of_service, etc. by providing a valid json object in the IRI_API_PARAMS environment variable.

If using docker (see next section), your dockerfile could extend this reference implementation via a FROM line and add your custom facility adapter code and init parameters in ENV lines.

Environment variables

  • API_URL_ROOT: the base url when constructing links returned by the api (eg.: https://iri.myfacility.com)
  • API_PREFIX: the path prefix where the api is hosted. Defaults to /. (eg.: /api)
  • API_URL: the path to the api itself. Defaults to api/v2.

OpenTelemetry

The API supports OpenTelemetry for distributed tracing and metrics. Traces and metrics can be independently enabled or disabled.

Variable Default Description
OPENTELEMETRY_ENABLED false Master switch. Must be true for any telemetry to be emitted.
OTEL_TRACES_ENABLED true Enable trace export. Only takes effect when OPENTELEMETRY_ENABLED=true.
OTEL_METRICS_ENABLED true Enable metric export. Only takes effect when OPENTELEMETRY_ENABLED=true.
OTLP_ENDPOINT "" gRPC endpoint for the OTLP collector (e.g. http://otel-collector:4317). When empty, telemetry is printed to the console.
OPENTELEMETRY_DEBUG false Sets trace sample rate to 100% (overrides OTEL_SAMPLE_RATE).
OTEL_SAMPLE_RATE 0.2 Trace sampling rate (0.0 to 1.0). Ignored when OPENTELEMETRY_DEBUG=true.
OTEL_METRIC_EXPORT_INTERVAL 60000 Metric export interval in milliseconds.

When metrics are enabled, the FastAPI instrumentor automatically emits standard HTTP server metrics: http.server.active_requests, http.server.duration, and http.server.response.size.

Examples:

# Traces and metrics to an OTLP collector
OPENTELEMETRY_ENABLED=true OTLP_ENDPOINT=http://otel-collector:4317

# Traces only, no metrics
OPENTELEMETRY_ENABLED=true OTEL_METRICS_ENABLED=false

# Metrics only, no traces
OPENTELEMETRY_ENABLED=true OTEL_TRACES_ENABLED=false

# Debug mode: 100% sampling, console output
OPENTELEMETRY_ENABLED=true OPENTELEMETRY_DEBUG=true

Links to data, created by this api, will concatenate these values producing links, eg: https://iri.myfacility.com/my_api_prefix/my_api_url/projects/123

  • IRI_API_PARAMS: as described above, this is a way to customize the API meta-data

  • IRI_API_ADAPTER_*: these values specify the business logic for the per-api-group implementation of a facility_adapter. For example: IRI_API_ADAPTER_status=myfacility.MyFacilityStatusAdapter would load the implementation of the app.routers.status.facility_adapter.FacilityAdapter abstract class to handle the status business logic for your facility.

    The full list of router adapters and the abstract base class each must implement:

    Variable Mounted at Abstract base class your adapter must subclass
    IRI_API_ADAPTER_facility /facility/... app.routers.facility.facility_adapter.FacilityAdapter
    IRI_API_ADAPTER_status /status/... app.routers.status.facility_adapter.FacilityAdapter
    IRI_API_ADAPTER_account /account/... app.routers.account.facility_adapter.FacilityAdapter
    IRI_API_ADAPTER_compute /compute/... app.routers.compute.facility_adapter.FacilityAdapter
    IRI_API_ADAPTER_filesystem /filesystem/... app.routers.filesystem.facility_adapter.FacilityAdapter
    IRI_API_ADAPTER_storage /storage/... app.routers.storage.facility_adapter.FacilityAdapter
    IRI_API_ADAPTER_task /task/... app.routers.task.facility_adapter.FacilityAdapter

    Each value is a module.path.ClassName string. The demo adapter's demo_adapter.combined.DemoAdapter (from the examples/demo-adapter submodule) implements all of them and is what make dev wires up by default. A router whose IRI_API_ADAPTER_* is not set is hidden from the API at startup; if IRI_SHOW_MISSING_ROUTES=true an unconfigured router instead fails fast at startup (the framework has no built-in fallback adapter).

  • IRI_SHOW_MISSING_ROUTES: by default (false), api groups without an IRI_API_ADAPTER_* environment variable are silently hidden, so a facility can expose only the groups it implements. If set to true, an unconfigured group instead makes startup fail fast, surfacing the missing adapter as a configuration error rather than silently dropping the route.

Facility-specific authentication

Required. Every domain whose FacilityAdapter extends AuthenticatedAdapter (account, compute, filesystem, storage, task -- see the base class in app/routers/iri_router.py) requires callers to send Authorization: Bearer <token> on every request; facility and status are public and need no token.

This framework ships no business logic of its own for Authentication methods. Each adapter must implement:

class AuthenticatedAdapter(ABC):
    async def get_current_user(self, api_key: str, client_ip: str | None) -> str:
        """Validate api_key, return the authenticated user's id (or raise)."""

    async def get_user(self, user_id: str, api_key: str, client_ip: str | None) -> User:
        """Look up name/email/etc. for the id returned by get_current_user."""

The demo adapter's DemoAuthMixin shows the minimal shape (checks a static key, returns a fixed user). Real deployments validate against the facility trusted mechanism -- for example, NERSC and ALCF validate a Globus token in get_current_user.

AmSC authentication

Optional, off by default. When enabled, IriRouter.current_user validates an AmSC Keycard bearer token (RIG audience-scopes it to this facility before forwarding it) before falling back to the facility-specific auth path above: JWKS signature/issuer/audience/expiry verification, an optional Ping userinfo freshness check, and mapping the tokens active amsc_project_context claim to a local facility username via a JSON file. See app/amsc_auth.py for the implementation details

Variable Default Description
AMSC_TOKEN_ENABLED false Enabled/Disable AmSC auth. When false, every other AMSC_* variable below is ignored.
AMSC_TOKEN_ISSUER (required when enabled) Expected iss claim (the AmSC Identity Provider, Ping).
AMSC_TOKEN_AUDIENCE (required when enabled) Comma-separated list of accepted aud values -- this facility's RIG-scoped audience identifier(s). AmSC uses full url, like https://api.iri.nersc.gov/. Match is exact: a token is rejected if its aud contains any value outside this list, even if it also contains an accepted one -- a generic AmSC-platform token that merely lists this facility alongside other audiences is not sufficient.
AMSC_TOKEN_SKIP_AUDIENCE_CHECK false Local-dev only -- never set in a real facility deployment. Skips aud verification entirely; AMSC_TOKEN_AUDIENCE becomes optional while this is true. Exists because a local/fake IdP (e.g. RIG's Tier-1 audience-scoped exchange in a sandbox) has no real PingAM issuance path for a facility running on localhost, so it can't mint a token whose aud matches. Signature, issuer, expiry, sub, and amsc_project_context are still enforced.
AMSC_OIDC_DISCOVERY_URL (unset) .well-known/openid-configuration URL used to resolve the JWKS and userinfo endpoints. Either this needs to be provided or the explicit AMSC_JWKS_URL (and AMSC_USERINFO_URL if the userinfo check is enabled) must be set.
AMSC_JWKS_URL (derived from discovery) Explicit JWKS endpoint, overrides the discovery endpoint.
AMSC_TOKEN_ALGORITHMS (derived from discovery) Comma-separated list of accepted JWT signing algorithms. If unset, derived from the discovery output id_token_signing_alg_values_supported. Falls back to RS256,ES256,RS384,RS512,ES384,ES512 if discovery is unset, unreachable, or has nothing usable after filtering.
AMSC_TOKEN_LEEWAY_SECONDS 30 Clock-skew leeway applied to exp/nbf checks.
AMSC_TOKEN_JWKS_CACHE_TTL_SECONDS 3600 How long JWKS keys and the discovery endpoint are cached before refetching.
AMSC_USERINFO_VALIDATION_ENABLED false When true, every AmSC-authenticated request also calls the userinfo endpoint (Ping) with the caller's token to catch revocation that offline JWT validation cannot see. Fail-closed: if Ping is unreachable or rejects the token, the request is denied.
AMSC_USERINFO_URL (derived from discovery) Explicit userinfo endpoint, overrides the discovery endpoint.
AMSC_USERINFO_TIMEOUT_SECONDS 5 Timeout for the discovery endpoint fetch and the userinfo call.
AMSC_PROJECT_MAPPING_FILE (required when enabled) Path to a JSON file mapping each AmSC amsc_project_context this facility has provisioned to a local facility username. See examples/demo-adapter/amsc_project_mapping.json for the format. An amsc_project_context with no entry is a 401, not a silent fallback.

Startup fails fast with a clear error if AMSC_TOKEN_ENABLED=true but required configuration is missing.

The default AuthenticatedAdapter.get_current_user_amsc resolves the mapping file; override possible on facility adapter if needed.

Logging

Logs always go to stdout. Optionally, logs can also be written to a rotating file.

Variable Default Description
LOG_LEVEL DEBUG Logging level for the API and adapters (DEBUG, INFO, WARNING, ERROR, CRITICAL).
IRI_LOG_FILE (none) File path for API logs. When set, logs go to both stdout and this file.
LOG_FILE (none) Fallback file path when IRI_LOG_FILE is not set.
IRI_LOG_ROTATION_DAYS 5 Number of daily rotated log files to retain.
LOG_ROTATION_DAYS 5 Fallback retention when IRI_LOG_ROTATION_DAYS is not set.

For local development, make writes logs to runtime-logs.log by default and keeps 5 daily rotated files. Use make LOG_FILE=/tmp/iri-api.log, make IRI_LOG_FILE=/tmp/iri-api.log, or make LOG_ROTATION_DAYS=10 to override those defaults. You can also put the same variables in local.env.

Idempotency

Compute submit_job and update_job endpoints support an optional Idempotency-Key request header. When provided, the server caches the first successful response for that key and returns it on any subsequent request with the same key and body — without calling the facility adapter again. This makes it safe for clients to retry on timeout without risking duplicate job submissions.

Behaviour

Scenario Response
First request Calls adapter, caches result. Response header: Idempotency-Key-Reply: miss
Retry, same body Returns cached result. Response header: Idempotency-Key-Reply: hit
Retry, different body 422 Unprocessable Entity
Concurrent duplicate (in-flight) 409 Conflict with Retry-After: 2
Adapter raises Lock released; client may retry safely

Backing store

The core library ships no backing store. Configure one with IRI_IDEMPOTENCY_STORE; if it is unset, a request that sends Idempotency-Key returns 501.

The demo adapter package provides reference stores:

Store Configure with Suitable for
In-process dict IRI_IDEMPOTENCY_STORE=demo_adapter.compute.idempotency.InMemoryIdempotencyStore Dev / single-instance
Redis IRI_IDEMPOTENCY_STORE=demo_adapter.compute.idempotency.RedisIdempotencyStore plus REDIS_URL Multi-replica production

For multi-replica deployments, use the Redis store. Run a local Redis instance with make redis.

Environment variables

Variable Default Description
IRI_IDEMPOTENCY_STORE (unset) Dotted path to an idempotency store class. The demo adapter provides in-memory and Redis reference stores.
REDIS_URL (unset) Redis connection URL (e.g. redis://localhost:6379) when using RedisIdempotencyStore.
IDEMPOTENCY_TTL_SECONDS 86400 How long a cached response is retained after a successful call (24 hours).
LOCK_TTL_SECONDS 60 Maximum seconds an in-flight request holds the lock. If the IRI process crashes mid-request, the lock auto-expires after this interval so the next retry is treated as a fresh request. Set higher if your facility's scheduler API is known to be slow.

Quick start (dev)

make redis                          # start Redis container on :6379
# add to local.env:
export IRI_IDEMPOTENCY_STORE=demo_adapter.compute.idempotency.RedisIdempotencyStore
export REDIS_URL=redis://localhost:6379
make                                # start IRI dev server

Docker support

You can either use the docker images created on github.com or build the image yourself.

Use the github docker image

Github is set up to automatically build the latest image and push it to its registry on each commit to the main branch.

For now (until this repo is made public), you will have to authenticate to the github container registry with your github username and Personal Access Token (PAT) as your password:

docker login ghcr.io -u <your username> (For the password, enter your PAT)

Once authenticated, you can now pull:

docker pull ghcr.io/doe-iri/iri-facility-api-python:main

And also run the code with the demo adapter:

docker run -p8000:8000 -e IRI_SHOW_MISSING_ROUTES=true ghcr.io/doe-iri/iri-facility-api-python:main

Visit: http://127.0.0.1:8000/

Build the image yourself

You can build and run the included dockerfile, for example: docker build -t iri . && docker run -p 8000:8000 iri

Using the base docker image

Rather than forking this repo, docker is recommended for running your facility implementation. For example, you could use the following example Dockerfile for your IRI api:

FROM ghcr.io/doe-iri/iri-facility-api-python:main
# or: FROM registry.myfacility.gov/isg/iri/iri:main

# The "myfacility" directory contains the adapters with business logic
# specific to your IRI implementaion.
# Here we copy them into the docker image to a location that will be
# visible to the running app.
COPY ./myfacility /app/myfacility/

# Install additional libraries your implementation needs
RUN pip install additional_libraries

# Customize your image via environment variables
ENV IRI_API_ADAPTER_status="myfacility.status_adapter.StatusAdapter"
ENV IRI_API_ADAPTER_account="myfacility.account_adapter.AccountAdapter"
ENV IRI_API_ADAPTER_compute="myfacility.compute_adapter.ComputeAdapter"
ENV API_PREFIX="/myfacility/"
ENV IRI_API_PARAMS='{ \
    "title": "Facility XYZ implementation of the IRI api", \
    "terms_of_service": "https://myfacility.gov/aup", \
    "docs_url": "/", \
    "contact": { \
        "name": "My Facility Contact", \
        "url": "https://myfacility.gov/about/contact-us/" \
    } \
}'

Next steps

  • Learn more about fastapi, including how to run it in production
  • Instead of the simulated state, keep real data in a database
  • Specify the monitoring endpoint by setting the OpenTelemetry env vars
  • Add additional routers for other API-s
  • Add authenticated API-s via an OAuth2 integration

About

The IRI Facility API reference implementation (Python)

Resources

Stars

25 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages