Skip to content

Service uptime - #203

Merged
brucetony merged 7 commits into
mainfrom
service-uptime
Jul 30, 2026
Merged

Service uptime#203
brucetony merged 7 commits into
mainfrom
service-uptime

Conversation

@brucetony

@brucetony brucetony commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added background monitoring for configured downstream services, including status, latency, errors, and historical results.
    • Added a service health history endpoint with filtering, time ranges, summaries, and detailed checks.
    • Added configurable monitoring intervals and data retention settings.
    • Expanded health responses with standardized statuses and latency information.
  • Documentation

    • Updated environment variable guidance, RBAC details, testing instructions, and service health monitoring documentation.
    • Removed the local Kong development configuration.
  • Bug Fixes

    • Improved shared lifecycle management for background services.
    • Updated PostgreSQL configuration names used for persisted event and health data.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@brucetony, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c57dc0e-3191-4c4a-8a2f-ee9b37a084e1

📥 Commits

Reviewing files that changed from the base of the PR and between b5b5afa and cae9bf4.

📒 Files selected for processing (5)
  • hub_adapter/dependencies.py
  • hub_adapter/routers/health.py
  • hub_adapter/routers/node.py
  • hub_adapter/service_health.py
  • tests/test_service_health.py
📝 Walkthrough

Walkthrough

The PR adds configurable downstream service health monitoring with asynchronous probes, Postgres persistence, lifecycle management, health-history APIs, new response schemas, shared managers, updated Postgres settings, tests, documentation, and removal of the Kong Docker configuration.

Changes

Service health monitoring

Layer / File(s) Summary
Health contracts and configuration
hub_adapter/schemas/health.py, hub_adapter/conf.py, hub_adapter/schemas/logs.py, README.md, hub_adapter/database.py
Adds health and monitoring enums, health-history response models, service-health settings, renamed Postgres settings, monitoring events, and service-health documentation.
Probe and storage implementation
hub_adapter/service_health.py, tests/test_service_health.py
Adds asynchronous downstream probing, Kong-specific checks, Peewee persistence, aggregation queries, retention pruning, and tests for probing and storage behavior.
Monitor lifecycle and application wiring
hub_adapter/managers.py, hub_adapter/server.py, hub_adapter/routers/node.py, tests/test_managers.py, tests/router_tests/test_node.py
Centralizes manager instances, starts and stops service monitoring with the application, restarts it after settings changes, and tests shared manager identity.
Health API and OpenAPI behavior
hub_adapter/routers/health.py, tests/router_tests/test_health.py
Delegates service health checks to asynchronous probes, adds /health/services/history, validates query parameters, maps database failures to 503, and updates API schema tests.
Container configuration removal
docker/kong/*
Removes the Kong declarative configuration, Docker Compose setup, and marker file.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FastAPI
  participant ServiceHealthMonitor
  participant DownstreamServices
  participant Postgres
  FastAPI->>ServiceHealthMonitor: start monitoring
  ServiceHealthMonitor->>DownstreamServices: probe configured health endpoints
  DownstreamServices-->>ServiceHealthMonitor: return status and latency
  ServiceHealthMonitor->>Postgres: persist sweep results
  FastAPI->>ServiceHealthMonitor: stop monitoring
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related to the service health monitoring work, but it is too broad to clearly describe the main change. Use a more specific title like "Add downstream service health monitoring" or "Add service health history endpoints".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch service-uptime

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (5)
hub_adapter/service_health.py (3)

175-180: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

create_tables runs on every bind, including read paths.

Every summarize_range, fetch_last_checks, fetch_checks and record_sweep call issues CREATE TABLE IF NOT EXISTS plus index DDL. _connect already ensures the table exists at startup, so this is redundant round-trips on each request/sweep (and DDL requires broader grants than reads).

Consider making creation explicit and idempotent-once:

♻️ Proposed refactor
 `@contextmanager`
-def bind_service_health(db: pw.Database):
-    """Bind the health check model to a database, creating the table if it does not exist yet."""
-    with db.bind_ctx((ServiceHealthCheck,)):
-        db.create_tables((ServiceHealthCheck,))
-        yield
+def bind_service_health(db: pw.Database, create: bool = False):
+    """Bind the health check model to a database, optionally creating the table."""
+    with db.bind_ctx((ServiceHealthCheck,)):
+        if create:
+            db.create_tables((ServiceHealthCheck,))
+        yield

and pass create=True only from _connect (Line 363).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hub_adapter/service_health.py` around lines 175 - 180, Update
bind_service_health so it only binds ServiceHealthCheck and no longer calls
create_tables on every context entry. Move table creation to the _connect
initialization path by invoking bind_service_health with create=True there, and
support the create flag so schema creation remains explicit and occurs once at
startup.

428-430: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Blocking peewee calls run on the event loop.

record_sweep and prune_old_checks are synchronous Postgres round trips executed directly inside the async sweep, so they block request handling — the periodic prune DELETE over the retention window is the risky one. Wrapping them in asyncio.to_thread keeps the loop responsive.

♻️ Proposed refactor
-            record_sweep(self._db, results)
-            self._prune_if_due()
+            await asyncio.to_thread(record_sweep, self._db, results)
+            await asyncio.to_thread(self._prune_if_due)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hub_adapter/service_health.py` around lines 428 - 430, Update the async sweep
around record_sweep and _prune_if_due to run their synchronous database work via
asyncio.to_thread, ensuring both the sweep insert and periodic prune_old_checks
DELETE execute off the event loop while preserving their existing order and
behavior.

385-396: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider retrying the connection on an explicit restart.

_connection_attempted is latched forever, so once Postgres is unavailable at startup, a later PATCH /node/settings (which calls start() again) still cannot enable monitoring — only a process restart can. Allowing the reconnect on an explicit restart would make recovery operator-friendly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hub_adapter/service_health.py` around lines 385 - 396, The start() connection
flow currently prevents recovery after an initial failure because
_connection_attempted remains latched; reset that state on an explicit restart
such as PATCH /node/settings before invoking start(), so start() retries
_connect() and can enable monitoring when Postgres becomes available, while
preserving the existing one-attempt behavior during normal operation.
tests/test_service_health.py (1)

225-288: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Aggregation helpers are never exercised.

summarize_range, fetch_last_checks, fetch_checks, record_sweep and prune_old_checks are mocked in every test, so the uptime math and the DISTINCT ON ordering in fetch_last_checks are untested. Note SQLite cannot stand in here (DISTINCT ON is Postgres-only), so this likely needs a marked integration test. Want me to draft one?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_service_health.py` around lines 225 - 288, Add a marked PostgreSQL
integration test covering the real aggregation helpers summarize_range,
fetch_last_checks, fetch_checks, record_sweep, and prune_old_checks instead of
mocking them. Seed representative checks, execute the helpers against the test
database, and assert uptime calculations plus fetch_last_checks selecting the
newest row per service according to its DISTINCT ON ordering; keep unit mocks
only for sweep orchestration behavior.
tests/router_tests/test_health.py (1)

81-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add ClassVar to satisfy Ruff RUF012.

🔧 Proposed fix
+from typing import ClassVar
+
 class TestOpenApiStatusEnums:
     ...
-    EXPECTED_ENUMS = {
+    EXPECTED_ENUMS: ClassVar[dict[str, list[str]]] = {
         "HealthStatus": ["OK", "WARNING", "ERROR", "CRITICAL"],
         "ServiceCheckStatus": ["OK", "ERROR"],
         "ServiceMonitoringStatus": ["ACTIVE", "DISABLED"],
     }

-    EXPECTED_REFS = {
+    EXPECTED_REFS: ClassVar[dict[tuple[str, str], str]] = {
         ("HealthCheck", "status"): "HealthStatus",
         ("ServiceHealthPoint", "status"): "ServiceCheckStatus",
         ("ServiceHealthSummary", "status"): "ServiceMonitoringStatus",
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/router_tests/test_health.py` around lines 81 - 91, Annotate the mutable
class-level constants EXPECTED_ENUMS and EXPECTED_REFS with ClassVar to satisfy
Ruff RUF012, preserving their existing values and structure.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@hub_adapter/routers/health.py`:
- Around line 165-181: Update async get_health_downstream_services_history so
the synchronous Peewee calls summarize_range, fetch_last_checks, and conditional
fetch_checks execute via the project’s thread-offload mechanism rather than
directly on the event loop. Preserve their arguments, include_checks behavior,
result assignments, and existing PeeweeException-to-503 handling.

In `@hub_adapter/service_health.py`:
- Around line 85-99: Update probe_service’s request error handling to catch all
httpx2.HTTPError failures, including httpx2.InvalidURL, and convert them into
the existing 503 ERROR result with an informative message and log entry. Ensure
probe_all’s asyncio.gather preserves the full sweep by using
return_exceptions=True if non-httpx2 exceptions can still escape.

In `@README.md`:
- Around line 140-159: Update the “Service health monitoring” section in
README.md to hyphenate “per service” as “per-service” in the description of
uptime and latency statistics. Leave the surrounding documentation unchanged.

In `@tests/test_service_health.py`:
- Around line 312-321: Update test_reports_that_monitoring_is_disabled to patch
the endpoint’s imported binding,
hub_adapter.routers.health.service_health_monitor, rather than
hub_adapter.managers.service_health_monitor, so the route uses the mocked
monitor. Preserve the existing response assertions.

---

Nitpick comments:
In `@hub_adapter/service_health.py`:
- Around line 175-180: Update bind_service_health so it only binds
ServiceHealthCheck and no longer calls create_tables on every context entry.
Move table creation to the _connect initialization path by invoking
bind_service_health with create=True there, and support the create flag so
schema creation remains explicit and occurs once at startup.
- Around line 428-430: Update the async sweep around record_sweep and
_prune_if_due to run their synchronous database work via asyncio.to_thread,
ensuring both the sweep insert and periodic prune_old_checks DELETE execute off
the event loop while preserving their existing order and behavior.
- Around line 385-396: The start() connection flow currently prevents recovery
after an initial failure because _connection_attempted remains latched; reset
that state on an explicit restart such as PATCH /node/settings before invoking
start(), so start() retries _connect() and can enable monitoring when Postgres
becomes available, while preserving the existing one-attempt behavior during
normal operation.

In `@tests/router_tests/test_health.py`:
- Around line 81-91: Annotate the mutable class-level constants EXPECTED_ENUMS
and EXPECTED_REFS with ClassVar to satisfy Ruff RUF012, preserving their
existing values and structure.

In `@tests/test_service_health.py`:
- Around line 225-288: Add a marked PostgreSQL integration test covering the
real aggregation helpers summarize_range, fetch_last_checks, fetch_checks,
record_sweep, and prune_old_checks instead of mocking them. Seed representative
checks, execute the helpers against the test database, and assert uptime
calculations plus fetch_last_checks selecting the newest row per service
according to its DISTINCT ON ordering; keep unit mocks only for sweep
orchestration behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 341b3cdf-7bb1-4b1b-99bd-72432040715b

📥 Commits

Reviewing files that changed from the base of the PR and between f0898ec and b5b5afa.

📒 Files selected for processing (18)
  • README.md
  • docker/kong/config/kong.yaml
  • docker/kong/docker-compose.yml
  • docker/kong/kong.txt
  • docker/kong/test-realm.json
  • hub_adapter/conf.py
  • hub_adapter/database.py
  • hub_adapter/managers.py
  • hub_adapter/routers/health.py
  • hub_adapter/routers/node.py
  • hub_adapter/schemas/health.py
  • hub_adapter/schemas/logs.py
  • hub_adapter/server.py
  • hub_adapter/service_health.py
  • tests/router_tests/test_health.py
  • tests/router_tests/test_node.py
  • tests/test_managers.py
  • tests/test_service_health.py
💤 Files with no reviewable changes (3)
  • docker/kong/config/kong.yaml
  • docker/kong/docker-compose.yml
  • docker/kong/kong.txt

Comment thread hub_adapter/routers/health.py
Comment thread hub_adapter/service_health.py
Comment thread README.md
Comment thread tests/test_service_health.py
@brucetony
brucetony merged commit 3262e7a into main Jul 30, 2026
7 checks passed
@brucetony
brucetony deleted the service-uptime branch July 30, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant