Skip to content

fix(dhcp): reconcile KEA config using native config hash#168

Open
dmathur0 wants to merge 1 commit into
mainfrom
davesh/dhcp-hash-reconcile
Open

fix(dhcp): reconcile KEA config using native config hash#168
dmathur0 wants to merge 1 commit into
mainfrom
davesh/dhcp-hash-reconcile

Conversation

@dmathur0

@dmathur0 dmathur0 commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the DHCP recovery failure observed during EKS upgrades. When the kea container restarts independently of the config-sync-v4 sidecar, Kea comes back up on the bootstrap /etc/kea/kea-dhcp4.conf (no subnets, default memfile lease DB). Because the desired config in Redis is unchanged, the old sync loop — which only compared the Redis value against its in-process previous_config — concluded "nothing changed" and never re-applied to Kea, leaving the pod unhealthy indefinitely.

This switches drift detection to Kea's native SHA-256 effective-config hash (Kea 2.4+), so any loss/modification of the running config is detected and reconciled regardless of whether Redis changed.

Changes

  • KeaClient.set_config() now returns the hash from the config-set response (str | None; None on pre-2.4 Kea).
  • New KeaClient.get_config_hash() using config-hash-get.
  • _sync_kea_configuration_async retains an expected_hash:
    • Startup: unconditionally applies the desired Redis config and records the verified hash (restart-safe).
    • Redis changed: apply new config, replace expected_hash.
    • Redis unchanged: compare config-hash-get to expected_hash; on mismatch (e.g. Kea restarted to bootstrap) re-apply the desired config and update expected_hash.
  • New _apply_and_verify_kea_config helper verifies the effective hash after applying before recording sync success.
  • Avoids comparing full config-get JSON (which contains Kea-generated defaults).

Note: the unreliable lease-database bootstrap heuristic referenced in the design does not exist on main, so there was nothing to remove here.

Test plan

  • set_config returns hash / handles hash-absent (pre-2.4)
  • matching hash → no reapply
  • mismatched hash → reapply
  • Kea restart/bootstrap recovery with unchanged Redis → reapply
  • Redis-changed path
  • config-set failure propagates; config-hash-get failure counted, no reapply
  • 16 targeted tests pass; 107/108 dhcp suite (1 failure needs Docker/testcontainers, unrelated)
  • ruff check clean, ruff format applied, mypy clean on changed source

Part 1 of 5 from the DHCP config-sync recovery proposal. Parts 2–5 (heartbeat liveness, strict traffic gating, observability, defense-in-depth) will follow on separate branches and stack on this.

Summary by CodeRabbit

  • Reliability Improvements

    • KEA configuration synchronization now verifies that applied settings match the running configuration.
    • Automatically detects configuration drift or KEA restarts and reapplies settings when needed.
    • Configuration updates now report KEA’s effective configuration hash.
    • Improved handling of synchronization and hash-check failures.
  • Tests

    • Added coverage for configuration updates, drift recovery, restarts, failures, and unchanged configurations.

The Kea container can restart independently while the config-sync sidecar
keeps running, bringing Kea back up from its bootstrap kea-dhcp4.conf. Since
the desired config in Redis is unchanged, the previous sync loop compared only
the Redis value against its in-process copy, concluded nothing changed, and
never re-applied to Kea.

Use Kea's native SHA-256 effective-configuration hash (Kea 2.4+) for drift
detection instead:
- set_config() now returns the hash from the config-set response.
- Add get_config_hash() wrapping the config-hash-get command.
- The sync loop tracks expected_hash from the initial apply and, when Redis is
  unchanged, compares config-hash-get against it, re-applying the desired
  config on drift. The effective hash is verified after every apply.

A config-sync restart stays safe: startup still unconditionally applies the
desired Redis config and captures a fresh expected_hash.

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

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The DHCP ConfGen CLI now verifies KEA’s effective configuration hash after applying configuration and detects KEA-side drift during refresh cycles. KeaClient exposes hashes from config-set and config-hash-get, with tests covering reconciliation and error paths.

Changes

KEA hash API and synchronization

Layer / File(s) Summary
KEA configuration hash APIs
src/nv_config_manager/dhcp/kea.py, src/tests/dhcp/test_kea.py
set_config returns KEA’s effective hash, get_config_hash retrieves the running hash, and tests cover successful responses, missing hashes, and command failures.
DHCP synchronization and drift recovery
src/nv_config_manager/dhcp/cli.py, src/tests/dhcp/test_cli.py
Initial and changed configurations are applied and verified; unchanged Redis state is compared with KEA’s expected hash, with drift, restart recovery, and error handling tested.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RedisClient
  participant _sync_kea_configuration_async
  participant KeaClient

  _sync_kea_configuration_async->>RedisClient: load desired DHCP configuration
  _sync_kea_configuration_async->>KeaClient: set_config(desired configuration)
  KeaClient-->>_sync_kea_configuration_async: applied configuration hash
  _sync_kea_configuration_async->>KeaClient: get_config_hash()
  KeaClient-->>_sync_kea_configuration_async: effective configuration hash
  _sync_kea_configuration_async->>KeaClient: reapply desired configuration on hash drift
Loading

Suggested reviewers: jojung1, polarweasel, zsblevins

🚥 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 matches the main change: DHCP KEA config reconciliation using native config hashes.
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 davesh/dhcp-hash-reconcile

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

Caution

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

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

1-1: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Handle config-hash-get failures during startup

  • src/nv_config_manager/dhcp/cli.py#L211-L235: catch KeaException from get_config_hash here and treat unsupported/missing hash as non-fatal, otherwise the sync process aborts before the refresh loop can recover.
  • src/nv_config_manager/dhcp/kea.py#L166-L183: update the docstring to say unsupported config-hash-get raises KeaException; it does not return None.
🤖 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 1, Update the startup flow around
get_config_hash in the DHCP CLI to catch KeaException and treat unsupported or
missing config hashes as non-fatal, allowing the refresh loop to continue;
preserve propagation of other failures as appropriate. Also update
get_config_hash’s docstring in the Kea integration to document that unsupported
config-hash-get raises KeaException rather than returning None.
🤖 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`:
- Around line 211-235: Update _apply_and_verify_kea_config to catch failures
from get_config_hash during startup and apply the same fallback behavior used by
the refresh loop. Allow unsupported or temporarily unavailable hash retrieval to
proceed without aborting synchronization, while preserving the existing
applied/effective hash comparison whenever a hash is successfully retrieved.

---

Outside diff comments:
In `@src/nv_config_manager/dhcp/cli.py`:
- Line 1: Update the startup flow around get_config_hash in the DHCP CLI to
catch KeaException and treat unsupported or missing config hashes as non-fatal,
allowing the refresh loop to continue; preserve propagation of other failures as
appropriate. Also update get_config_hash’s docstring in the Kea integration to
document that unsupported config-hash-get raises KeaException rather than
returning None.
🪄 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: f18424bf-ad7d-4aaa-8611-451f73e15b9d

📥 Commits

Reviewing files that changed from the base of the PR and between 784510c and 315143a.

📒 Files selected for processing (4)
  • src/nv_config_manager/dhcp/cli.py
  • src/nv_config_manager/dhcp/kea.py
  • src/tests/dhcp/test_cli.py
  • src/tests/dhcp/test_kea.py

Comment on lines +211 to +235
async def _apply_and_verify_kea_config(
kea_client: KeaClient,
config: dict[str, Any],
ip_version: int,
) -> str | None:
"""Apply the desired configuration to KEA and return its verified hash.

KEA (2.4+) returns the SHA-256 hash of the effective configuration from
``config-set``. Before treating the sync as successful, the running
configuration is confirmed against that hash via ``config-hash-get`` so a
configuration that was rolled back or only partially applied surfaces as an
error rather than being silently trusted. The verified effective hash is
returned to track for subsequent drift detection.
"""
applied_hash = await kea_client.set_config(config, version=ip_version)
effective_hash = await kea_client.get_config_hash(version=ip_version)
if applied_hash is not None and effective_hash != applied_hash:
raise KeaException(
f"KEA effective configuration hash ({effective_hash}) does not match "
f"the hash returned by config-set ({applied_hash}); "
"the configuration was not applied cleanly."
)
return effective_hash


Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant CLI and client code paths.
git ls-files src/nv_config_manager/dhcp/cli.py src/nv_config_manager/dhcp/kea.py tests | sed 's#^`#FILE` #'
printf '\n--- cli.py around relevant lines ---\n'
sed -n '180,300p' src/nv_config_manager/dhcp/cli.py
printf '\n--- kea.py hash-related methods ---\n'
rg -n "config-hash-get|get_config_hash|config-set|KeaException|pre-2\.4|hash" src/nv_config_manager/dhcp/kea.py src/nv_config_manager/dhcp/cli.py tests -S
printf '\n--- tests touching config hash handling ---\n'
rg -n "config_hash|get_config_hash|hash-get|config-set|pre-2\.4|KeaException" tests -S

Repository: NVIDIA/nv-config-manager

Length of output: 10208


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- cli.py relevant slice ---\n'
sed -n '210,290p' src/nv_config_manager/dhcp/cli.py

printf '\n--- kea.py relevant slice ---\n'
sed -n '1,260p' src/nv_config_manager/dhcp/kea.py

printf '\n--- tests mentioning startup refresh / hash handling ---\n'
rg -n "refresh loop|startup|get_config_hash|config-hash-get|config-hash|get_config_hash_failure|without reapply|pre-2\.4" tests -S

Repository: NVIDIA/nv-config-manager

Length of output: 14070


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '210,290p' src/nv_config_manager/dhcp/cli.py
echo '---'
sed -n '1,260p' src/nv_config_manager/dhcp/kea.py
echo '---'
rg -n "config-hash-get|get_config_hash|test_config_hash_get_failure_is_handled_without_reapply|startup|refresh loop" tests src/nv_config_manager/dhcp -S

Repository: NVIDIA/nv-config-manager

Length of output: 14709


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- cli.py from 290 onward ---\n'
sed -n '290,380p' src/nv_config_manager/dhcp/cli.py

printf '\n--- top-level test files ---\n'
git ls-files | rg '(^|/)(test|tests)/|test_.*\.(py|ts|js)$|_test\.(py|ts|js)$' -n

printf '\n--- search for exception handling around refresh_kea_configuration ---\n'
rg -n "_sync_kea_configuration_async|refresh_kea_configuration|except .*KeaException|ClickException|sys\.excepthook|_exception_handler" src/nv_config_manager/dhcp/cli.py -n -S

Repository: NVIDIA/nv-config-manager

Length of output: 24456


Handle get_config_hash() failures on startup. _apply_and_verify_kea_config() still calls get_config_hash() before entering the refresh loop, so a pre-2.4 Kea server or any transient hash-get error can abort sync during startup even though the loop later tolerates the same failure. Mirror the loop’s fallback here so unsupported servers start cleanly.

🤖 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 211 - 235, Update
_apply_and_verify_kea_config to catch failures from get_config_hash during
startup and apply the same fallback behavior used by the refresh loop. Allow
unsupported or temporarily unavailable hash retrieval to proceed without
aborting synchronization, while preserving the existing applied/effective hash
comparison whenever a hash is successfully retrieved.

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