Skip to content

Commit

Permalink
Issue #22 add online/offline status to federation overview
Browse files Browse the repository at this point in the history
  • Loading branch information
soxofaan committed Mar 5, 2025
1 parent 40e7dd9 commit 8f1ab3b
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 27 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The format is roughly based on [Keep a Changelog](https://keepachangelog.com/en/
## unreleased

- Bump minimum required Python version to 3.11 ([#127](https://github.com/Open-EO/openeo-aggregator/issues/127), [#174](https://github.com/Open-EO/openeo-aggregator/issues/174))
- Add title and description to federation listing in capabilities document ([#22](https://github.com/Open-EO/openeo-aggregator/issues/22))
- Add title, description and online/offline status to federation listing in capabilities document ([#22](https://github.com/Open-EO/openeo-aggregator/issues/22))


## 0.43.0
Expand Down
10 changes: 2 additions & 8 deletions src/openeo_aggregator/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1596,14 +1596,8 @@ def capabilities_billing(self) -> dict:

def postprocess_capabilities(self, capabilities: dict) -> dict:
# TODO: which url to use? unversioned or versioned? see https://github.com/Open-EO/openeo-api/pull/419
capabilities["federation"] = {
bid: {
"url": status["root_url"],
"title": status.get("title") or f"Backend {bid!r}",
"description": status.get("description") or f"OpenEO backend {bid!r}",
}
for bid, status in self._backends.get_status().items()
}
capabilities["federation"] = self._backends.get_federation_overview()

# TODO: standardize this field?
capabilities["_partitioned_job_tracking"] = bool(self.batch_jobs.partitioned_job_tracker)
return capabilities
Expand Down
1 change: 1 addition & 0 deletions src/openeo_aggregator/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class AggregatorBackendConfig(OpenEoBackendConfig):
packages=["openeo", "openeo_driver", "openeo_aggregator"],
)

# TODO: allow to specify more info per backend in addtion to just URL: title, description, experimental flag, ...
aggregator_backends: Dict[str, str] = attrs.field(validator=attrs.validators.min_len(1))

# See `ZooKeeperPartitionedJobDB.from_config` for supported fields.
Expand Down
34 changes: 24 additions & 10 deletions src/openeo_aggregator/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ def __init__(
)
# TODO: backend_urls as dict does not have explicit order, while this is important.
_log.info(f"Creating MultiBackendConnection with {backends=!r}")
# TODO: support more backend info than just URL (title, description, experimental flag, ...)
self._backend_urls = backends
self._configured_oidc_providers = configured_oidc_providers

Expand All @@ -258,7 +259,7 @@ def from_config() -> "MultiBackendConnection":
)

def _get_connections(self, skip_failures=False) -> Iterator[BackendConnection]:
"""Create new backend connections."""
"""Create new backend connections, possibly skipping connection failures."""
for bid, url in self._backend_urls.items():
try:
_log.info(f"Create backend {bid!r} connection to {url!r}")
Expand Down Expand Up @@ -313,20 +314,33 @@ def get_connection(self, backend_id: str) -> BackendConnection:
return con
raise OpenEOApiException(f"No backend with id {backend_id!r}")

def get_status(self) -> dict:
# TODO: reconsider method name (currently it has little to do with status)
return {
def get_federation_overview(self) -> dict:
"""
Federation overview, to be used in the capabilities document (`GET /`),
under the "federation" field, per federation extension
(conformance class https://api.openeo.org/extensions/federation/0.1.0)
"""
federation = {
c.id: {
# TODO: avoid private attributes?
# TODO: add real backend status? (cached?)
"root_url": c._root_url,
"orig_url": c._orig_url,
"title": c.capabilities().get("title"),
"description": c.capabilities().get("description"),
"url": c.root_url,
"title": c.capabilities().get("title", f"Backend {c.id!r}"),
"description": c.capabilities().get("description", f"Federated openEO backend {c.id!r}"),
"status": "online",
}
for c in self.get_connections()
}

for bid, url in self._backend_urls.items():
if bid not in federation:
federation[bid] = {
"url": url,
"status": "offline",
"title": f"Backend {bid!r}",
"description": f"Federated openEO backend {bid!r}",
}

return federation

def _get_api_versions(self) -> List[str]:
return list(set(c.capabilities().api_version() for c in self.get_connections()))

Expand Down
13 changes: 11 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ def backend1(requests_mock, mbldr) -> str:
domain = "https://b1.test/v1"
# TODO: how to work with different API versions?
requests_mock.get(
domain + "/", json=mbldr.capabilities(title="Dummy Federation One", description="Welcome to Federation One.")
domain + "/",
json=mbldr.capabilities(
title="Dummy Federation One",
description="Welcome to Federation One.",
),
)
requests_mock.get(domain + "/credentials/oidc", json=mbldr.credentials_oidc())
requests_mock.get(domain + "/processes", json=mbldr.processes(*_DEFAULT_PROCESSES))
Expand All @@ -54,7 +58,12 @@ def backend1(requests_mock, mbldr) -> str:
@pytest.fixture
def backend2(requests_mock, mbldr) -> str:
domain = "https://b2.test/v1"
requests_mock.get(domain + "/", json=mbldr.capabilities(title="Dummy The Second"))
requests_mock.get(
domain + "/",
json=mbldr.capabilities(
title="Dummy The Second",
),
)
requests_mock.get(domain + "/credentials/oidc", json=mbldr.credentials_oidc())
requests_mock.get(domain + "/processes", json=mbldr.processes(*_DEFAULT_PROCESSES))
return domain
Expand Down
29 changes: 23 additions & 6 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,19 +267,36 @@ def test_map(self, multi_backend_connection, backend1, backend2, requests_mock):
assert isinstance(res, types.GeneratorType)
assert list(res) == [("b1", {"bar": 1}), ("b2", {"meh": 2})]

def test_status(self, multi_backend_connection):
assert multi_backend_connection.get_status() == {
def test_get_federation_overview_basic(self, multi_backend_connection):
assert multi_backend_connection.get_federation_overview() == {
"b1": {
"orig_url": "https://b1.test/v1",
"root_url": "https://b1.test/v1",
"url": "https://b1.test/v1",
"title": "Dummy Federation One",
"description": "Welcome to Federation One.",
"status": "online",
},
"b2": {
"orig_url": "https://b2.test/v1",
"root_url": "https://b2.test/v1",
"url": "https://b2.test/v1",
"title": "Dummy The Second",
"description": "Test instance of openEO Aggregator",
"status": "online",
},
}

def test_get_federation_overview_offline(self, multi_backend_connection, backend1, backend2, requests_mock):
requests_mock.get(f"{backend2}/", status_code=500, json={"error": "nope"})
assert multi_backend_connection.get_federation_overview() == {
"b1": {
"url": "https://b1.test/v1",
"title": "Dummy Federation One",
"description": "Welcome to Federation One.",
"status": "online",
},
"b2": {
"url": "https://b2.test/v1",
"description": "Federated openEO backend 'b2'",
"title": "Backend 'b2'",
"status": "offline",
},
}

Expand Down
2 changes: 2 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ def test_capabilities(self, api100):
"url": "https://b1.test/v1",
"title": "Dummy Federation One",
"description": "Welcome to Federation One.",
"status": "online",
},
"b2": {
"url": "https://b2.test/v1",
"title": "Dummy The Second",
"description": "Test instance of openEO Aggregator",
"status": "online",
},
}

Expand Down

0 comments on commit 8f1ab3b

Please sign in to comment.