Skip to content

feat: add ttl controller in janitor - #1206

Merged
lalitadithya merged 10 commits into
NVIDIA:mainfrom
XRFXLP:xrfxlp/370/implementation
Apr 20, 2026
Merged

lalitadithya merged 10 commits into
NVIDIA:mainfrom
XRFXLP:xrfxlp/370/implementation

Conversation

@XRFXLP

@XRFXLP XRFXLP commented Apr 20, 2026 •

Copy link
Copy Markdown
Member

Summary

Implementation based on: ADR-037: Janitor — TTL-Based Cleanup of Maintenance CRs

Testing

Cleanup logic test:

$ kubectl apply -f - <<'EOF'
apiVersion: janitor.dgxc.nvidia.com/v1alpha1
kind: RebootNode
metadata:
  name: ttl-quick-test
  annotations:
    nvsentinel.nvidia.com/ttl: "30s"
spec:
  nodeName: nvsentinel-worker2
  force: false
EOF
rebootnode.janitor.dgxc.nvidia.com/ttl-quick-test created

$ kg rebootnode
NAME             NODE                 FORCE   NODEREADY   AGE
ttl-quick-test   nvsentinel-worker2   false   Unknown     6s

$ kg rebootnode
NAME             NODE                 FORCE   NODEREADY   AGE
ttl-quick-test   nvsentinel-worker2   false   Unknown     24s

$ kg rebootnode
No resources found

As expected the CR was deleted after ~30 seconds.

From the logs of janitor:

{"time":"2026-04-20T06:39:51.536921132Z","level":"INFO","msg":"Successfully created node lock lease and acquired lock","module":"janitor","version":"dev","maintenanceResource":"ttl-quick-test","nodeLockName":"nvsentinel-worker2"}
{"time":"2026-04-20T06:39:51.63894852Z","level":"INFO","msg":"Sending reboot signal to node","module":"janitor","version":"dev","node":"nvsentinel-worker2"}
{"time":"2026-04-20T06:39:56.839000591Z","level":"INFO","msg":"RebootNode status updated","module":"janitor","version":"dev","node":"nvsentinel-worker2"}
{"time":"2026-04-20T06:40:21.000748196Z","level":"INFO","msg":"resource has expired","module":"janitor","version":"dev","name":"ttl-quick-test","expiry":"2026-04-20T06:40:21Z"}
{"time":"2026-04-20T06:40:21.000761951Z","level":"INFO","msg":"deleting expired resource","module":"janitor","version":"dev","kind":"RebootNode","name":"ttl-quick-test","expiry":"2026-04-20T06:40:21Z"}
{"time":"2026-04-20T06:40:21.0152205Z","level":"INFO","msg":"Validation for Janitor CR upon deletion","module":"janitor","version":"dev","logger":"janitor-webhook","type":"RebootNode","name":"ttl-quick-test"}

Annotation addition on resource if not present:

$ kubectl apply -f - <<'EOF'
apiVersion: janitor.dgxc.nvidia.com/v1alpha1
kind: RebootNode
metadata:
  name: ttl-default-test
spec:
  nodeName: nvsentinel-worker
  force: false
EOF
rebootnode.janitor.dgxc.nvidia.com/ttl-default-test created

$ kubectl get rebootnode ttl-default-test -o jsonpath='{.metadata.annotations}' | jq 
{
  "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"janitor.dgxc.nvidia.com/v1alpha1\",\"kind\":\"RebootNode\",\"metadata\":{\"annotations\":{},\"name\":\"ttl-default-test\"},\"spec\":{\"force\":false,\"nodeName\":\"nvsentinel-worker\"}}\n",
  "nvsentinel.nvidia.com/expiry": "2026-05-04T06:41:55Z",
  "nvsentinel.nvidia.com/ttl": "336h0m0s"
}

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📚 Documentation
  • 🔧 Refactoring
  • 🔨 Build/CI

Component(s) Affected

  • Core Services
  • Documentation/CI
  • Fault Management
  • Health Monitors
  • Janitor
  • Other: ____________

Testing

  • Tests pass locally
  • Manual testing completed
  • No breaking changes (or documented)

Checklist

  • Self-review completed
  • Documentation updated (if needed)
  • Ready for review

Summary by CodeRabbit

  • New Features

    • Maintenance resources include a TTL annotation and janitor now supports a configurable default TTL plus an enable/disable toggle (via CLI/Helm) to control TTL-based auto-cleanup.
  • Metrics

    • Added a counter for TTL-triggered maintenance deletions.
  • Tests

    • New unit and integration tests covering TTL processing, expiry, requeue behavior, idempotency, preservation, and controller integration.
  • Documentation

    • Updated design docs clarifying TTL semantics, default behavior, and the enable/disable interactions.

Signed-off-by: Ajay Mishra <ajmishra@nvidia.com>
@coderabbitai

coderabbitai Bot commented Apr 20, 2026 •

Copy link
Copy Markdown
Contributor

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: 7cbadb2a-1a94-416a-9b58-9f95fc2a226d

📥 Commits

Reviewing files that changed from the base of the PR and between e330496 and 4eb168d.

📒 Files selected for processing (1)
  • janitor/pkg/ttl/reconciler.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • janitor/pkg/ttl/reconciler.go

📝 Walkthrough

Walkthrough

Introduce a configurable TTL cleanup system: new generic TTL package and reconciler, controller setup, janitor CLI/Helm wiring to enable/default TTLs, TTL deletion metrics, Helm annotations for default TTLs, and unit + envtest tests for TTL behavior.

Changes

Cohort / File(s) Summary
Helm templates & values
distros/kubernetes/nvsentinel/charts/fault-remediation/values.yaml, distros/kubernetes/nvsentinel/charts/janitor/values.yaml, distros/kubernetes/nvsentinel/values-full.yaml, distros/kubernetes/nvsentinel/values-tilt.yaml
Add nvsentinel.nvidia.com/ttl: "336h" annotation to maintenance CR templates and introduce ttl chart values (enabled, defaultTTL).
Janitor deployment args
distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml
Add container args --enable-ttl={{ .Values.ttl.enabled }} and --default-ttl={{ .Values.ttl.defaultTTL }}.
Janitor startup wiring
janitor/main.go
Add CLI flags --enable-ttl and --default-ttl (validation) and conditional registration of per-kind TTL reconcilers via registerTTLReconcilers.
TTL core package
janitor/pkg/ttl/ttl.go
New TTL state machine: annotation constants, Clock interface, Action/Result types, and Process() applying defaults, computing expiry, capping requeue, honoring preserve/deletion timestamp, and returning actions.
Generic reconciler & setup
janitor/pkg/ttl/reconciler.go, janitor/pkg/ttl/setup.go
New generic Reconciler[T] with options (WithDefaultTTL, WithClock, WithMetrics), Reconcile logic (Get → Process → Update/Delete → metrics callback), and Setup helper to register controllers.
Metrics
janitor/pkg/metrics/metrics.go
Add janitor_ttl_deletions_total CounterVec (label: kind) and IncTTLDeletion(kind) to record TTL-triggered deletions.
Unit & integration tests
janitor/pkg/ttl/ttl_test.go, janitor/pkg/ttl/reconciler_test.go
Add deterministic unit tests (fake clock) for TTL logic and envtest-based reconciler integration tests covering annotation application, expiry deletion, preserve behavior, idempotency, and nil-option safety.
Janitor tests adjustment
tests/janitor_test.go
Relax interval-overlap assertions to allow boundary-touching in node-locking test.
Docs
docs/designs/037-janitor-cr-ttl-cleanup.md
Document --enable-ttl/ttl.enabled control, --default-ttl semantics (zero disables default but not per-CR annotations), and mitigation notes.

