Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTX Code Release (PI-3): Enhancements to T-Route so it can be used for OWP's Replace and Route #837

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c22c82a
migrated fastapi wrapper to the troute branch
taddyb Aug 21, 2024
43d4489
Merge pull request #1 from taddyb33/migration
taddyb Aug 24, 2024
c7cc896
ensuring this dir is here
taddyb Aug 24, 2024
a2c826f
added restart files to remove dry conditions for RFC routing
taddyb Aug 24, 2024
4eba8a9
Merge branch 'development' of https://github.com/taddyb33/t-route-dev…
taddyb Aug 24, 2024
60f2c7c
Merge pull request #2 from taddyb33/migration
taddyb Aug 24, 2024
7306c68
Added docs for t-route api release
taddyb Sep 3, 2024
60986b8
Merge branch 'development' of https://github.com/taddyb33/t-route-dev…
taddyb Sep 3, 2024
510386e
linted code?
taddyb Sep 3, 2024
3dfb149
Merge pull request #3 from taddyb33/migration
taddyb Sep 3, 2024
0a6f481
added test files
taddyb Sep 3, 2024
627d57f
Merge pull request #4 from taddyb33/test_docs
taddyb Sep 3, 2024
2cf4c3f
added author name in app/main.py, added specifics to testing README f…
taddyb Sep 6, 2024
81fe066
changed port from 8004 to 8000
taddyb Sep 6, 2024
d0f8575
added the lower colorado forcing and domain files into the api test
taddyb Sep 6, 2024
35e4b6c
Merge branch 'NOAA-OWP:master' into development
taddyb Sep 9, 2024
ffb9f96
if the folder doesn't exist
AminTorabi-NOAA Aug 30, 2024
b28544f
handling edge cases
AminTorabi-NOAA Sep 5, 2024
2ecd279
Update readme.md: removed obsolete no-e option (#843)
JurgenZach-NOAA Sep 6, 2024
2deff3c
Speeding up writing output (#841)
AminTorabi-NOAA Sep 6, 2024
51b2253
Merge branch 'rebase' of https://github.com/taddyb33/t-route-dev into…
taddyb Sep 9, 2024
b3960e0
removing lower colorado duplicated data
taddyb Sep 9, 2024
5e1a004
Merge pull request #5 from taddyb33/rebase
taddyb Sep 9, 2024
884c101
Delete data/troute_restart/.gitkeep
taddyb33 Oct 7, 2024
ea96782
completed general PR comments and formatting
taddyb Oct 7, 2024
7e5dc45
Merge branch 'development' of https://github.com/taddyb33/t-route-dev…
taddyb Oct 7, 2024
4fc70fa
added general LowerColorado Test case for API
taddyb Oct 9, 2024
8ca681a
reorganized dockerfiles
taddyb Oct 21, 2024
7c20da5
updated docs, added README
taddyb Nov 5, 2024
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,7 @@ bower_components/
.grunt/
src/vendor/
dist/
data/
output
test_compose.yaml
src/app/.ruff_cache
Copy link
Member

Choose a reason for hiding this comment

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

add new line for posix

26 changes: 26 additions & 0 deletions Dockerfile.troute_api
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM rockylinux:9.2 as rocky-base
RUN yum install -y epel-release
RUN yum install -y netcdf netcdf-fortran netcdf-fortran-devel netcdf-openmpi

RUN yum install -y git cmake python python-devel pip

WORKDIR "/t-route/"
COPY . "/t-route/"

RUN ln -s /usr/lib64/gfortran/modules/netcdf.mod /usr/include/openmpi-x86_64/netcdf.mod
Copy link
Member

Choose a reason for hiding this comment

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

Copy link

Choose a reason for hiding this comment

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

just an FYI, this was taken from docker/Dockerfile.troute https://github.com/NOAA-OWP/t-route/blob/master/docker/Dockerfile.troute#L10C1-L10C88

if I'm making a change to the troute_api container, then the regular container should reflect the same addition


ENV PYTHONPATH=/t-route:$PYTHONPATH
RUN pip install uv==0.2.5
RUN uv venv

ENV VIRTUAL_ENV=/t-route/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

RUN uv pip install --no-cache-dir -r requirements.txt

ENV WITH_EDITABLE=true
Copy link
Member

Choose a reason for hiding this comment

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

I don't think an editable build is really needed here

RUN ./compiler.sh

RUN uv pip install --no-cache-dir -r requirements-app.txt

WORKDIR "/t-route/src/"
Copy link
Member

Choose a reason for hiding this comment

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

posix newline

18 changes: 18 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

if ! docker login --username ${GH_USERNAME} --password ${GH_TOKEN} ghcr.io; then
Copy link
Member

Choose a reason for hiding this comment

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

This needs some additional context, probably in a readme, which describes what is accomplished with this script and what the ghcr.io resource is. Probably also good to describe how to build and use locally without using ghrc.

Copy link

Choose a reason for hiding this comment

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

This script is used to push a built T-Route container to their Github Container Registry. After more thought, I think it's best to delete this script and use CI/CD with github secrets set by maintainers to update docker containers rather than manual builds using ENV credentials.

thoughts?

echo "Error: Failed to login to ghcr.io. Please check your GH_USERNAME and GH_TOKEN env vars"
exit 1
fi

if ! docker build -t ghcr.io/NOAA-OWP/t-route/t-route-api:${TAG} -f Dockerfile.troute_api .; then
echo "Error: Failed to build Docker image. Please check your Dockerfile and build context."
exit 1
fi

if ! docker push ghcr.io/NOAA-OWP/t-route/t-route-api:${TAG}; then
echo "Error: Failed to push Docker image. Please check your TAG env var"
exit 1
fi

echo "Successfully built and pushed Docker image with tag: ${TAG}"
24 changes: 24 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
troute:
build:
context: .
dockerfile: Dockerfile.troute_api
ports:
- "8004:8000"
volumes:
- type: bind
source: ./data/troute_output
Copy link
Member

Choose a reason for hiding this comment

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

It might be worth moving these paths to ENV vars and using a .env file, with corresponding documentation/description of what is expected to be in these mounts when run, or what will be put in them when finsihed.

target: /t-route/output
- type: bind
source: ./data
target: /t-route/data
- type: bind
source: ./src/app/core
target: /t-route/src/app/core
command: sh -c ". /t-route/.venv/bin/activate && uvicorn app.main:app --host 0.0.0.0 --port 8000"
healthcheck:
test: curl --fail -I http://localhost:8000/health || exit 1
interval: 30s
timeout: 5s
retries: 3
start_period: 5s
Copy link
Member

Choose a reason for hiding this comment

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

newline will hereto be referred to as cowbell

More cowbell!

Copy link

Choose a reason for hiding this comment

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

This is a great addition

Empty file added data/troute_restart/.gitkeep
Copy link
Member

Choose a reason for hiding this comment

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

What is the need to keep the empty folder in git?

Copy link

Choose a reason for hiding this comment

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

This was for having a place within the container for restart files to be saved. I'm going to look into generating this inside the container using the dockerfile

Empty file.
88 changes: 88 additions & 0 deletions doc/api/api_docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# T-Route FastAPI

The following doc is meant to explain the T-Route FastAPI implementation using docker compose and shared volumes.

## Why an API?

T-Route is used in many contexts for hydrological river routing:
- NGEN
- Scientific Python
- Replace and Route (RnR)

In the latest PR for RnR, there is a requirement to run T-Route as a service. This service requires an easy way to dynamically create config files, restart flow from Initial Conditions, and run T-Route. To satisfy this requirement, a FastAPI endpoint was created in `/src/app` along with code to dynamically create t-route endpoints.
Copy link
Member

Choose a reason for hiding this comment

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

links/references to RnR here would be useful context


## Why use shared volumes?

Since T-Route is running in a docker container, there has to be a connection between the directories on your machine and the directories within the container. We're sharing the following folders by default:
- `data/rfc_channel_forcings`
- For storing RnR RFC channel domain forcing files (T-Route inputs)
- `data/rfc_geopackage_data`
- For storing HYFeatures gpkg files
- Indexed by the NHD COMID, also called hf_id. Ex: 2930769 is the hf_id for the CAGM7 RFC forecast point.
- `data/troute_restart`
- For storing TRoute Restart files
- `data/troute_output`
- For outputting results from the T-Route container

## Quickstart
1. From the Root directory, run:
```shell
docker compose up
```

This will start the T-Route container and run the API on localhost:8004. To view the API spec and swagger docs, visit localhost:8004/docs

2. Submit a request
Copy link
Member

Choose a reason for hiding this comment

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

would be good to use an example with available data, I'm not sure that this example would run "out of the box" without staging the required inputs. If that is the case, steps to stage that data would be useful.

```shell
curl -X 'GET' \
'http://localhost:8004/api/v1/flow_routing/v4/?lid=CAGM7&feature_id=2930769&hy_id=1074884&initial_start=0&start_time=2024-08-24T00%3A00%3A00&num_forecast_days=5' \
-H 'accept: application/json'
```

This curl command is pinging the flow_routing v4 endpoint `api/v1/flow_routing/v4/` with the following metadata:
```
lid=CAGM7
feature_id=2930769
hy_id=1074884
initial_start=0
start_time=2024-08-24T00:00:00
num_forecast_days=5
```

which informs T-Route which location folder to look at, what feature ID to read a gpkg from, the HY feature_id where flow is starting, the initial start flow for flow restart, start-time of the routing run, and the number of days to forecast.

You can also run the following args from the swagger endpoint:

![alt text](swagger_endpoints.png)

The result for a successful routing is a status 200:
```json
{
"message": "T-Route run successfully",
"lid": "CAGM7",
"feature_id": "2930769"
}
```

and an internal 500 error if there is something wrong.

## Building and pushing to a container registry

the `build.sh` script is created to simplify the process of pushing the T-route image to a docker container registry. Below is the URL for the pushed container:
```shell
ghcr.io/NOAA-OWP/t-route/t-route-api:${TAG}
```
To run that script, the following ENV variables have to be set:
Copy link
Member

Choose a reason for hiding this comment

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

Here is part of what I was looking for when I seen build.sh at the top level. This documentation is pretty disconnected from that, and could still benefit from describing building/using without a registry. I would suggest moving this section to a separate readme more easily associated/found with build.sh

- ${GH_USERNAME}
- your github username
- ${GH_TOKEN}
- your github access token
- ${TAG}
- the version tag
- ex: 0.0.2

If you want to build this off a forked version, change the container registry to your user accounts container registry.

## Testing:

To test this container, follow the steps within `test/api/README.md`
Binary file added doc/api/api_spec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/api/swagger_endpoints.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions requirements-app.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fastapi==0.110.1
uvicorn==0.30.1
pydantic<=1.10
Empty file added src/app/__init__.py
Empty file.
Empty file added src/app/api/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions src/app/api/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Author: Tadd Bindas"""

from fastapi import APIRouter

from app.api.routes import v4_routing
Copy link
Member

Choose a reason for hiding this comment

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

routes and routing -- the cognitive relationship here can be difficult. Two things would help

  1. Descriptive comment that v4_routing is the troute entrypoint used by this api to do hydrologic routing
  2. Consider renaming the routes module to something different, e.g. troute_v4

Both would probably be reasonable.


api_router = APIRouter()
api_router.include_router(
Copy link
Member

Choose a reason for hiding this comment

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

tag with troute v4 Flow routing to be a little more clear?

v4_routing.router, prefix="/flow_routing/v4", tags=["v4 Flow Routing"]
)
Empty file added src/app/api/routes/__init__.py
Empty file.
70 changes: 70 additions & 0 deletions src/app/api/routes/v4_routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Author: Tadd Bindas"""

from typing import Annotated

from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from nwm_routing import main_v04 as t_route

from app.api.services.initialization import (create_initial_start_file,
create_params, edit_yaml)
from app.core import get_settings
from app.core.settings import Settings
from app.schemas import TRouteOuput

router = APIRouter()


@router.get("/", response_model=TRouteOuput)
async def get_gauge_data(
lid: str,
feature_id: str,
hy_id: str,
initial_start: float,
start_time: str,
Copy link
Member

Choose a reason for hiding this comment

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

Use an actual datetime type

num_forecast_days: int,
Copy link
Member

Choose a reason for hiding this comment

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

Consider using a conint (constrainted) here to forecast days run via API to something reasonable. Maybe a few years max to start with?

settings: Annotated[Settings, Depends(get_settings)],
) -> TRouteOuput:
"""An API call for running T-Route

Parameters:
----------
lid: str
The Location of the RFC Point
feature_id: str
The COMID associated with the LID
hy_id: str
The HY_ID associated with the LID
initial_start: float
The initial start for T-Route
start_time: str
The start time for the forecast
num_forecast_days: int
The number of days in the forecast

Returns:
-------
TRouteOutput
A successful T-Route run
"""
base_config = settings.base_config
params = create_params(
lid, feature_id, hy_id, initial_start, start_time, num_forecast_days, settings
)
restart_file = create_initial_start_file(params, settings)
yaml_file_path = edit_yaml(base_config, params, restart_file)
try:
t_route(["-f", yaml_file_path.__str__()])
except Exception as e:
JSONResponse(
status_code=500,
content={"message": e},
)

yaml_file_path.unlink()

return TRouteOuput(
message="T-Route run successfully",
lid=lid,
feature_id=feature_id,
)
Empty file.
Loading