Skip to content
Merged
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
28 changes: 3 additions & 25 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,7 @@ on:
branches: [ main ]

jobs:

# unittests:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
# - name: Replace run only unittest command
# run: |
# sed -i "s+# RUN make test+RUN make unittest+g" docker/Dockerfile
# - name: Unittests of actinia-ogc-api-processes-plugin
# id: docker_build
# uses: docker/build-push-action@v6
# with:
# push: false
# tags: actinia-ogc-api-processes-plugin-tests:alpine
# context: .
# file: docker/Dockerfile
# no-cache: true
# # pull: true

integration-tests:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand All @@ -44,7 +22,7 @@ jobs:
run: docker logs docker-actinia-ogc-api-processes-1
- name: Docker logs actinia-core
run: docker logs docker-actinia-core-1
- name: Run integration test
run: docker exec -t docker-actinia-ogc-api-processes-1 make integrationtest
- name: Run unit and integration tests
run: docker exec -t docker-actinia-ogc-api-processes-1 make test
- name: Stop containers
run: docker compose -f "docker/docker-compose.yml" down
24 changes: 24 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"configurations": [
{
"name": "Docker: Python - Flask",
"type": "docker",
"request": "launch",
"preLaunchTask": "docker-run: debug",
"python": {
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/src/actinia-ogc-api-processes-plugin"
}
],
"projectType": "flask"
},
"dockerServerReadyAction": {
"action": "openExternally",
"pattern": "Running on (https?://\\S+|[0-9]+)",
"uriFormat": "%s://localhost:%s/processes"
}
}
]
}
61 changes: 61 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "docker-build",
"label": "docker-build",
"platform": "python",
"dockerBuild": {
"tag": "actinia-ogc-api-processes-plugin:latest",
"dockerfile": "${workspaceFolder}/docker/Dockerfile",
"context": "${workspaceFolder}"
}
},
{
"type": "docker-run",
"label": "docker-run: debug",
"dependsOn": [
"docker-build"
],
"python": {
"module": "flask",
"args": [
"-e",
"/src/.env",
"run",
"--no-debugger",
"--host",
"0.0.0.0",
"--port",
"4044"
]
},
"dockerRun": {
"containerName": "actinia-ogc-api-processes",
"remove": true,
"network": "actinia-docker_actinia-dev",
"ports": [
{
"containerPort": 4044,
"hostPort": 4044
}
],
"env": {
"PYTHONUNBUFFERED": "1",
"PYTHONDONWRITEBYTECODE": "1",
"FLASK_APP": "actinia_ogc_api_processes_plugin.main",
"FLASK_DEBUG": "1",
"FLASK_ENV": "development"
},
"customOptions": "--ip 172.18.0.12",
"volumes": [
{
"localPath": "${workspaceFolder}",
"containerPath": "/src/actinia-ogc-api-processes-plugin",
"permissions": "rw"
}
]
}
}
]
}
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ docker compose -f docker/docker-compose.yml up
# -- only current plugin (Note: need to start actinia + valkey separately)
docker compose -f docker/docker-compose.yml run --rm --service-ports --entrypoint sh actinia-ogc-api-processes
# within docker
gunicorn -b 0.0.0.0:3003 -w 8 --access-logfile=- -k gthread actinia_ogc_api_processes_plugin.main:flask_app
gunicorn -b 0.0.0.0:4044 -w 8 --access-logfile=- -k gthread actinia_ogc_api_processes_plugin.main:flask_app
```

### DEV setup
```bash
# Uncomment the volume mount of the ogc-api-processes-plugin and additional marked sections of actinia-ogc-api-processes service within docker/docker-compose.yml,
# Note: might also need to set:
# - within config/mount/sample.ini: processing_base_url = http://127.0.0.1:8088/api/v3
# - within src/actinia_ogc_api_processes_plugin/main.py set port: flask_app.run(..., port=3003)
# - within src/actinia_ogc_api_processes_plugin/main.py set port: flask_app.run(..., port=4044)
# then:
docker compose -f docker/docker-compose.yml down
docker compose -f docker/docker-compose.yml up --build
Expand All @@ -29,7 +29,7 @@ docker compose -f docker/docker-compose.yml up --build
docker attach $(docker ps | grep docker-actinia-ogc-api-processes | cut -d " " -f1)

# In another terminal: example call of processes-endpoint:
curl -u actinia-gdi:actinia-gdi -X GET http://localhost:3003/processes
curl -u actinia-gdi:actinia-gdi -X GET http://localhost:4044/processes
```

### Installation hints
Expand Down
5 changes: 1 addition & 4 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ RUN pip3 install --no-cache-dir gunicorn && \

COPY . /src/actinia-ogc-api-processes-plugin/
RUN pip3 install --no-cache-dir -e /src/actinia-ogc-api-processes-plugin/
# TODO
# For tests, when created:
# RUN chmod a+x /src/actinia-ogc-api-processes-plugin/tests.sh

WORKDIR /src/actinia-ogc-api-processes-plugin
# RUN make test

CMD ["gunicorn", "-b", "0.0.0.0:3003", "-w", "8", "--access-logfile=-", "-k", "gthread", "actinia_ogc_api_processes_plugin.main:flask_app"]
CMD ["gunicorn", "-b", "0.0.0.0:4044", "-w", "8", "--access-logfile=-", "-k", "gthread", "actinia_ogc_api_processes_plugin.main:flask_app"]
6 changes: 3 additions & 3 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ services:
cap_add:
- SYS_PTRACE
ports:
- "3003:3003"
- "4044:4044"
# -- For dev-setup/debugging uncomment following:
# network_mode: "host"
# stdin_open: true
# tty: true
# command: >
# sh -c "python -m actinia_ogc_api_processes_plugin.main --workers 1 --timeout 3600 --bind 0.0.0.0:3003"
# sh -c "python -m actinia_ogc_api_processes_plugin.main --workers 1 --timeout 3600 --bind 0.0.0.0:4044"


actinia-core:
image: mundialis/actinia:2.12.2
image: mundialis/actinia:2.13.1
# ports:
# - "8088:8088"
depends_on:
Expand Down
110 changes: 110 additions & 0 deletions src/actinia_ogc_api_processes_plugin/api/job_status_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python
"""SPDX-FileCopyrightText: (c) 2026 by mundialis GmbH & Co. KG.

SPDX-License-Identifier: GPL-3.0-or-later

JobStatusInfo endpoint implementation.
"""

__license__ = "GPL-3.0-or-later"
__author__ = "Carmen Tawalika"
__copyright__ = "Copyright 2026 mundialis GmbH & Co. KG"
__maintainer__ = "mundialis GmbH & Co. KG"

from flask import jsonify, make_response
from flask_restful_swagger_2 import Resource, swagger
from requests.exceptions import ConnectionError as req_ConnectionError

from actinia_ogc_api_processes_plugin.apidocs import job_status_info
from actinia_ogc_api_processes_plugin.authentication import require_basic_auth
from actinia_ogc_api_processes_plugin.core.job_status_info import (
get_job_status_info,
)
from actinia_ogc_api_processes_plugin.model.response_models import (
SimpleStatusCodeResponseModel,
StatusInfoResponseModel,
)
from actinia_ogc_api_processes_plugin.resources.logging import log


class JobStatusInfo(Resource):
"""JobStatusInfo handling."""

def __init__(self) -> None:
"""Initialise."""
self.msg = "Return job status information"

@require_basic_auth()
@swagger.doc(job_status_info.describe_job_status_info_get_docs)
def get(self, job_id):
"""Return status information for a given job id."""
try:
status, status_info, resp = get_job_status_info(job_id)
if status == 200:
# build StatusInfoResponseModel from status_info dict
model_kwargs = {}
for k in (
"processID",
"type",
"jobID",
"status",
"message",
"created",
"started",
"finished",
"updated",
"progress",
"links",
):
if k in status_info:
model_kwargs[k] = status_info[k]

res = jsonify(StatusInfoResponseModel(**model_kwargs))
return make_response(res, 200)
elif status == 401:
log.error("ERROR: Unauthorized Access")
log.debug(f"actinia response: {resp.text}")
res = jsonify(
SimpleStatusCodeResponseModel(
status=401,
message="ERROR: Unauthorized Access",
),
)
return make_response(res, 401)
elif status in {400, 404}:
log.error("ERROR: No such job")
log.debug(f"actinia response: {resp.text}")
res = jsonify(
{
"type": (
"http://www.opengis.net/def/exceptions/"
"ogcapi-processes-1/1.0/no-such-job"
),
"title": "No Such Job",
"status": 404,
"detail": f"Job '{job_id}' not found",
},
)
return make_response(res, 404)
else:
log.error("ERROR: Internal Server Error")
code = getattr(resp, "status_code", status)
text = getattr(resp, "text", "")
log.debug(f"actinia status code: {code}")
log.debug(f"actinia response: {text}")
res = jsonify(
SimpleStatusCodeResponseModel(
status=500,
message="ERROR: Internal Server Error",
),
)
return make_response(res, 500)
except req_ConnectionError as e:
log.error(f"Connection ERROR: {e}")
res = jsonify(
SimpleStatusCodeResponseModel(
status=503,
message=f"Connection ERROR: {e}",
),
)
return make_response(res, 503)
44 changes: 44 additions & 0 deletions src/actinia_ogc_api_processes_plugin/apidocs/job_status_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python
"""SPDX-FileCopyrightText: (c) 2026 by mundialis GmbH & Co. KG.
SPDX-License-Identifier: GPL-3.0-or-later
API docs for JobStatusInfo endpoint.
"""

__license__ = "GPL-3.0-or-later"
__author__ = "Carmen Tawalika"
__copyright__ = "Copyright 2026 mundialis GmbH & Co. KG"
__maintainer__ = "mundialis GmbH & Co. KG"

from actinia_ogc_api_processes_plugin.model.response_models import (
SimpleStatusCodeResponseModel,
StatusInfoResponseModel,
)

describe_job_status_info_get_docs = {
"tags": ["job_status_info"],
"description": "Retrieves the status information for a job.",
"responses": {
"200": {
"description": "This response returns the job status information.",
"schema": StatusInfoResponseModel,
},
"401": {
"description": "Unauthorized Access",
"schema": SimpleStatusCodeResponseModel,
},
"404": {
"description": "Job not found",
"schema": SimpleStatusCodeResponseModel,
},
"500": {
"description": "Internal Server Error",
"schema": SimpleStatusCodeResponseModel,
},
"503": {
"description": "Connection Error",
"schema": SimpleStatusCodeResponseModel,
},
},
}
Loading