Sequence Diagram

sequenceDiagram
    participant Controller as Controller Runtime
    participant Reconciler as TTL Reconciler
    participant API as Kubernetes API
    participant Metrics as Metrics Registry

    Controller->>Reconciler: Reconcile(ctx, Request)
    Reconciler->>API: Get CR (NamespacedName)
    API-->>Reconciler: CR (may lack annotations)

    Reconciler->>Reconciler: Process(ctx, obj, defaultTTL, clock)
    alt Apply default TTL (missing & defaultTTL>0)
        Reconciler->>API: Update CR annotations (write ttl, expiry)
        API-->>Reconciler: Update OK
        Reconciler-->>Controller: Result(requeue after ≤ 1h)
    else Expired
        Reconciler->>Metrics: IncTTLDeletion(kind)
        Reconciler->>API: Delete CR
        API-->>Reconciler: Deleted / NotFound tolerated
        Reconciler-->>Controller: Result(no requeue)
    else Preserve or Noop
        Reconciler-->>Controller: Result(no requeue)
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰
I nibble at timestamps, tuck them neat,
A carrot-clock for resources to meet,
When TTLs close and petals fall,
The janitor hops and clears them all,
Tidiness in every clustered seat.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a TTL controller to the janitor component, which is the primary feature across all modified files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
janitor/pkg/ttl/ttl_test.go (1)

48-214: Collapse the Process scenario coverage into a table-driven test.

These cases all follow the same setup/call/assert pattern with only annotations, default TTL, clock movement, and expected Result changing. A table-driven test will make future TTL edge cases much cheaper to add and review. As per coding guidelines, "Write table-driven tests when testing multiple scenarios in Go".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@janitor/pkg/ttl/ttl_test.go` around lines 48 - 214, Collapse the many similar
TestProcess_* cases into a single table-driven test (e.g.
TestProcess_TableDriven) that iterates over cases describing name,
initialAnnotations, defaultTTL, clockAdvance (duration to advance the
newClock/clk), and expected results (expected Action:
ActionRequeue/ActionNoop/ActionExpired, expected Changed bool, expected
RequeueAfter duration, expected annotation assertions for
TTLAnnotation/ExpiryAnnotation/PreserveAnnotation). For each case call
Process(ctx, rn, defaultTTL, clk) using newRebootNode and newClock (and use
clk.advance where needed), then assert result fields and rn.Annotations per
case; include a case for initializing nil annotation map and for
maxRequeueInterval behavior using maxRequeueInterval, and cover invalid
TTL/Expiry strings and zero TTL; keep existing helper names (Process,
newRebootNode, newClock, clk.advance, TTLAnnotation, ExpiryAnnotation,
PreserveAnnotation, maxRequeueInterval) so assertions and setup map directly to
the original tests.
janitor/main.go (1)

494-519: Consider collapsing repeated TTL setup code into a small helper.

The three near-identical option/setup blocks are good candidates for a tiny helper to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@janitor/main.go` around lines 494 - 519, The three TTL setup blocks duplicate
the same pattern; create a small helper like setupTTL[T any](mgr ctrl.Manager,
name string, ttl time.Duration) that builds opts using
ttl.WithDefaultTTL[T](ttl) and
ttl.WithMetrics[T](janitormetrics.GlobalMetrics.IncTTLDeletion) and then calls
ttl.Setup[T](mgr, name, opts...); replace the current calls for RebootNode,
GPUReset and TerminateNode with succinct calls to
setupTTL[*janitordgxcnvidiacomv1alpha1.RebootNode](mgr, "rebootnode-ttl",
defaultTTL) etc., and propagate/return any error from the helper so existing
error logging/formatting (slog.Error and fmt.Errorf) can remain unchanged where
you call it.
distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml (1)

62-62: Avoid duplicating the default TTL in template logic.

ttl.defaultTTL is already defined in chart values, so keeping default "336h" here creates two sources of truth.

♻️ Suggested simplification
-            - "--default-ttl={{ .Values.ttl.defaultTTL | default "336h" }}"
+            - "--default-ttl={{ .Values.ttl.defaultTTL }}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml` at
line 62, The template currently uses a fallback default inside the TTL
interpolation ("--default-ttl={{ .Values.ttl.defaultTTL | default "336h" }}"),
creating a second source of truth; remove the inline default so the flag
directly uses .Values.ttl.defaultTTL (i.e., "--default-ttl={{
.Values.ttl.defaultTTL }}") and rely on the chart values file to define the
default TTL instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@janitor/main.go`:
- Around line 340-344: The flag DurationVar allowing negative values should be
validated after flags are parsed: check rf.defaultTTL (the variable set by
flag.DurationVar for --default-ttl) and if it is negative (< 0) exit with a
clear error (fail fast) and message stating that --default-ttl must be >= 0; add
this guard early in main() (after flag.Parse) and use the same logging/exit
pattern used elsewhere in janitor/main.go to report the invalid value.

In `@janitor/pkg/ttl/reconciler_test.go`:
- Around line 44-50: The tests use newFakeClient (which wraps
fake.NewClientBuilder) and must be migrated to an envtest-based client that
talks to a real API server; remove or stop using newFakeClient and instead
initialize an envtest.Environment in test setup (e.g., TestMain or a
setupTestEnv helper), start it to obtain a rest.Config, build the scheme with
newScheme(), then create a real controller-runtime client via client.New(cfg,
client.Options{Scheme: scheme}) and use that client in the reconciler tests;
also update any test setup/teardown to Start/Stop the envtest environment and
remove fake-client-specific workarounds around ResourceVersion and subresource
behavior (adjust assertions that expect API-server semantics to use the
envtest-backed client).

In `@janitor/pkg/ttl/reconciler.go`:
- Around line 42-56: The option setters WithClock[T] and WithMetrics[T] can
overwrite safe defaults with nil and cause panics; update WithClock to ignore
nil inputs (do nothing if c == nil) and update WithMetrics to ignore nil
functions (do nothing if fn == nil) so Reconciler's r.clock and r.onDeleted
remain the initialized defaults in NewReconciler; touch the Option[T] closures
for WithClock and WithMetrics to early-return when their parameter is nil,
referencing the Reconciler type and fields r.clock and r.onDeleted to locate the
changes.

---

Nitpick comments:
In `@distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml`:
- Line 62: The template currently uses a fallback default inside the TTL
interpolation ("--default-ttl={{ .Values.ttl.defaultTTL | default "336h" }}"),
creating a second source of truth; remove the inline default so the flag
directly uses .Values.ttl.defaultTTL (i.e., "--default-ttl={{
.Values.ttl.defaultTTL }}") and rely on the chart values file to define the
default TTL instead.

In `@janitor/main.go`:
- Around line 494-519: The three TTL setup blocks duplicate the same pattern;
create a small helper like setupTTL[T any](mgr ctrl.Manager, name string, ttl
time.Duration) that builds opts using ttl.WithDefaultTTL[T](ttl) and
ttl.WithMetrics[T](janitormetrics.GlobalMetrics.IncTTLDeletion) and then calls
ttl.Setup[T](mgr, name, opts...); replace the current calls for RebootNode,
GPUReset and TerminateNode with succinct calls to
setupTTL[*janitordgxcnvidiacomv1alpha1.RebootNode](mgr, "rebootnode-ttl",
defaultTTL) etc., and propagate/return any error from the helper so existing
error logging/formatting (slog.Error and fmt.Errorf) can remain unchanged where
you call it.

In `@janitor/pkg/ttl/ttl_test.go`:
- Around line 48-214: Collapse the many similar TestProcess_* cases into a
single table-driven test (e.g. TestProcess_TableDriven) that iterates over cases
describing name, initialAnnotations, defaultTTL, clockAdvance (duration to
advance the newClock/clk), and expected results (expected Action:
ActionRequeue/ActionNoop/ActionExpired, expected Changed bool, expected
RequeueAfter duration, expected annotation assertions for
TTLAnnotation/ExpiryAnnotation/PreserveAnnotation). For each case call
Process(ctx, rn, defaultTTL, clk) using newRebootNode and newClock (and use
clk.advance where needed), then assert result fields and rn.Annotations per
case; include a case for initializing nil annotation map and for
maxRequeueInterval behavior using maxRequeueInterval, and cover invalid
TTL/Expiry strings and zero TTL; keep existing helper names (Process,
newRebootNode, newClock, clk.advance, TTLAnnotation, ExpiryAnnotation,
PreserveAnnotation, maxRequeueInterval) so assertions and setup map directly to
the original tests.
🪄 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: Pro Plus

Run ID: 3eea23b7-1c16-45d0-abe1-b2b6ca4f9c12

📥 Commits

Reviewing files that changed from the base of the PR and between 0bddacc and b76a848.

📒 Files selected for processing (12)
  • distros/kubernetes/nvsentinel/charts/fault-remediation/values.yaml
  • distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml
  • distros/kubernetes/nvsentinel/charts/janitor/values.yaml
  • distros/kubernetes/nvsentinel/values-full.yaml
  • distros/kubernetes/nvsentinel/values-tilt.yaml
  • janitor/main.go
  • janitor/pkg/metrics/metrics.go
  • janitor/pkg/ttl/reconciler.go
  • janitor/pkg/ttl/reconciler_test.go
  • janitor/pkg/ttl/setup.go
  • janitor/pkg/ttl/ttl.go
  • janitor/pkg/ttl/ttl_test.go

Comment thread janitor/main.go Resolved
Comment thread janitor/pkg/ttl/reconciler_test.go Resolved
Comment thread janitor/pkg/ttl/reconciler.go Resolved
Signed-off-by: Ajay Mishra <ajmishra@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
fault-remediation/pkg/config/config.go (1)

203-204: Avoid repeated sorting inside nested superseding-group checks.

Line 203 re-sorts the same action-name set for each group check, adding avoidable O(n log n) work per call. Consider passing precomputed sorted names from Validate into downstream validators.

♻️ Refactor sketch
 func (c *TomlConfig) Validate() error {
   ...
   actionNames := sortedActionNames(c.RemediationActions)
   ...
   for _, actionName := range actionNames {
-    if err := c.validateSupersedingGroups(actionName, c.RemediationActions[actionName]); err != nil {
+    if err := c.validateSupersedingGroups(actionName, c.RemediationActions[actionName], actionNames); err != nil {
       return err
     }
   }
   return nil
 }

-func (c *TomlConfig) validateSupersedingGroups(actionName string, resource MaintenanceResource) error {
+func (c *TomlConfig) validateSupersedingGroups(
+  actionName string,
+  resource MaintenanceResource,
+  actionNames []string,
+) error {
   for _, group := range resource.SupersedingEquivalenceGroups {
-    if err := validateOneSupersedingGroup(actionName, group, resource, c.RemediationActions); err != nil {
+    if err := validateOneSupersedingGroup(actionName, group, resource, c.RemediationActions, actionNames); err != nil {
       return err
     }
   }
   return nil
 }

 func validateOneSupersedingGroup(
   actionName, group string,
   resource MaintenanceResource,
   remediationActions map[string]MaintenanceResource,
+  actionNames []string,
 ) error {
-  for _, name := range sortedActionNames(remediationActions) {
+  for _, name := range actionNames {
     maintenanceResource := remediationActions[name]
     ...
   }
   ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fault-remediation/pkg/config/config.go` around lines 203 - 204, The loop in
Validate repeatedly calls sortedActionNames(remediationActions) causing
redundant O(n log n) sorts for each superseding-group check; instead compute
sorted := sortedActionNames(remediationActions) once in Validate and pass that
slice into downstream validators/loops (referencing Validate, sortedActionNames,
remediationActions, and the for ... range over sortedActionNames) so all
subsequent checks iterate the precomputed sorted slice rather than re-sorting
each time.
janitor/pkg/ttl/ttl_test.go (1)

53-258: Align test names with the repository test naming format.

A few names (TestProcess, TestKindFromType) don’t fully match the TestFunctionName_Scenario_ExpectedBehavior convention. Renaming will improve consistency and discoverability.

As per coding guidelines, "Name Go tests descriptively using format: TestFunctionName_Scenario_ExpectedBehavior."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@janitor/pkg/ttl/ttl_test.go` around lines 53 - 258, Rename the tests to
follow the repository convention (TestFunctionName_Scenario_ExpectedBehavior):
change TestProcess to a descriptive name like
TestProcess_TableDriven_Scenarios_HandleTTLAndExpiry and change TestKindFromType
to TestKindFromType_CRDAndCoreTypes_ReturnsCorrectKind; update any references to
these symbols (test function names) and run `go test` to ensure there are no
remaining name collisions or references (also apply similar renames to the
related helper/subtests if they rely on the old names).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@fault-remediation/pkg/config/config.go`:
- Around line 203-204: The loop in Validate repeatedly calls
sortedActionNames(remediationActions) causing redundant O(n log n) sorts for
each superseding-group check; instead compute sorted :=
sortedActionNames(remediationActions) once in Validate and pass that slice into
downstream validators/loops (referencing Validate, sortedActionNames,
remediationActions, and the for ... range over sortedActionNames) so all
subsequent checks iterate the precomputed sorted slice rather than re-sorting
each time.

In `@janitor/pkg/ttl/ttl_test.go`:
- Around line 53-258: Rename the tests to follow the repository convention
(TestFunctionName_Scenario_ExpectedBehavior): change TestProcess to a
descriptive name like TestProcess_TableDriven_Scenarios_HandleTTLAndExpiry and
change TestKindFromType to TestKindFromType_CRDAndCoreTypes_ReturnsCorrectKind;
update any references to these symbols (test function names) and run `go test`
to ensure there are no remaining name collisions or references (also apply
similar renames to the related helper/subtests if they rely on the old names).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 56c41796-57bc-4d66-b780-f9d4926bf475

📥 Commits

Reviewing files that changed from the base of the PR and between b76a848 and 3697d32.

📒 Files selected for processing (6)
  • distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml
  • fault-remediation/pkg/config/config.go
  • janitor/main.go
  • janitor/pkg/ttl/reconciler.go
  • janitor/pkg/ttl/reconciler_test.go
  • janitor/pkg/ttl/ttl_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml
  • janitor/pkg/ttl/reconciler_test.go

@github-actions

Copy link
Copy Markdown
Contributor

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/nvidia/nvsentinel/fault-remediation/pkg/config 30.33% (+0.57%) 👍
github.com/nvidia/nvsentinel/janitor 0.00% (ø)
github.com/nvidia/nvsentinel/janitor/pkg/ttl 18.97% (+18.97%) 🎉

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/nvidia/nvsentinel/fault-remediation/pkg/config/config.go 30.33% (+0.57%) 389 (+43) 118 (+15) 271 (+28) 👍
github.com/nvidia/nvsentinel/janitor/main.go 0.00% (ø) 1821 (+346) 0 1821 (+346)
github.com/nvidia/nvsentinel/janitor/pkg/ttl/reconciler.go 18.80% (+18.80%) 367 (+367) 69 (+69) 298 (+298) 🎉

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/nvidia/nvsentinel/janitor/pkg/ttl/reconciler_test.go
  • github.com/nvidia/nvsentinel/janitor/pkg/ttl/ttl_test.go

XRFXLP added 2 commits April 20, 2026 09:40
The "minor changes" commit (3697d32) unintentionally bundled a deterministic
validation refactor of fault-remediation/pkg/config/config.go (sortedActionNames
helper, split Validate into two passes) alongside the legitimate TTL review-fix
changes. That refactor is unrelated to the janitor TTL work and belongs in its
own PR.

Revert fault-remediation/pkg/config/config.go to its pre-3697d32 state so this
PR stays focused on ADR-037 (TTL-based cleanup of janitor CRs).

Signed-off-by: Ajay Mishra <ajmishra@nvidia.com>
Made-with: Cursor
Follow the project convention established in
janitor/pkg/controller/suite_test.go and
plugins/slinky-drainer/pkg/controller/drainrequest_controller_test.go:
reconciler tests use envtest with the real janitor CRDs registered, not
controller-runtime's fake client.

- Replace newFakeClient helper with a TestMain-managed envtest.Environment
  pointing at distros/.../charts/janitor/crds for the CRDs.
- Share a package-level k8sClient across all reconciler tests.
- Switch tests from fake.NewClientBuilder().WithObjects(...) to
  k8sClient.Create(...) with per-test cleanup hooks to avoid cross-test
  leakage (CRs are cluster-scoped, so unique names via t.Name() are used).
- Adjust the deletion-in-progress test to trigger deletion through the real
  API server (Create with finalizer + Delete) rather than setting
  DeletionTimestamp directly.
- Discard controller-runtime logger during tests.

Pure-unit tests that don't interact with the API server (TestProcess,
TestReconciler_NilOptionsIgnored, TestKindFromType, TestNewZeroRef_*) keep
their in-memory structure; envtest is transparently available to them via
the shared setup.

Signed-off-by: Ajay Mishra <ajmishra@nvidia.com>
Made-with: Cursor
@github-actions

Copy link
Copy Markdown
Contributor

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/nvidia/nvsentinel/janitor/pkg/ttl 18.74% (+18.74%) 🎉

Coverage by file

Changed unit test files

  • github.com/nvidia/nvsentinel/janitor/pkg/ttl/reconciler_test.go

XRFXLP added 2 commits April 20, 2026 11:25
TestJanitorNodeLocking's "different nodes should overlap" assertion used a
strict time.Time.Before() comparison, which rejects the boundary case where
the second RebootNode starts at the exact same RFC3339 second that the first
finishes. On fast KWOK/kind runs this intermittently fails — not because
the janitor serialized operations across nodes (the behavior under test),
but because the two intervals happened to touch at a one-second boundary.

Relax the assertion to !After on both bounds so touching intervals count as
"not serialized". The strict Before is kept on the same-node pair, where
exact-touch is still a legitimate "did not overlap" answer.

Signed-off-by: Ajay Mishra <ajmishra@nvidia.com>
Made-with: Cursor
@XRFXLP XRFXLP self-assigned this Apr 20, 2026
@XRFXLP XRFXLP linked an issue Apr 20, 2026 that may be closed by this pull request
1 task done
Comment thread janitor/pkg/ttl/reconciler.go Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
janitor/main.go (1)

518-531: Optional: table-drive the per-kind TTL setup to reduce repetition.

Current logic is correct; a small descriptor slice would make future CR-kind additions less error-prone.

♻️ Example refactor
 func registerTTLReconcilers(mgr ctrl.Manager, enabled bool, defaultTTL time.Duration) error {
 	if !enabled {
 		slog.Info("TTL reconcilers disabled; maintenance CRs will not be auto-deleted")

 		return nil
 	}

-	if err := setupTTL[*janitordgxcnvidiacomv1alpha1.RebootNode](
-		mgr, "rebootnode-ttl", "RebootNode", defaultTTL); err != nil {
-		return err
-	}
-
-	if err := setupTTL[*janitordgxcnvidiacomv1alpha1.GPUReset](
-		mgr, "gpureset-ttl", "GPUReset", defaultTTL); err != nil {
-		return err
-	}
-
-	if err := setupTTL[*janitordgxcnvidiacomv1alpha1.TerminateNode](
-		mgr, "terminatenode-ttl", "TerminateNode", defaultTTL); err != nil {
-		return err
-	}
+	if err := setupTTL[*janitordgxcnvidiacomv1alpha1.RebootNode](mgr, "rebootnode-ttl", "RebootNode", defaultTTL); err != nil {
+		return err
+	}
+	if err := setupTTL[*janitordgxcnvidiacomv1alpha1.GPUReset](mgr, "gpureset-ttl", "GPUReset", defaultTTL); err != nil {
+		return err
+	}
+	if err := setupTTL[*janitordgxcnvidiacomv1alpha1.TerminateNode](mgr, "terminatenode-ttl", "TerminateNode", defaultTTL); err != nil {
+		return err
+	}

 	slog.Info("TTL reconcilers registered for RebootNode, GPUReset, TerminateNode",
 		"default-ttl", defaultTTL)

 	return nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@janitor/main.go` around lines 518 - 531, Replace the three repetitive
setupTTL calls with a single loop-driven construct: define a small slice of
descriptors (each holding the display name string, the TTL key string, and the
resource type identifier) for RebootNode, GPUReset, and TerminateNode and
iterate it to call setupTTL for each entry; keep using mgr and defaultTTL and
invoke setupTTL[*janitordgxcnvidiacomv1alpha1.RebootNode] /
setupTTL[*janitordgxcnvidiacomv1alpha1.GPUReset] /
setupTTL[*janitordgxcnvidiacomv1alpha1.TerminateNode] appropriately when
constructing the slice entries so the existing setupTTL, mgr, and defaultTTL
usages remain unchanged while eliminating repeated code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@janitor/main.go`:
- Around line 518-531: Replace the three repetitive setupTTL calls with a single
loop-driven construct: define a small slice of descriptors (each holding the
display name string, the TTL key string, and the resource type identifier) for
RebootNode, GPUReset, and TerminateNode and iterate it to call setupTTL for each
entry; keep using mgr and defaultTTL and invoke
setupTTL[*janitordgxcnvidiacomv1alpha1.RebootNode] /
setupTTL[*janitordgxcnvidiacomv1alpha1.GPUReset] /
setupTTL[*janitordgxcnvidiacomv1alpha1.TerminateNode] appropriately when
constructing the slice entries so the existing setupTTL, mgr, and defaultTTL
usages remain unchanged while eliminating repeated code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ef5e8046-843b-4461-822e-e4ea5e55ff83

📥 Commits

Reviewing files that changed from the base of the PR and between da58baa and e330496.

📒 Files selected for processing (4)
  • distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml
  • distros/kubernetes/nvsentinel/charts/janitor/values.yaml
  • docs/designs/037-janitor-cr-ttl-cleanup.md
  • janitor/main.go
✅ Files skipped from review due to trivial changes (2)
  • distros/kubernetes/nvsentinel/charts/janitor/templates/deployment.yaml
  • docs/designs/037-janitor-cr-ttl-cleanup.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • distros/kubernetes/nvsentinel/charts/janitor/values.yaml

Signed-off-by: Ajay Mishra <ajmishra@nvidia.com>
@github-actions

Copy link
Copy Markdown
Contributor

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/nvidia/nvsentinel/janitor/pkg/ttl 18.79% (+18.79%) 🎉

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/nvidia/nvsentinel/janitor/pkg/ttl/reconciler.go 18.67% (+18.67%) 498 (+498) 93 (+93) 405 (+405) 🎉

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

@lalitadithya
lalitadithya merged commit ef144ba into NVIDIA:main Apr 20, 2026
74 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically locked since it has been closed for 90 days with no further activity. Please open a new pull request for related changes.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jul 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support auto clean up on stale Janitor CRs

2 participants