Skip to content

OADP filtering via labelSelector and orLabelSelectors#186

Draft
michalskrivanek wants to merge 4 commits intoopenshift:mainfrom
michalskrivanek:label
Draft

OADP filtering via labelSelector and orLabelSelectors#186
michalskrivanek wants to merge 4 commits intoopenshift:mainfrom
michalskrivanek:label

Conversation

@michalskrivanek
Copy link

@michalskrivanek michalskrivanek commented Feb 24, 2026

  • feat: respect backup labelSelector when pausing/unpausing HCs and NPs
  • test: add labelSelector filtering tests for HC and NP pause
  • feat: support orLabelSelectors for pause filtering

What this PR does / why we need it

Which issue(s) this PR fixes

Fixes #

Checklist

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Summary by CodeRabbit

  • New Features

    • Label-based filtering for pause/unpause: backup and restore operations can target hosted clusters and node pools with label selectors; only matching resources are processed and counts are reported in logs.
    • More robust update behavior with improved conflict handling to reduce failed update attempts.
  • Tests

    • Added tests verifying label-selector filtering for hosted clusters and node pools.

michalskrivanek and others added 3 commits February 23, 2026 15:17
When multiple HostedClusters coexist in a shared namespace, the plugin
previously paused all HCs/NPs found in the included namespaces regardless
of which cluster the backup targeted. This passes the backup's
LabelSelector through to the List calls so only matching resources are
affected, preventing concurrent backups from interfering with each other.
Requires someone to set the right labels and corresponding labelSelector.

Signed-off-by: Michal Skrivanek <michal.skrivanek@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michal Skrivanek <michal.skrivanek@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
noteit only suppots specific pattern

orLabelSelectors:
  - matchExpressions:                              # "unlabeled resources"
    - key: hypershift.openshift.io/hosted-control-plane
      operator: DoesNotExist
  - matchExpressions:                              # "this cluster's resources"
    - key: hypershift.openshift.io/hosted-control-plane
      operator: In
      values: ["cluster-1"]

Signed-off-by: Michal Skrivanek <michal.skrivanek@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between f2dd5f2 and eb8c8fc.

📒 Files selected for processing (1)
  • pkg/common/utils.go

Walkthrough

Adds a ResourceFilter type (Velero-style AND/OR label selectors), a constructor and Matches method; threads the filter into UpdateHostedCluster, UpdateNodepools and updateHypershiftResource; applies client-side label filtering when listing resources and updates call sites in backup and restore to build and pass the filter.

Changes

Cohort / File(s) Summary
Filter implementation & tests
pkg/common/utils.go, pkg/common/utils_test.go
Adds ResourceFilter type, NewResourceFilter(...) constructor, and Matches(labels) method. Updates UpdateHostedCluster, UpdateNodepools, and updateHypershiftResource signatures to accept a *ResourceFilter. Implements client-side label filtering during resource listing and enhanced update loop/conflict handling. Adds tests TestLabelSelectorHostedCluster and TestLabelSelectorNodepools.
Backup integration
pkg/core/backup.go
Adds backupFilter helper to construct a ResourceFilter from a Backup's label selectors; passes the filter into pauseAll/unPauseAll flows by providing it to UpdateHostedCluster and UpdateNodepools; surfaces filter construction errors.
Restore integration
pkg/core/restore.go
Creates a ResourceFilter from restore labels and propagates it to calls that update NodePools and HostedClusters; adds error handling for filter creation failures.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Operator
participant CommonUtils
participant KubernetesAPI
Operator->>CommonUtils: NewResourceFilter(labelSelector, orLabelSelectors)
Operator->>CommonUtils: UpdateHostedCluster(..., filter) / UpdateNodepools(..., filter)
CommonUtils->>KubernetesAPI: List resources (unfiltered)
KubernetesAPI-->>CommonUtils: Resource list
CommonUtils->>CommonUtils: Filter items with ResourceFilter.Matches()
CommonUtils->>KubernetesAPI: Patch/Update matched resources (with audit annotations & retry)
KubernetesAPI-->>CommonUtils: Update responses / conflicts
CommonUtils->>Operator: Report updated/skipped counts

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ❓ Inconclusive Test files referenced in PR summary (pkg/common/utils_test.go) could not be located in the repository despite extensive investigation. Provide access to the actual test files or share git diff output showing test implementations to enable assessment against quality criteria.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding label-based filtering (labelSelector and orLabelSelectors) to OADP plugin functionality, which aligns with the primary objective of respecting backup resource selectors when pausing/unpausing HostedClusters and NodePools.
Stable And Deterministic Test Names ✅ Passed All test names are stable and deterministic with no dynamic values like generated identifiers, timestamps, or runtime-dependent information.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@openshift-ci openshift-ci bot requested review from jparrill and kaovilai February 24, 2026 08:14
@openshift-ci
Copy link

openshift-ci bot commented Feb 24, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: michalskrivanek
Once this PR has been reviewed and has the lgtm label, please assign csrwng for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Feb 24, 2026
@openshift-ci
Copy link

openshift-ci bot commented Feb 24, 2026

Hi @michalskrivanek. Thanks for your PR.

I'm waiting for a openshift member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@michalskrivanek michalskrivanek marked this pull request as draft February 24, 2026 08:15
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Feb 24, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
pkg/common/utils_test.go (2)

212-213: Avoid silently ignoring the error from NewResourceFilter.

NewResourceFilter(nil, nil) never errors today, but the _, _ pattern is easily copied into cases where the selector could be invalid. Assign the error and assert it.

♻️ Proposed fix (representative — apply consistently at all four call sites)
-filter, _ := NewResourceFilter(nil, nil)
+filter, err := NewResourceFilter(nil, nil)
+g.Expect(err).NotTo(HaveOccurred())

Also applies to: 373-374, 455-455, 533-533

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

In `@pkg/common/utils_test.go` around lines 212 - 213, The tests currently call
NewResourceFilter(nil, nil) and ignore its error return with the "_, _" pattern;
change each call (including the ones around UpdateHostedCluster in the provided
snippet and the other three occurrences) to capture the error (e.g., filter, err
:= NewResourceFilter(...)) and add an assertion/check that err is nil before
proceeding (fail the test if non-nil) so invalid selector errors are not
silently ignored; keep references to NewResourceFilter and UpdateHostedCluster
to locate the call sites.

396-550: Missing test coverage for orLabelSelectors — the core new feature.

The PR commits state support for orLabelSelectors (e.g., DoesNotExist to select unlabeled resources OR In to select a named cluster), but TestLabelSelectorHostedCluster and TestLabelSelectorNodepools only exercise single labelSelector. An orLabelSelectors test case such as the one below would directly validate the OR-semantics path through NewResourceFilter and Matches:

