Skip to content

fix(dhcp): re-apply KEA config when running config drifts back to boo…#23

Closed
dmathur0 wants to merge 4 commits into
mainfrom
port/dhcp-config-drift-recovery-mr240
Closed

fix(dhcp): re-apply KEA config when running config drifts back to boo…#23
dmathur0 wants to merge 4 commits into
mainfrom
port/dhcp-config-drift-recovery-mr240

Conversation

@dmathur0

@dmathur0 dmathur0 commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

…tstrap

Ported from kiwi-platform MR !240. When the kea container is recycled it reloads the baked-in bootstrap config (no remote lease-database), but the sync loop only re-applied on Redis change, so the pod deadlocked with /healthcheck returning 500. Adds _kea_running_config_diverged() to detect the drift and force a re-apply, plus a looser backstop livenessProbe on config-sync-v4 so kubelet can recover if the in-loop check itself wedges.

Description

Validation

The kind integration test is manual due to taking ~30 min to complete. When the PR is ready for review,
run Actions -> Kind Integration -> Run workflow against the copy-pr-bot generated
pull-request/<PR_NUMBER> branch. Use the default test_path for the full suite,
or narrow it only while debugging.

Passing Kind Integration run:

Checklist

  • I am familiar with the contributing guidelines in CONTRIBUTING.md.
  • Commits are signed off for DCO compliance.
  • New or existing tests cover these changes, or the PR explains why tests are not needed.
  • Documentation is updated for user-facing behavior changes.
  • Generated artifacts are updated when applicable, such as OpenAPI specs,
    docs screenshots, or Helm/rendered outputs.

Summary by CodeRabbit

  • Bug Fixes

    • DHCP KEA sync now detects post-restart configuration divergence for remote (PostgreSQL) lease databases and re-applies the correct lease-db settings, even when the stored config is unchanged.
  • Improvements

    • Added a conservative “backstop” liveness probe to the config-sync service to trigger restarts when needed.
    • Healthcheck logic and lease-database injection now consistently follow the configured remote vs local mode.
    • The sync loop performs a best-effort running-state probe and forces resync when KEA falls back to bootstrap behavior.
  • Tests

    • Added unit and async end-to-end tests covering divergence detection, resync behavior, local-mode skipping, and query error handling.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared remote lease-db helper, uses it in DHCP health and Kea config generation, extends the KEA sync loop with bootstrap detection and re-apply behavior, adds async tests, and adds a Kubernetes liveness probe backstop.

Changes

KEA lease-db helper and recovery flow

Layer / File(s) Summary
Shared lease-db helper
src/nv_config_manager/common/config.py, src/nv_config_manager/dhcp/api.py, src/nv_config_manager/dhcp/kea_dhcp_confgen.py
Adds is_remote_lease_db(config=None) and a module logger, then uses the helper in /healthcheck and Kea lease-database injection.
KEA bootstrap probe
src/nv_config_manager/dhcp/cli.py
Adds _kea_running_config_is_bootstrap, derives remote_lease_db from loaded config, and re-applies injected KEA config when the running config has reverted to bootstrap.
Sync loop tests
src/tests/dhcp/test_cli.py
Adds async tests for bootstrap detection and sync-loop behavior, including remote/local mode, query errors, re-apply on divergence, and no-op when stable.
Kubernetes liveness backstop
deploy/helm/templates/dhcp.yaml
Adds a config-sync-v4 livenessProbe that GETs /healthcheck on port 9090 with delayed, less frequent polling and a longer failure window.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A bootstrap blinked, then lost its way,
I checked the leases and hopped to stay.
When KEA drifted, I bounced right in,
Reapply the config, let goodness win!
And a healthcheck poke keeps watch at night.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: re-applying KEA config when the running config drifts back to bootstrap.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 port/dhcp-config-drift-recovery-mr240

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

@dmathur0 dmathur0 force-pushed the port/dhcp-config-drift-recovery-mr240 branch from 2160eda to 79b0f65 Compare June 1, 2026 22:52

@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: 1

🤖 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 `@src/nv_config_manager/dhcp/cli.py`:
- Line 259: The code reads ini_config["dhcp.lease_db"].getboolean("local") into
remote_lease_db without guarding for a missing section/option; change the access
to either check ini_config.has_section("dhcp.lease_db") and
ini_config.has_option("dhcp.lease_db", "local") before indexing, or call
ini_config["dhcp.lease_db"].getboolean("local", fallback=False) (or another
sensible default) and log a clear warning when the section/option is absent;
apply the same defensive change to the analogous lookups in api.py and
kea_dhcp_confgen.py (search for usages of ini_config[...] .getboolean("local")
and the remote_lease_db assignment) so startup/healthcheck won’t crash on
incomplete INI.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 547532aa-f2fc-48a6-b8b4-27a4f152fdb2

📥 Commits

Reviewing files that changed from the base of the PR and between 6bec6d7 and 2160eda.

📒 Files selected for processing (3)
  • deploy/helm/templates/dhcp.yaml
  • src/nv_config_manager/dhcp/cli.py
  • src/tests/dhcp/test_cli.py

Comment thread src/nv_config_manager/dhcp/cli.py Outdated
…tstrap

When the kea container is recycled it reloads the baked-in bootstrap config
(no remote lease-database), but the sync loop only re-applied on Redis
change, so the pod deadlocked with /healthcheck returning 500. Adds
_kea_running_config_diverged() to detect the drift and force a re-apply, plus
a looser backstop livenessProbe on config-sync-v4 so kubelet can recover if
the in-loop check itself wedges.

Signed-off-by: Davesh Mathur <daveshm@nvidia.com>

@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.

♻️ Duplicate comments (1)
src/nv_config_manager/dhcp/cli.py (1)

259-259: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard dhcp.lease_db.local lookup to avoid startup crash.

At Line 259, direct indexing can raise if [dhcp.lease_db] or local is missing, and this is outside the loop try, so the process can fail before sync starts.

Proposed defensive read
-    remote_lease_db = not ini_config["dhcp.lease_db"].getboolean("local")
+    if ini_config.has_section("dhcp.lease_db"):
+        local_lease_db = ini_config["dhcp.lease_db"].getboolean("local", fallback=False)
+    else:
+        logger.warning("Missing [dhcp.lease_db] section; defaulting to remote lease-db mode.")
+        local_lease_db = False
+    remote_lease_db = not local_lease_db
🤖 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 `@src/nv_config_manager/dhcp/cli.py` at line 259, The direct access
ini_config["dhcp.lease_db"]["local"] can raise if the section or option is
missing; change the assignment to safely read the value by using ConfigParser's
safe lookup (e.g., ini_config.getboolean("dhcp.lease_db", "local", fallback=...)
or check ini_config.has_section("dhcp.lease_db") and
ini_config.has_option("dhcp.lease_db", "local")) so remote_lease_db is computed
defensively (default to False or a sensible fallback) and avoid raising before
sync starts; update the code that sets remote_lease_db to use this guarded
lookup instead of direct indexing.
🧹 Nitpick comments (1)
src/nv_config_manager/dhcp/cli.py (1)

226-228: ⚡ Quick win

Align docstring with actual probe-failure behavior.

At Line 226, the docstring says unreachable states can warrant re-apply, but the implementation returns False on KEA query errors (Line 240). Please update the wording to match current behavior.

Proposed docstring correction
-    Returns True if the running config looks like the bootstrap (or is
-    otherwise unreachable in a way that warrants re-applying).
+    Returns True if the running config looks like bootstrap drift for
+    remote lease-db setups. Probe/query failures are treated as
+    non-divergence (best-effort) and return False.
🤖 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 `@src/nv_config_manager/dhcp/cli.py` around lines 226 - 228, The docstring for
the function that checks whether the running config looks like the bootstrap
(the probe that can return True/False) is inaccurate: update its wording to
state that probe failures (specifically KEA query errors) are treated as
non-unreachable and result in False rather than triggering a re-apply; mention
KEA query errors explicitly so the docstring matches the implementation that
returns False on KEA probe failures.
🤖 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.

Duplicate comments:
In `@src/nv_config_manager/dhcp/cli.py`:
- Line 259: The direct access ini_config["dhcp.lease_db"]["local"] can raise if
the section or option is missing; change the assignment to safely read the value
by using ConfigParser's safe lookup (e.g.,
ini_config.getboolean("dhcp.lease_db", "local", fallback=...) or check
ini_config.has_section("dhcp.lease_db") and
ini_config.has_option("dhcp.lease_db", "local")) so remote_lease_db is computed
defensively (default to False or a sensible fallback) and avoid raising before
sync starts; update the code that sets remote_lease_db to use this guarded
lookup instead of direct indexing.

---

Nitpick comments:
In `@src/nv_config_manager/dhcp/cli.py`:
- Around line 226-228: The docstring for the function that checks whether the
running config looks like the bootstrap (the probe that can return True/False)
is inaccurate: update its wording to state that probe failures (specifically KEA
query errors) are treated as non-unreachable and result in False rather than
triggering a re-apply; mention KEA query errors explicitly so the docstring
matches the implementation that returns False on KEA probe failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 60adcd1c-8f9c-4b58-8efd-b15e7d0266b4

📥 Commits

Reviewing files that changed from the base of the PR and between 2160eda and 79b0f65.

📒 Files selected for processing (3)
  • deploy/helm/templates/dhcp.yaml
  • src/nv_config_manager/dhcp/cli.py
  • src/tests/dhcp/test_cli.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • deploy/helm/templates/dhcp.yaml
  • src/tests/dhcp/test_cli.py

@dmathur0 dmathur0 force-pushed the port/dhcp-config-drift-recovery-mr240 branch from 79b0f65 to 82ee2c1 Compare June 1, 2026 22:59
Comment thread src/nv_config_manager/dhcp/cli.py Outdated
asyncio.run(_refresh_loop_async(ip_version, check, refresh_interval))


async def _kea_running_config_diverged(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would rename this, diverged implies you're doing a direct comparison, which would be the cleanest approach but would always report divergence as kea has a lot of defaults and minor formatting changes between what we generate and what it reports back as its config. In the absence of being able to do a true divergence test, we should name this in a way that indicates it's only checking for the default configuration state

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good call. Renamed it to _kea_running_config_is_bootstrap (and updated the call site/tests/docstring) to make clear it only detects the bootstrap-default state rather than performing a true divergence comparison. Pushed in dfc6342.

- Rename _kea_running_config_diverged to _kea_running_config_is_bootstrap.
  The check does not do a true divergence comparison (KEA reports many
  defaults/formatting differences that would always look divergent); it only
  detects the specific bootstrap-default state, so the name now reflects that.
- Guard the [dhcp.lease_db] local lookup behind a shared is_remote_lease_db()
  helper that reads the option defensively (fallback + warning) so a missing
  section/option no longer crashes DHCP startup, the sync loop, or
  /healthcheck. Applied in cli.py, api.py, and kea_dhcp_confgen.py.
- Correct the docstring to state that probe/query failures are treated as
  non-divergence (return False).

Signed-off-by: Davesh Mathur <daveshm@nvidia.com>

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/nv_config_manager/dhcp/kea_dhcp_confgen.py (1)

824-831: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Defensive remote default still crashes here on a missing [dhcp.lease_db] section.

is_remote_lease_db() returns True when the section/option is absent (logging a warning), but the very next lines unconditionally index app_config["dhcp.lease_db"]["database"|"host"|"user"|"password"]. So a missing section/keys will raise KeyError at injection time, defeating the helper's stated goal of not crashing the sync path. Consider validating the required keys (or getitem with a clear error) before building the dict.

🛡️ Sketch
     if is_remote_lease_db(app_config):
+        if not app_config.has_section("dhcp.lease_db"):
+            raise RuntimeError(
+                "Remote lease-db mode selected but [dhcp.lease_db] section is missing"
+            )
         dhcp_config[dhcp_key]["lease-database"] = {
             "type": "postgresql",
             "name": app_config["dhcp.lease_db"]["database"],
🤖 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 `@src/nv_config_manager/dhcp/kea_dhcp_confgen.py` around lines 824 - 831, The
remote lease DB fallback in the DHCP config generation still dereferences
app_config["dhcp.lease_db"] directly, so a missing section or keys will raise a
KeyError after is_remote_lease_db() has already returned True. Update the
lease-database handling in kea_dhcp_confgen.py to validate the required
dhcp.lease_db entries before building the PostgreSQL dict, or use a guarded
lookup that raises a clear configuration error instead of crashing; keep the fix
localized to the block that constructs dhcp_config[dhcp_key]["lease-database"]
and reuse is_remote_lease_db()/app_config access patterns consistently.
src/nv_config_manager/dhcp/cli.py (1)

239-251: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Normalize get_config result like the healthcheck does.

api.py treats get_config's result defensively with config if isinstance(config, list) else [config], but here running[0] is indexed directly. If KEA returns a single dict (not a list), running[0] raises KeyError. It's swallowed by the loop's except, so the bootstrap re-apply is silently skipped on that path. Mirror the healthcheck normalization for consistency.

🛡️ Sketch
-    if not running:
-        return False
-    arguments = running[0].get("arguments", {}) if isinstance(running[0], dict) else {}
+    if not running:
+        return False
+    running_list = running if isinstance(running, list) else [running]
+    first = running_list[0]
+    arguments = first.get("arguments", {}) if isinstance(first, dict) else {}
🤖 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 `@src/nv_config_manager/dhcp/cli.py` around lines 239 - 251, The divergence
check in the KEA config path assumes get_config always returns a list, but
api.py handles this defensively because KEA may return a single dict instead.
Update the logic around get_config in the DHCP CLI helper to normalize the
result the same way as the healthcheck does before indexing, then read
arguments/Dhcp{ip_version}/lease-database from the normalized collection. Keep
the existing exception handling and logging, but make sure running[0] is never
accessed unless the response is confirmed to be a list-like sequence.
🤖 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.

Outside diff comments:
In `@src/nv_config_manager/dhcp/cli.py`:
- Around line 239-251: The divergence check in the KEA config path assumes
get_config always returns a list, but api.py handles this defensively because
KEA may return a single dict instead. Update the logic around get_config in the
DHCP CLI helper to normalize the result the same way as the healthcheck does
before indexing, then read arguments/Dhcp{ip_version}/lease-database from the
normalized collection. Keep the existing exception handling and logging, but
make sure running[0] is never accessed unless the response is confirmed to be a
list-like sequence.

In `@src/nv_config_manager/dhcp/kea_dhcp_confgen.py`:
- Around line 824-831: The remote lease DB fallback in the DHCP config
generation still dereferences app_config["dhcp.lease_db"] directly, so a missing
section or keys will raise a KeyError after is_remote_lease_db() has already
returned True. Update the lease-database handling in kea_dhcp_confgen.py to
validate the required dhcp.lease_db entries before building the PostgreSQL dict,
or use a guarded lookup that raises a clear configuration error instead of
crashing; keep the fix localized to the block that constructs
dhcp_config[dhcp_key]["lease-database"] and reuse
is_remote_lease_db()/app_config access patterns consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 74055a62-69a4-444a-9e82-0b47c644e5bd

📥 Commits

Reviewing files that changed from the base of the PR and between 82ee2c1 and dfc6342.

📒 Files selected for processing (5)
  • src/nv_config_manager/common/config.py
  • src/nv_config_manager/dhcp/api.py
  • src/nv_config_manager/dhcp/cli.py
  • src/nv_config_manager/dhcp/kea_dhcp_confgen.py
  • src/tests/dhcp/test_cli.py

@daveshm

daveshm commented Jun 24, 2026

Copy link
Copy Markdown

/ok to test c10a6b0

@github-actions

Copy link
Copy Markdown

@dmathur0

Copy link
Copy Markdown
Collaborator Author

Split the Helm-only backstop livenessProbe (the deploy/helm/templates/dhcp.yaml change) out into #65 so it can ship independently now. This PR's KEA bootstrap-drift detection / re-apply application logic stays here and should be held until after QA. Once #65 merges I'll drop the dhcp.yaml hunk from this branch so the two don't overlap.

dmathur0 added a commit that referenced this pull request Jun 25, 2026
Adds a conservative livenessProbe to the config-sync-v4 container in
deploy/helm/templates/dhcp.yaml so kubelet can recycle the sidecar if the
in-loop KEA divergence check ever wedges (asyncio loop stalls or the kea
control socket stays unresponsive). On restart the container re-runs the
unconditional "Run once" set_config() and heals the deadlock.

Thresholds are intentionally looser than the other probes
(failureThreshold * periodSeconds = 300s) so the in-loop recovery wins
under normal conditions and we only recycle when the loop itself is broken.

Split out of #23 so this low-risk, Helm-only safety net can ship now while
the KEA bootstrap-drift detection/re-apply application logic waits for QA.

Signed-off-by: Davesh Mathur <daveshm@nvidia.com>
spidercensus pushed a commit that referenced this pull request Jun 25, 2026
Adds a conservative livenessProbe to the config-sync-v4 container in
deploy/helm/templates/dhcp.yaml so kubelet can recycle the sidecar if the
in-loop KEA divergence check ever wedges (asyncio loop stalls or the kea
control socket stays unresponsive). On restart the container re-runs the
unconditional "Run once" set_config() and heals the deadlock.

Thresholds are intentionally looser than the other probes
(failureThreshold * periodSeconds = 300s) so the in-loop recovery wins
under normal conditions and we only recycle when the loop itself is broken.

Split out of #23 so this low-risk, Helm-only safety net can ship now while
the KEA bootstrap-drift detection/re-apply application logic waits for QA.

Signed-off-by: Davesh Mathur <daveshm@nvidia.com>
The config-sync-v4 backstop livenessProbe was split out of this PR and merged
to main independently in #65. Remove it here so this branch carries only the
KEA bootstrap-drift detection / re-apply application logic (and tests), which
is being held for QA, and the two changes no longer overlap.

Signed-off-by: Davesh Mathur <daveshm@nvidia.com>
@dmathur0 dmathur0 closed this Jul 8, 2026
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.

3 participants