Skip to content
Open

V2 #82

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ See it live:

- NERSC instance:
- API docs: https://api.iri.nersc.gov
- API requests: https://api.iri.nersc.gov/api/v1/
- API requests: https://api.iri.nersc.gov/api/v2/
- ALCF instance:
- API docs: https://api.alcf.anl.gov
- API requests: https://api.alcf.anl.gov/api/v1/
- API requests: https://api.alcf.anl.gov/api/v2/
- ESnet instance: https://iri-dev.ppg.es.net

## Prerequisites
Expand Down Expand Up @@ -50,7 +50,7 @@ If using docker (see next section), your dockerfile could extend this reference

- `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/v1`.
- `API_URL`: the path to the api itself. Defaults to `api/v2`.
- `OPENTELEMETRY_ENABLED`: Enables OpenTelemetry. If enabled, the application will use OpenTelemetry SDKs and emit traces, metrics, and logs. Default to false
- `OTLP_ENDPOINT`: OpenTelemetry Protocol collector endpoint to export telemetry data. If empty or not set, telemetry data is logged locally to log file. Default: ""

Expand Down
2 changes: 1 addition & 1 deletion VALIDATION.MD
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ On every pull request or push to `main` branch, Github Actions run the following
3. Waits for `/openapi.json` to become available on localhost:8000.
4. Runs Schemathesis validation twice:
- Against Facilities API’s OpenAPI spec. (http://localhost:8000/openapi.json)
- Against the official IRI Facility API OpenAPI spec. (https://github.com/doe-iri/iri-facility-api-docs/blob/main/specification/openapi/openapi_iri_facility_api_v1.json)
- Against the official IRI Facility API OpenAPI spec. (https://github.com/doe-iri/iri-facility-api-docs/blob/main/specification/openapi/openapi_iri_facility_api_v2.json)
5. Fails the workflow if either validation fails.
6. Saves Schemathesis HTML/XML reports as artifacts (or saves it locally when run with `act`).
7. Dumps API container logs and do clean up to stop container.
Expand Down
17 changes: 9 additions & 8 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

logger = get_stream_logger(__name__, LOG_LEVEL)

API_VERSION = "1.0.0"
API_VERSION = "2.0.0"
API_VERSION_SHORT = "v2"

API_URL_ROOT = os.environ.get("API_URL_ROOT", "https://api.iri.nersc.gov")
API_PREFIX = os.environ.get("API_PREFIX", "/")
API_URL = os.environ.get("API_URL", f"api/{API_VERSION_SHORT}")

# lines in the description can't have indentation (markup format)
description = """
Expand All @@ -19,12 +24,13 @@
"""

# version is the openapi.json spec version
# /api/v1 mount point means it's the latest backward-compatible url
# /api/v2 mount point means it's the latest backward-compatible url
API_CONFIG = {
"title": "IRI Facility API reference implementation",
"description": description,
"version": API_VERSION,
"docs_url": "/",
"docs_url": f"/{API_URL}",
"openapi_url": f"/{API_URL}/openapi.json",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had to make docs_url and openapi_url unique so we could serve them at the same time. For some reason path rewriting in the ingress rules did not work for me.

"contact": {"name": "Facility API contact", "url": "https://www.somefacility.gov/about/contact-us/"},
"terms_of_service": "https://www.somefacility.gov/terms-of-service",
}
Expand All @@ -35,11 +41,6 @@
except Exception as exc:
logger.error(f"Error parsing IRI_API_PARAMS: {exc}")


API_URL_ROOT = os.environ.get("API_URL_ROOT", "https://api.iri.nersc.gov")
API_PREFIX = os.environ.get("API_PREFIX", "/")
API_URL = os.environ.get("API_URL", "api/v1")

OPENTELEMETRY_ENABLED = os.environ.get("OPENTELEMETRY_ENABLED", "false").lower() == "true"
OPENTELEMETRY_DEBUG = os.environ.get("OPENTELEMETRY_DEBUG", "false").lower() == "true"
OTLP_ENDPOINT = os.environ.get("OTLP_ENDPOINT", "")
Expand Down
26 changes: 14 additions & 12 deletions app/routers/error_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

from .. import config


class Problem(BaseModel):
model_config = ConfigDict(extra="allow", json_schema_extra={"description": 'Error structure for REST interface based on RFC 9457, "Problem Details for HTTP APIs."'})
type: str = Field(..., description="A URI reference that identifies the problem type.", example="https://example.com/notFound", json_schema_extra={"format": "uri", "default": "about:blank"})
status: int = Field(..., ge=100, le=599, description="The HTTP status code for this occurrence.", example=404)
title: str|None = Field(default=None, description="Short human-readable summary.", example="Not Found")
detail: str|None = Field(default=None, description="Human-readable explanation.", example="Descriptive text.")
instance: str = Field(..., description="A URI reference identifying this occurrence.", example="http://localhost/api/v1/resource/123")
instance: str = Field(..., description="A URI reference identifying this occurrence.", example=f"http://localhost/{config.API_URL}/resource/123")


def get_url_base(request: Request) -> str:
Expand Down Expand Up @@ -222,50 +224,50 @@ async def global_handler(request: Request, exc: Exception):
"title": "Invalid parameter",
"status": 400,
"detail": "modified_since must be in ISO 8601 format.",
"instance": "/api/v1/status/resources?modified_since=BADVALUE",
"instance": f"/{config.API_URL}/status/resources?modified_since=BADVALUE",
"invalid_params": [{"name": "modified_since", "reason": "Invalid datetime format"}],
}

EXAMPLE_401 = {"type": "https://iri.example.com/problems/unauthorized", "title": "Unauthorized", "status": 401, "detail": "Bearer token is missing or invalid.", "instance": "/api/v1/status/resources"}
EXAMPLE_401 = {"type": "https://iri.example.com/problems/unauthorized", "title": "Unauthorized", "status": 401, "detail": "Bearer token is missing or invalid.", "instance": f"/{config.API_URL}/status/resources"}

EXAMPLE_403 = {
"type": "https://iri.example.com/problems/forbidden",
"title": "Forbidden",
"status": 403,
"detail": "Caller is authenticated but lacks required role.",
"instance": "/api/v1/status/resources",
"instance": f"/{config.API_URL}/status/resources",
}

EXAMPLE_404 = {
"type": "https://iri.example.com/problems/not-found",
"title": "Not Found",
"status": 404,
"detail": "The resource ID 'abc123' does not exist.",
"instance": "/api/v1/status/resources/abc123",
"instance": f"/{config.API_URL}/status/resources/abc123",
}

EXAMPLE_405 = {
"type": "https://iri.example.com/problems/method-not-allowed",
"title": "Method Not Allowed",
"status": 405,
"detail": "HTTP method TRACE is not allowed for this endpoint.",
"instance": "/api/v1/status/resources",
"instance": f"/{config.API_URL}/status/resources",
}

EXAMPLE_409 = {
"type": "https://iri.example.com/problems/conflict",
"title": "Conflict",
"status": 409,
"detail": "A job with this ID already exists.",
"instance": "/api/v1/compute/job/perlmutter/123",
"instance": f"/{config.API_URL}/compute/job/perlmutter/123",
}

EXAMPLE_422 = {
"type": "https://iri.example.com/problems/unprocessable-entity",
"title": "Unprocessable Entity",
"status": 422,
"detail": "The PSIJ JobSpec is syntactically correct but invalid.",
"instance": "/api/v1/compute/job/perlmutter",
"instance": f"/{config.API_URL}/compute/job/perlmutter",
"invalid_params": [{"name": "job_spec.executable", "reason": "Executable must be provided"}],
}

Expand All @@ -274,31 +276,31 @@ async def global_handler(request: Request, exc: Exception):
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred.",
"instance": "/api/v1/status/resources",
"instance": f"/{config.API_URL}/status/resources",
}

EXAMPLE_501 = {
"type": "https://iri.example.com/problems/not-implemented",
"title": "Not Implemented",
"status": 501,
"detail": "This functionality is not implemented.",
"instance": "/api/v1/status/resources",
"instance": f"/{config.API_URL}/status/resources",
}

EXAMPLE_503 = {
"type": "https://iri.example.com/problems/service-unavailable",
"title": "Service Unavailable",
"status": 503,
"detail": "The service is temporarily unavailable.",
"instance": "/api/v1/status/resources",
"instance": f"/{config.API_URL}/status/resources",
}

EXAMPLE_504 = {
"type": "https://iri.example.com/problems/gateway-timeout",
"title": "Gateway Timeout",
"status": 504,
"detail": "The server did not receive a timely response.",
"instance": "/api/v1/status/resources",
"instance": f"/{config.API_URL}/status/resources",
}

DEFAULT_RESPONSES = {
Expand Down
6 changes: 3 additions & 3 deletions test/test_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
# CONFIG — EDIT THESE AS NEEDED
# =========================

BASE_URL = "http://localhost:8000/api/v1"
#BASE_URL = "https://api.iri.nersc.gov/api/v1"
#BASE_URL = "https://iri-dev.ppg.es.net/api/v1"
BASE_URL = "http://localhost:8000/api/v2"
#BASE_URL = "https://api.iri.nersc.gov/api/v2"
#BASE_URL = "https://iri-dev.ppg.es.net/api/v2"
TOKEN = os.environ.get("IRI_API_TOKEN", "12345")
# =========================
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"}
Expand Down
Loading