Skip to content

feat(fault-quarantine): gate events for opted-out nodes via skipNodeL… - #1572

Open
cbumb wants to merge 2 commits into
NVIDIA:mainfrom
cbumb:NKX-13877_impl
Open

feat(fault-quarantine): gate events for opted-out nodes via skipNodeL…#1572
cbumb wants to merge 2 commits into
NVIDIA:mainfrom
cbumb:NKX-13877_impl

Conversation

@cbumb

@cbumb cbumb commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a configurable skipNodeLabels list to fault-quarantine that silently drops all events (healthy and unhealthy) for nodes carrying matching labels. This prevents opted-out nodes (e.g. nodes under external remediation via ExtRR) from being quarantined or uncordoned by fault-quarantine. Default config ships with nvsentinel.dgxc.nvidia.com/managed=false. Updates ADR-040 to reflect the fault-quarantine approach for cluster-scope monitor gating.

Known tradeoff: Because the gate is in fault-quarantine rather than at the monitor/platform-connector level, node status conditions set by platform-connectors may become stale for nodes under an ERR. This is a reasonable tradeoff since events should still be "observed, recorded, and exported as usual" per ADR-040, and the guard only applies to state-changing actions (quarantine, uncordon, annotation updates).

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

    • Added configurable node-label exclusions for fault quarantine.
    • Nodes matching configured labels are excluded from quarantine and uncordoning actions.
    • Added default handling for nodes labeled as unmanaged.
    • Added metrics to track events skipped due to node labels.
  • Documentation

    • Updated external remediation behavior and failure-mode documentation.
  • Tests

    • Added end-to-end coverage for label matching and remediation lifecycle changes.

@copy-pr-bot

copy-pr-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Fault-quarantine now skips events for nodes matching configured label key/value pairs. Helm and TOML configuration support the labels. Metrics and tracing record skipped events. End-to-end tests cover filtering and ExternalRemediationRequest lifecycle transitions.

Changes

Node-label suppression

Layer / File(s) Summary
Configuration and behavior contract
fault-quarantine/pkg/config/config.go, distros/kubernetes/nvsentinel/charts/fault-quarantine/..., docs/designs/040-external-remediation-request.md
Adds SkipNodeLabels configuration with a default managed=false label. Updates the documented filtering behavior for quarantine, uncordon, annotations, and eviction-window events.
Reconciler filtering and metrics
fault-quarantine/pkg/reconciler/reconciler.go, fault-quarantine/pkg/metrics/metrics.go
Skips matching node events before quarantine or uncordon handling. Records tracing attributes and increments a per-label skip counter.
End-to-end behavior validation
fault-quarantine/pkg/reconciler/reconciler_e2e_test.go, tests/managed_optout_test.go
Tests matching labels, nonmatching values, empty configuration, cache misses, healthy events, and ExternalRemediationRequest lifecycle transitions.

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

Sequence Diagram(s)

sequenceDiagram
  participant HealthEvent
  participant ProcessEvent
  participant NodeInformer
  participant EventsSkippedNodeLabel
  HealthEvent->>ProcessEvent: submit node health event
  ProcessEvent->>NodeInformer: read cached node labels
  NodeInformer-->>ProcessEvent: return labels
  ProcessEvent->>EventsSkippedNodeLabel: increment skip counter
  ProcessEvent-->>HealthEvent: drop matching event
Loading

Possibly related PRs

Suggested labels: area/fault-management, area/deployment, area/docs, area/tests, size/L

🚥 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 describes gating fault-quarantine events for opted-out nodes through skipNodeLabels, which is the primary change.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% 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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@cbumb cbumb closed this Aug 6, 2026
@cbumb cbumb reopened this Aug 6, 2026

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

🧹 Nitpick comments (2)
fault-quarantine/pkg/reconciler/reconciler.go (1)

531-535: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a descriptive loop variable.

Rename sl to skipNodeLabel. This name identifies the configured value used for the label comparison and metric.

As per coding guidelines, “Use meaningful variable names in Go code (e.g., synced over ok for cache sync checks).”

🤖 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 `@fault-quarantine/pkg/reconciler/reconciler.go` around lines 531 - 535, Rename
the loop variable in the SkipNodeLabels iteration from sl to skipNodeLabel, and
update its Key and Value references in the label comparison, logging, and metric
call while preserving the existing behavior.

Source: Coding guidelines

fault-quarantine/pkg/reconciler/reconciler_e2e_test.go (1)

6722-6722: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the required test-name pattern.

Add an explicit scenario and expected behavior to each test name. Add exported-function comments for the four new fault-quarantine test functions.

  • fault-quarantine/pkg/reconciler/reconciler_e2e_test.go#L6722-L6722: rename to include that the unhealthy event is skipped.
  • fault-quarantine/pkg/reconciler/reconciler_e2e_test.go#L6788-L6788: rename to include that the healthy event does not uncordon.
  • fault-quarantine/pkg/reconciler/reconciler_e2e_test.go#L6887-L6887: rename to include that an empty configuration quarantines the node.
  • fault-quarantine/pkg/reconciler/reconciler_e2e_test.go#L6945-L6945: rename to include that a nonmatching value quarantines the node.
  • tests/managed_optout_test.go#L40-L40: rename to the TestFunctionName_Scenario_ExpectedBehavior pattern.

As per coding guidelines, “Name tests descriptively following the pattern TestFunctionName_Scenario_ExpectedBehavior in Go” and “Include function comments for exported Go functions.”

🤖 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 `@fault-quarantine/pkg/reconciler/reconciler_e2e_test.go` at line 6722, Rename
the four tests at
fault-quarantine/pkg/reconciler/reconciler_e2e_test.go:6722-6722, 6788-6788,
6887-6887, and 6945-6945 to follow TestFunctionName_Scenario_ExpectedBehavior,
explicitly describing respectively the skipped unhealthy event, healthy event
not uncordoning, empty configuration quarantining the node, and nonmatching
value quarantining the node; add exported-function comments for each. Rename the
test at tests/managed_optout_test.go:40-40 to the same descriptive pattern,
preserving its scenario and expected behavior.

Source: Coding guidelines

🤖 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 `@distros/kubernetes/nvsentinel/charts/fault-quarantine/values.yaml`:
- Around line 70-76: Remove the hard-coded managed=false CEL filters from the
rule templates referenced by the skipNodeLabels configuration, including the
expressions around NodeEventRules, quarantine, uncordon, and related actions.
Ensure the managed label exclusion is enforced only through skipNodeLabels, so
setting it to an empty list removes the exclusion while preserving other
configured label filters.

In `@docs/designs/040-external-remediation-request.md`:
- Around line 305-311: Update the remaining release-taint timing section to
remove references to cluster-scope monitors stopping emission and any
release-taint guard handling late events. Describe that cluster-scope monitors
continue emitting events, while fault-quarantine centrally checks configured
skipNodeLabels and silently drops matching healthy or unhealthy events before
state changes.

In `@fault-quarantine/pkg/reconciler/reconciler_e2e_test.go`:
- Around line 6836-6839: Update each Eventually callback in
fault-quarantine/pkg/reconciler/reconciler_e2e_test.go at lines 6836-6839,
6939-6942, and 7000-7003 to check the Nodes().Get error before accessing
node.Spec.Unschedulable; return err == nil && node.Spec.Unschedulable in all
three sites.

In `@fault-quarantine/pkg/reconciler/reconciler.go`:
- Around line 525-529: The ownership decision in the reconciler’s node lookup
must not rely solely on the potentially stale NodeInformer cache. In the
state-changing event path around GetNode, perform an authoritative or causally
synchronized label/ownership check after the ExtRR PATCH succeeds, and use that
result to decide whether to skip processing; preserve the existing behavior for
nodes confirmed as owned or released.

---