// Example test case to add to TestLabelSelectorHostedCluster.tests
{
    name: "orLabelSelectors selects unlabeled OR cluster dummy-1",
    orLabelSelectors: []*metav1.LabelSelector{
        // arm 1: unlabeled (DoesNotExist)
        {MatchExpressions: []metav1.LabelSelectorRequirement{
            {Key: "my-label/cluster-name", Operator: metav1.LabelSelectorOpDoesNotExist},
        }},
        // arm 2: cluster dummy-1 (In)
        {MatchExpressions: []metav1.LabelSelectorRequirement{
            {Key: "my-label/cluster-name", Operator: metav1.LabelSelectorOpIn, Values: []string{"dummy-1"}},
        }},
    },
    expectPaused: map[string]bool{"dummy-1": true, "dummy-2": false, "dummy-3": true},
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/utils_test.go` around lines 396 - 550, Tests for orLabelSelectors
are missing; add cases exercising OR semantics so NewResourceFilter and its
Matches path are validated. Update TestLabelSelectorHostedCluster and
TestLabelSelectorNodepools to include a test case with an orLabelSelectors slice
(e.g., one arm with MatchExpressions using LabelSelectorOpDoesNotExist for
"my-label/cluster-name" and another arm using LabelSelectorOpIn with value
"dummy-1"), call NewResourceFilter(...) with that orLabelSelectors, run
UpdateHostedCluster/UpdateNodepools, and assert expectations per resource name
to ensure or-logic selects unlabeled resources OR the named cluster/nodepool as
intended.
pkg/core/restore.go (1)

178-189: Consider hoisting the filter out of the per-item Execute path.

The filter is reconstructed from the same (immutable) input.Restore.Spec on every invocation of Execute for a MainKinds resource. The cost is negligible today, but caching the compiled *common.ResourceFilter on RestorePlugin (analogous to how BackupPlugin could cache its filter) would make the intent clearer and avoid repeated parsing.

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

In `@pkg/core/restore.go` around lines 178 - 189, The code repeatedly reconstructs
the ResourceFilter inside Execute for MainKinds; hoist and cache it on
RestorePlugin instead so it's built once from
input.Restore.Spec.LabelSelector/OrLabelSelectors and reused. Add a field (e.g.,
resourceFilter *common.ResourceFilter) to RestorePlugin, initialize it when the
plugin is created (or lazily on first Execute by calling
common.NewResourceFilter with input.Restore.Spec.LabelSelector and
OrLabelSelectors), and replace the local NewResourceFilter call in Execute with
the cached RestorePlugin.resourceFilter before calling common.UpdateNodepools
and common.UpdateHostedCluster.
pkg/common/utils.go (1)

614-647: Add a default case to the paused switch to avoid a silent no-op update.

When paused is neither "" nor "true" (e.g., an RFC3339 timestamp, which is a valid HyperShift PausedUntil value), needsUpdate can be true but pausedUntilSetter is never invoked. The code then calls c.Update without mutating the field — the resource appears perpetually stale, burns all 10 retry attempts, and surfaces an error.

♻️ Proposed fix
 switch paused {
 case "":
     log.Infof("setting PauseUntil to nil (unpause) in %s %s", resourceType, current.GetName())
     pausedUntilSetter(current, nil)
     removePauseAuditAnnotations(current)
 case "true":
     log.Infof("setting PauseUntil to %s in %s %s", paused, resourceType, current.GetName())
     pausedUntilSetter(current, ptr.To(paused))
     addPauseAuditAnnotations(current)
+default:
+    // Arbitrary date/time pause value; set the field but skip audit annotations.
+    log.Infof("setting PauseUntil to %s in %s %s", paused, resourceType, current.GetName())
+    pausedUntilSetter(current, ptr.To(paused))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/utils.go` around lines 614 - 647, The switch on paused only
handles "" and "true", causing a no-op when paused is an RFC3339 timestamp; add
a default case in that switch (near the paused switch handling in the function
that computes needsUpdate) that calls pausedUntilSetter(current, ptr.To(paused))
and invokes addPauseAuditAnnotations(current) (same behavior as the "true"
branch) so the resource is actually mutated before calling c.Update; ensure this
default is used when paused is neither "" nor "true" so needsUpdate results in a
real field change and postUpdateHook/c.Update behave correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/common/utils.go`:
- Around line 536-547: ResourceFilter.Matches should guard against a nil
receiver to avoid a panic when callers like UpdateHostedCluster or
UpdateNodepools pass a nil *ResourceFilter; update the start of the Matches
method (ResourceFilter.Matches) to check if f == nil (and keep the existing
empty-selectors check) and return true for a nil/empty filter so callers that
pass nil will match safely without dereferencing f.selectors.

---

Nitpick comments:
In `@pkg/common/utils_test.go`:
- Around line 212-213: The tests currently call NewResourceFilter(nil, nil) and
ignore its error return with the "_, _" pattern; change each call (including the
ones around UpdateHostedCluster in the provided snippet and the other three
occurrences) to capture the error (e.g., filter, err := NewResourceFilter(...))
and add an assertion/check that err is nil before proceeding (fail the test if
non-nil) so invalid selector errors are not silently ignored; keep references to
NewResourceFilter and UpdateHostedCluster to locate the call sites.
- Around line 396-550: Tests for orLabelSelectors are missing; add cases
exercising OR semantics so NewResourceFilter and its Matches path are validated.
Update TestLabelSelectorHostedCluster and TestLabelSelectorNodepools to include
a test case with an orLabelSelectors slice (e.g., one arm with MatchExpressions
using LabelSelectorOpDoesNotExist for "my-label/cluster-name" and another arm
using LabelSelectorOpIn with value "dummy-1"), call NewResourceFilter(...) with
that orLabelSelectors, run UpdateHostedCluster/UpdateNodepools, and assert
expectations per resource name to ensure or-logic selects unlabeled resources OR
the named cluster/nodepool as intended.

In `@pkg/common/utils.go`:
- Around line 614-647: The switch on paused only handles "" and "true", causing
a no-op when paused is an RFC3339 timestamp; add a default case in that switch
(near the paused switch handling in the function that computes needsUpdate) that
calls pausedUntilSetter(current, ptr.To(paused)) and invokes
addPauseAuditAnnotations(current) (same behavior as the "true" branch) so the
resource is actually mutated before calling c.Update; ensure this default is
used when paused is neither "" nor "true" so needsUpdate results in a real field
change and postUpdateHook/c.Update behave correctly.

In `@pkg/core/restore.go`:
- Around line 178-189: The code repeatedly reconstructs the ResourceFilter
inside Execute for MainKinds; hoist and cache it on RestorePlugin instead so
it's built once from input.Restore.Spec.LabelSelector/OrLabelSelectors and
reused. Add a field (e.g., resourceFilter *common.ResourceFilter) to
RestorePlugin, initialize it when the plugin is created (or lazily on first
Execute by calling common.NewResourceFilter with
input.Restore.Spec.LabelSelector and OrLabelSelectors), and replace the local
NewResourceFilter call in Execute with the cached RestorePlugin.resourceFilter
before calling common.UpdateNodepools and common.UpdateHostedCluster.

ℹ️ Review info

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between e223e9f and f2dd5f2.

📒 Files selected for processing (4)
  • pkg/common/utils.go
  • pkg/common/utils_test.go
  • pkg/core/backup.go
  • pkg/core/restore.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant