Skip to content

feat(catalog): storj health signal for satellite dial-back failure + DDNS guidance - #318

Merged
GeiserX merged 1 commit into
mainfrom
fix/storj-dialback-health
Aug 10, 2026
Merged

feat(catalog): storj health signal for satellite dial-back failure + DDNS guidance#318
GeiserX merged 1 commit into
mainfrom
fix/storj-dialback-health

Conversation

@GeiserX

@GeiserX GeiserX commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Why

When a storj node advertises a stale address (e.g. after a silent ISP IP re-provision), satellites dial a dead address and count the node offline — new data stops, egress income stalls, sustained offline leads to suspension. The container stays healthy and the dashboard stays green the whole time: container health is computed from restarts and crashes, and is structurally blind to reachability. In the motivating incident the failure line appeared ~40x/hour for days and the first human-visible signal was provider email.

What

  • health_signals on services/storage/storj.yml: matches the node relaying the satellite's own dial-back error, anchored on the mechanism (ping satellite.*failed to dial storage node) so timeout and connection-refused variants match alike. The existing producer-state machinery (/api/services/{slug}/producer-state) scans the last 200 worker log lines for declared signals, so this surfaces as FAILING with the exact remedy in the dashboard.
  • ADDRESS env description and docs/guides/storj.md now lead with a DDNS hostname and explain why a literal IP strands the node, plus how to self-check with the node's /api/sno/ (lastPinged, quicStatus).

Testing

  • New TestStorjDialBackSignal pins the real catalog entry against the verbatim live incident log line (not a paraphrase), plus the connection-refused variant.
  • Negative controls: a successful ping satellite succeeded line (shares the trigger words) and ordinary healthy log lines must produce zero hits.
  • Full suite: 4533 passed, 6 skipped, coverage 95.61%. ruff check + ruff format --check clean. generate_readme_tables.py --check and referral_check.py pass locally.

Summary by CodeRabbit

  • Documentation

    • Updated Storj deployment guidance to require a DDNS hostname and port for external access.
    • Added troubleshooting steps for unreachable nodes, including failure symptoms, recovery behavior, and diagnostics.
  • Bug Fixes

    • Added clearer health reporting when satellite connections fail, including guidance for checking advertised addresses and port forwarding.
  • Tests

    • Expanded coverage for Storj connection timeouts, refused connections, successful pings, and unrelated healthy logs.

…DDNS guidance

The Aug 2026 incident: the node advertised a stale IP after a silent ISP
re-provision, and satellites' dial-backs timed out ~40x/hour for days
while the container looked perfectly healthy — container health is
structurally blind to it, and the offline emails arrived days later.

- health_signals on storj: the verbatim failure line the node logs when
  a satellite cannot dial it back, anchored on the mechanism (failed to
  dial storage node) so a refused connection matches the same way. The
  producer-state route already scans worker logs for declared signals,
  so this lights up the dashboard with the exact remedy: check ADDRESS
  against the CURRENT public IP, prefer a DDNS hostname, confirm 28967.
- ADDRESS env description + storj guide now lead with the DDNS hostname
  and say plainly why a literal IP is a time bomb, including how to
  self-check reachability via the node dashboard API.
- Tests pin the real catalog entry against the verbatim live log line,
  with negative controls: a successful ping (which shares the words
  'ping satellite') and ordinary healthy log lines must not match.
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 61472333-fe81-4098-a334-02bdf38a81d9

📥 Commits

Reviewing files that changed from the base of the PR and between 03a8c0f and 6ca3d28.

📒 Files selected for processing (3)
  • docs/guides/storj.md
  • services/storage/storj.yml
  • tests/test_producer_state.py

📝 Walkthrough

Walkthrough

Storj deployment guidance now requires DDNS hostnames with ports. The Storj service defines a satellite dial-failure health signal. Tests cover failure, success, unrelated logs, and signal metadata.

Changes

Storj reachability

Layer / File(s) Summary
Storj address and health signal
services/storage/storj.yml, docs/guides/storj.md
The configuration and deployment guide prefer DDNS addresses with TCP/UDP port 28967. The health signal marks nodes as failing when satellites cannot dial them and provides diagnostic guidance.
Storj health signal validation
tests/test_producer_state.py
Tests validate signal metadata, timeout and connection-refused failures, successful pings, and unrelated healthy logs.

Estimated code review effort: 2 (Simple) | ~15 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: a Storj satellite dial-back health signal and updated DDNS guidance.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/storj-dialback-health

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.

@GeiserX
GeiserX merged commit 94b7fd6 into main Aug 10, 2026
11 checks passed
@GeiserX
GeiserX deleted the fix/storj-dialback-health branch August 10, 2026 12:49
GeiserX added a commit that referenced this pull request Aug 10, 2026
… positives

Round 2 from independent review:

- getaddrinfo raises UnicodeError (a ValueError, NOT an OSError) for an
  IDNA-invalid label; uncaught it escaped to the route umbrella and
  silently zeroed the log-signal scan sharing the try block — the address
  check disabling the very detection (#318) it complements. Caught, and
  the call site got its own suppress guard so no future failure mode can
  shadow log signals again. Worker-supplied hostnames are shape-validated
  before the resolver ever sees them, and resolutions are memoized 60s so
  a blackholed resolver costs one executor thread per window.
- Judge only RUNNING containers on the node the caller asked about: an
  exited container elsewhere in the fleet carries its last run's env, and
  judging it produced findings about the wrong machine.
- Cross-family comparisons are silence: the egress detectors are
  dual-stack, so a v6 egress against a v4 literal (or a v6-only DDNS name
  against v4 egress) says nothing about staleness. Same-family filtering
  before the membership check.
- Resolved IPs are redacted to public-only before being echoed: they
  originate from a worker-supplied name, and repeating a private answer
  would let a rogue worker read the hub's internal DNS view.
- A dangling 'host:' colon is a typo, stripped instead of earning a
  confident NXDOMAIN about a name never looked up (bare v6 keeps its
  trailing colons).
- advertised_address_env may never name a secret-flagged var: CI guard in
  the catalog tests plus a runtime backstop in the worker.
- External (image-matched) containers now carry advertised_address too —
  running a storagenode BEFORE installing CashPilot is the common storj
  adoption path, and those nodes were blind spots.
- Stale-egress caveat in both mismatch messages (worker egress readings
  are cached up to an hour; the first hour after an IP change can read
  stale and self-heals).

Tests: resolver three-valued contract on the REAL function (NXDOMAIN,
EAI_AGAIN, timeout, UnicodeError), family-guard decision rows, private-IP
redaction, running/worker_id filtering, secret backstop, external-node
coverage.
GeiserX added a commit that referenced this pull request Aug 10, 2026
…chine no longer has (#320)

* feat(fleet): flag a dial-back service whose advertised address the machine no longer has

The check that catches a stale advertised IP the day the ISP re-provisions
it, instead of days later via the provider's offline emails.

- advertised_address_env in the service schema: the ONE env var holding the
  address the network dials the service back at. The worker copies that
  single variable's value into heartbeat container entries — never any other
  env, which holds credentials — at the cost of one inspect call per
  declaring container. storj declares ADDRESS.
- The hub compares it against that worker's detected egress IP in producer
  state: a stale public literal, a private literal, and a hostname resolving
  away from the egress are FAILING findings that outrank PRODUCING (storage
  and held components keep ticking while inbound work is dead). Undetected
  egress, transient DNS trouble, and unreported addresses are NO CLAIM —
  a wrong 'your address is stale' sends the operator to fix DNS that is
  fine. Only a definitive NXDOMAIN counts as a resolution verdict.
- The mismatch reason names its assumption (inbound rides the machine's
  default egress) so a deliberate second-WAN forward can be read and
  dismissed rather than mistrusted.

Tests: decision-table over the verdict (findings AND no-claim rows), the
hub helper with an injectable resolver, worker-side extraction proving the
declared var and ONLY the declared var leaves the container inspect, a
catalog guard that every declared advertised_address_env names a real env
var, and a control that undeclared services carry no key at all.

* fix(fleet): harden the advertised-address check against its own false positives

Round 2 from independent review:

- getaddrinfo raises UnicodeError (a ValueError, NOT an OSError) for an
  IDNA-invalid label; uncaught it escaped to the route umbrella and
  silently zeroed the log-signal scan sharing the try block — the address
  check disabling the very detection (#318) it complements. Caught, and
  the call site got its own suppress guard so no future failure mode can
  shadow log signals again. Worker-supplied hostnames are shape-validated
  before the resolver ever sees them, and resolutions are memoized 60s so
  a blackholed resolver costs one executor thread per window.
- Judge only RUNNING containers on the node the caller asked about: an
  exited container elsewhere in the fleet carries its last run's env, and
  judging it produced findings about the wrong machine.
- Cross-family comparisons are silence: the egress detectors are
  dual-stack, so a v6 egress against a v4 literal (or a v6-only DDNS name
  against v4 egress) says nothing about staleness. Same-family filtering
  before the membership check.
- Resolved IPs are redacted to public-only before being echoed: they
  originate from a worker-supplied name, and repeating a private answer
  would let a rogue worker read the hub's internal DNS view.
- A dangling 'host:' colon is a typo, stripped instead of earning a
  confident NXDOMAIN about a name never looked up (bare v6 keeps its
  trailing colons).
- advertised_address_env may never name a secret-flagged var: CI guard in
  the catalog tests plus a runtime backstop in the worker.
- External (image-matched) containers now carry advertised_address too —
  running a storagenode BEFORE installing CashPilot is the common storj
  adoption path, and those nodes were blind spots.
- Stale-egress caveat in both mismatch messages (worker egress readings
  are cached up to an hour; the first hour after an IP change can read
  stale and self-heals).

Tests: resolver three-valued contract on the REAL function (NXDOMAIN,
EAI_AGAIN, timeout, UnicodeError), family-guard decision rows, private-IP
redaction, running/worker_id filtering, secret backstop, external-node
coverage.

* fix(fleet): bound the resolve cache, name the node in mismatch findings

CodeRabbit round on the review commit:

- _RESOLVE_CACHE keys are worker-supplied hostnames and nothing ever
  evicted them — steady memory growth for the hub's lifetime. Expired
  entries are purged on every write and the table is capped at 256,
  oldest-first.
- The mismatch reason said 'this machine' without saying WHICH machine —
  a fleet finding nobody can act on. The worker's node name is prefixed
  into the reason.
- The resolver tests keyed cache uniqueness on id(), which CPython
  reuses; fixed host + explicit cache clear instead.
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