Nitpick comments:
In `@fault-quarantine/pkg/reconciler/reconciler_e2e_test.go`:
- Line 6722: Rename the four tests at
fault-quarantine/pkg/reconciler/reconciler_e2e_test.go:6722-6722, 6788-6788,
6887-6887, and 6945-6945 to follow TestFunctionName_Scenario_ExpectedBehavior,
explicitly describing respectively the skipped unhealthy event, healthy event
not uncordoning, empty configuration quarantining the node, and nonmatching
value quarantining the node; add exported-function comments for each. Rename the
test at tests/managed_optout_test.go:40-40 to the same descriptive pattern,
preserving its scenario and expected behavior.

In `@fault-quarantine/pkg/reconciler/reconciler.go`:
- Around line 531-535: Rename the loop variable in the SkipNodeLabels iteration
from sl to skipNodeLabel, and update its Key and Value references in the label
comparison, logging, and metric call while preserving the existing behavior.
🪄 Autofix

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: fea49da3-0299-4388-b667-b4fa810f1d29

📥 Commits

Reviewing files that changed from the base of the PR and between 362e7ad and 52abfb5.

📒 Files selected for processing (8)
  • distros/kubernetes/nvsentinel/charts/fault-quarantine/templates/configmap.yaml
  • distros/kubernetes/nvsentinel/charts/fault-quarantine/values.yaml
  • docs/designs/040-external-remediation-request.md
  • fault-quarantine/pkg/config/config.go
  • fault-quarantine/pkg/metrics/metrics.go
  • fault-quarantine/pkg/reconciler/reconciler.go
  • fault-quarantine/pkg/reconciler/reconciler_e2e_test.go
  • tests/managed_optout_test.go

Comment thread distros/kubernetes/nvsentinel/charts/fault-quarantine/values.yaml
Comment thread docs/designs/040-external-remediation-request.md
Comment thread fault-quarantine/pkg/reconciler/reconciler_e2e_test.go
Comment thread fault-quarantine/pkg/reconciler/reconciler.go
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/nvidia/nvsentinel/fault-quarantine/pkg/config 0.00% (ø)
github.com/nvidia/nvsentinel/fault-quarantine/pkg/metrics 47.37% (ø)
github.com/nvidia/nvsentinel/fault-quarantine/pkg/reconciler 25.56% (+0.23%) 👍
github.com/nvidia/nvsentinel/tests 0.00% (ø)

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/nvidia/nvsentinel/fault-quarantine/pkg/config/config.go 0.00% (ø) 0 0 0
github.com/nvidia/nvsentinel/fault-quarantine/pkg/metrics/metrics.go 47.37% (ø) 19 9 10
github.com/nvidia/nvsentinel/fault-quarantine/pkg/reconciler/reconciler.go 25.56% (+0.23%) 4116 (+89) 1052 (+32) 3064 (+57) 👍

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/fault-quarantine/pkg/reconciler/reconciler_e2e_test.go
  • github.com/nvidia/nvsentinel/tests/managed_optout_test.go

@lalitadithya

Copy link
Copy Markdown
Collaborator

why can't this be dropped at the platform connector layer since that has access to the node labels IIRC?

@cbumb

cbumb commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

why can't this be dropped at the platform connector layer since that has access to the node labels IIRC?

MetadataAugmentor caches node data for up to an hour, so the label change wouldn't take effect for up to an hour in either direction.
OverrideTransformer can only set three fields (isFatal, isHealthy, recommendedAction) and cannot set ProcessingStrategy.
Both pieces would need code changes to actually work.

@lalitadithya

Copy link
Copy Markdown
Collaborator

MetadataAugmentor caches node data for up to an hour, so the label change wouldn't take effect for up to an hour in either direction.

we could always cache this specific label for a shorter TTL. We have to keep in mind that the system will be eventually consistent, and we can't fire off a get call for every health event. The FQ also has an informer which is backed by a cache

@cbumb

cbumb commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

MetadataAugmentor caches node data for up to an hour, so the label change wouldn't take effect for up to an hour in either direction.

we could always cache this specific label for a shorter TTL. We have to keep in mind that the system will be eventually consistent, and we can't fire off a get call for every health event. The FQ also has an informer which is backed by a cache

Yes both are eventually consistent. The difference is the lag. If I am not wrong FQ's informer uses a watch, so the API server pushes label changes in sub-second. A TTL cache is poll-based, so the lag equals the full TTL no matter how short we set it. Even at 30s TTL, that's 30 seconds of wrong behavior on every label flip vs under a second with the informer right?

And I also confirmed with Ajay that informers on a DaemonSet are wasteful, the only option in platform-connector would be the TTL cache. FQ is a single Deployment, so one informer is fine.

Both approaches work, but I thought the informer in FQ gives tighter consistency with less infrastructure cost.

@lalitadithya

Copy link
Copy Markdown
Collaborator

If I am not wrong FQ's informer uses a watch, so the API server pushes label changes in sub-second. A TTL cache is poll-based, so the lag equals the full TTL no matter how short we set it. Even at 30s TTL, that's 30 seconds of wrong behavior on every label flip vs under a second with the informer right?

yes, but there is no guarantee. a watch drop can also result in a larger lag. Yes, we can't do informers in the daemonset it will have to be TTL based.

Now that I think about it more, I'm not sure if the platform connector is the right place either. We also have to keep in mind that the number of cluster wide monitors are limited to currently HEA, CSP and KOM. The HEA case is an easy one because it doesn't publish a healthy event, so dropping the event is good enough. The CSP and KOM will need some thought because as I understand it KOM keeps state on the node object so dropping events out of it will cause the state to go out of sync with reality. The CSP health monitor also has some state that it maintains in the database. So, I would like to understand what the implications of having the checks in the monitor itself and making sure state logic if any accounts for this?

@cbumb

cbumb commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

So, I would like to understand what the implications of having the checks in the monitor itself and making sure state logic if any accounts for this?

Yeah that's the thing, each monitor handles state differently. KOM has state on the node object, CSP has state in the database, and HEA is the only clean one. Accounting for all of that means three separate implementations with different state cleanup logic.

That's why I went with fault-quarantine. Monitors keep running normally and their internal state stays consistent. Events still flow through and get stored for audit. FQ just decides not to act on opted-out nodes. Nothing goes out of sync, and also future monitors get the same behavior without opting in. We also dont need to worry about someone not adding the logic in the future monitors.

@lalitadithya

Copy link
Copy Markdown
Collaborator

Yeah that's the thing, each monitor handles state differently. KOM has state on the node object, CSP has state in the database, and HEA is the only clean one. Accounting for all of that means three separate implementations with different state cleanup logic.

correct, this is exactly why I think it has to be per health monitor. If KOM has state saying that the node is bad and it uses that to dedup and we drop the unhealthy event then the node will never be marked once it returns. Same with the case of the CSP health monitor. I don't think we can account for these types of nuances centrally unless we build a backlog of unprocessed events and then reprocess them like cold start, but that is going to get complex quickly

@natherz97

Copy link
Copy Markdown
Contributor

Adding to platform-connector or fault-quarantine are the only approaches which enforce comprehensive coverage for all health events, otherwise any health-monitor which is added would need knowledge of this label. Right now the fault-quarantine rule-set is the mechanism we have to indicate whether a given unhealthy event should contribute to a node's quarantine session. We do not have an equivalent to apply to healthy events so this PR is the first step that applies filtering across all events (I think we could add a full rule-set evaluation for healthy events or for all events later if we need functionality beyond node labels). Independent of this use-case it does seem like we should have this equivalent for healthy events.

I'm trying to think what we want to do in the short-term since if we added this functionality to the global monitors without any optimizations about when it sends events for a node (such as queuing events or force-sending a healthy event when the label is removed), we'd get the same behavior as keeping it in fault-quarantine with the tradeoff of having to implement it 3 times.

@cbumb

cbumb commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

correct, this is exactly why I think it has to be per health monitor. If KOM has state saying that the node is bad and it uses that to dedup and we drop the unhealthy event then the node will never be marked once it returns. Same with the case of the CSP health monitor. I don't think we can account for these types of nuances centrally unless we build a backlog of unprocessed events and then reprocess them like cold start, but that is going to get complex quickly

That's a fair concern. But I think the window is pretty narrow. After external remediation, the fault should normally be fixed, so the monitor would see the state change and publish a healthy event, which clears the dedup. The case where this breaks is if the fault persists after remediation AND only a cluster-scope monitor can detect it AND its dedup prevents re-publishing. That's a pretty specific edge case right?

Also If I am not wrong DaemonSet monitors cold-start when the managed label is removed, so they re-detect everything fresh. This only applies to the cluster-scope ones.

And if we do put the check in each monitor, we'd need to build that "remember to re-publish later" logic into each one differently, which is the same state complexity you were calling out earlier.

If we're really worried about the re-detection case, I think a simpler path would be to add something in FQ that re-processes stored events when a node's skip label is removed, rather than building custom state management into each monitor. But I'd treat that as a follow-up if it turns out to be a real problem.

@cbumb

cbumb commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

correct, this is exactly why I think it has to be per health monitor. If KOM has state saying that the node is bad and it uses that to dedup and we drop the unhealthy event then the node will never be marked once it returns. Same with the case of the CSP health monitor. I don't think we can account for these types of nuances centrally unless we build a backlog of unprocessed events and then reprocess them like cold start, but that is going to get complex quickly

That's a fair concern. But I think the window is pretty narrow. After external remediation, the fault should normally be fixed, so the monitor would see the state change and publish a healthy event, which clears the dedup. The case where this breaks is if the fault persists after remediation AND only a cluster-scope monitor can detect it AND its dedup prevents re-publishing. That's a pretty specific edge case right?

Also If I am not wrong DaemonSet monitors cold-start when the managed label is removed, so they re-detect everything fresh. This only applies to the cluster-scope ones.

And if we do put the check in each monitor, we'd need to build that "remember to re-publish later" logic into each one differently, which is the same state complexity you were calling out earlier.

If we're really worried about the re-detection case, I think a simpler path would be to add something in FQ that re-processes stored events when a node's skip label is removed, rather than building custom state management into each monitor. But I'd treat that as a follow-up if it turns out to be a real problem.

I think the last point coincides with Nathan's comment

@lalitadithya

Copy link
Copy Markdown
Collaborator

if we would like to break this into two parts that is fine with me, we could start off with dropping the health events in a central place and then as a follow up we can have per health monitor handling if required. For this as well, I'm not sure if FQ is the right home for this logic because this means we drop the events after some part of the system and some downstream consumers have already consumed it. Specifically, this would mean that node conditions, the event exporter and the health event analyzer and anything downstream of these such as an observability stack or an analytics pipeline. Dropping it in the earliest possible place I think would be the most ideal so that we never end up in a state where part of the system emit signals while others don't. Previously this not much of a concern because the behaviour was consistent between per node health monitors and cluster wide health monitors. But now with the more recent changes the per node health monitor are evicted which would mean they will not suffer the similar issue.

We could add a delayed processing strategy to the health events which would work as well, but that is something we'd probably want to do as a follow up if we have more use cases outside of this.

I agree that having this per health monitor does complicate the logic, but in an ideal world we should have minimal number of cluster wide health monitors since most of the faults can be detected on a per node level. With that said, I also think it would be simpler than having to do event replays in FQ given that right now the biggest source for event replay would likely be the CSP health monitor since those deals with times for maintenance and it is entirely possible that a future maint notification comes while a node is being rebooted and those can't be dropped. Similar case would be with the KOM which monitoring node conditions that are set by an external entity in response to a change outside of the node/cluster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants