Skip to content

Add periodic resync to operator reconciler - #1504

Open
mkowalski wants to merge 2 commits into
nmstate:mainfrom
mkowalski:fix-periodic-resync-operator
Open

Add periodic resync to operator reconciler#1504
mkowalski wants to merge 2 commits into
nmstate:mainfrom
mkowalski:fix-periodic-resync-operator

Conversation

@mkowalski

@mkowalski mkowalski commented Apr 22, 2026

Copy link
Copy Markdown
Member

Is this a BUG FIX or a FEATURE?:

/kind bug

What this PR does / why we need it:

Problem: The operator only reconciles on NMState CR, Deployment, or DaemonSet changes. If the openshift.io/node-selector: "" namespace annotation is removed externally, the reconciler never fires and pods remain stuck in Pending state on clusters with defaultNodeSelector configured.

Fix (belt and suspenders, per review):

  • Returns ctrl.Result{RequeueAfter: 5 * time.Minute} from Reconcile, adding a periodic resync similar to what CNO does with its 3-minute resync timer
  • Watches the handler namespace via Watches + EnqueueRequestsFromMapFunc, mapping its events to NMState reconcile requests so external modifications are reverted immediately
  • Restricts the manager cache for Namespace objects to metadata.name == HANDLER_NAMESPACE, so the operator does not cache/watch every namespace in the cluster

This ensures that externally modified resources are restored to their desired state — immediately on namespace events, and at worst within the resync period for anything else.

Ref: https://redhat.atlassian.net/browse/OCPBUGS-67277

Special notes for your reviewer:
Inspired by CNO's ResyncPeriod approach. The 5-minute interval is conservative enough to not cause excessive API server load but responsive enough to fix the issue within a reasonable time window.

Release note:

The NMState operator now watches its handler namespace and periodically reconciles its managed resources every 5 minutes, ensuring that externally modified namespace annotations (such as openshift.io/node-selector) are restored. This fixes an issue where pods could get stuck in Pending state on clusters with defaultNodeSelector configured if the annotation was removed.

@kubevirt-bot kubevirt-bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. kind/bug dco-signoff: yes Indicates the PR's author has DCO signed all their commits. labels Apr 22, 2026
@kubevirt-bot
kubevirt-bot requested review from phoracek and qinqon April 22, 2026 08:17
@kubevirt-bot

Copy link
Copy Markdown
Collaborator

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign qinqon 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

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a periodic resync mechanism for the NMState operator to ensure that externally modified resources, such as namespace annotations, are restored. The implementation adds a manual resync loop via a Start method and includes a watch for Namespace resources. Feedback suggests simplifying the implementation by using the idiomatic RequeueAfter result in the Reconcile function instead of a manual loop, which would also eliminate the need to register the reconciler as a separate runnable. Additionally, the watch on Namespace resources should be removed as they are not owned by the NMState CR, making the Owns call ineffective.

Comment on lines +165 to +193
func (r *NMStateReconciler) Start(ctx context.Context) error {
ticker := time.NewTicker(ResyncPeriod)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
r.Log.Info("Periodic resync triggered")
instanceList := &nmstatev1.NMStateList{}
if err := r.List(ctx, instanceList, &client.ListOptions{}); err != nil {
r.Log.Error(err, "failed listing NMState instances during periodic resync")
continue
}
for i := range instanceList.Items {
instance := &instanceList.Items[i]
if _, err := r.Reconcile(ctx, ctrl.Request{
NamespacedName: types.NamespacedName{
Name: instance.Name,
Namespace: instance.Namespace,
},
}); err != nil {
r.Log.Error(err, "failed to reconcile NMState instance during periodic resync",
"name", instance.Name)
}
}
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Implementing a manual resync loop via manager.Runnable adds unnecessary complexity to the controller. This approach bypasses the controller's workqueue and safety mechanisms. A more idiomatic and simpler way to achieve periodic reconciliation is to return ctrl.Result{RequeueAfter: ResyncPeriod}, nil at the end of the Reconcile function. This leverages the built-in queueing mechanism and adheres to the preference for simpler implementations.

References
  1. Changes that add complexity to 'future-proof' the code against hypothetical scenarios require strong justification. Without it, prefer the simpler implementation that covers known requirements.

For(&nmstatev1.NMState{}).
Owns(&appsv1.Deployment{}).
Owns(&appsv1.DaemonSet{}).
Owns(&corev1.Namespace{}).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The Owns method is intended for resources that have an OwnerReference pointing to the primary resource being reconciled (NMState). Since the Namespace is not owned by the NMState CR, this watch will not trigger any reconciliation. If immediate reconciliation on namespace changes is not a strictly defined requirement, this watch should be removed to keep the implementation simple, relying on the periodic resync instead.

References
  1. Changes that add complexity to 'future-proof' the code against hypothetical scenarios require strong justification. Without it, prefer the simpler implementation that covers known requirements.

Comment thread cmd/operator/main.go Outdated
Comment on lines +141 to +145
// Register periodic resync to ensure externally modified resources
// (e.g. namespace annotations) are restored.
if err = mgr.Add(reconciler); err != nil {
return fmt.Errorf("failed adding periodic resync runnable: %w", err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If the manual Start method is removed from the reconciler in favor of the simpler RequeueAfter approach, this call to mgr.Add is no longer necessary and should be removed to reduce code complexity.

References
  1. Changes that add complexity to 'future-proof' the code against hypothetical scenarios require strong justification. Without it, prefer the simpler implementation that covers known requirements.

@mkowalski
mkowalski force-pushed the fix-periodic-resync-operator branch from 0f3341a to f2d7d8f Compare April 22, 2026 08:21
@mkowalski
mkowalski force-pushed the fix-periodic-resync-operator branch from f2d7d8f to 9923a61 Compare April 22, 2026 14:36

@qinqon qinqon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mkowalski I would do a belt and suspenders and do both, Namespace reconcile (you have to be sure that we filter the cache to just our namespace) and the 5 min thing you have already do, this way we have the best of both words.

@qinqon

qinqon commented Jun 30, 2026

Copy link
Copy Markdown
Member

@mkowalski let's re-purpose this PR to watch for the kubernetes-nmstate namespace instead.

Copilot AI balanced review requested due to automatic review settings August 18, 2026 15:20
@kubevirt-prow kubevirt-prow Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 18, 2026
@mkowalski

Copy link
Copy Markdown
Member Author

@qinqon done — kept the 5 min periodic resync and added a Watches on the handler namespace mapping events to NMState reconcile requests. The manager cache is filtered with a metadata.name == HANDLER_NAMESPACE field selector for Namespace objects, so we only cache/watch our own namespace. Added unit tests for the mapping function.

This message was generated using AI. Please verify before acting on it.

Return RequeueAfter from the Reconcile function to schedule periodic
re-reconciliation every 5 minutes. This ensures that externally
modified resources, such as the openshift.io/node-selector namespace
annotation, are periodically restored.

Previously, the operator only reconciled on NMState CR, Deployment,
or DaemonSet changes. If the namespace annotation was removed
externally, the reconciler would never fire and pods scheduled after
the removal would remain stuck in Pending state on clusters with
defaultNodeSelector configured.

Fixes: https://redhat.atlassian.net/browse/OCPBUGS-67277
Signed-off-by: Mateusz Kowalski <mko@redhat.com>
Generated-by: OpenClaw OpenClaw 2026.4.15 (041266a)
AI-model: claude-opus-4.6
In addition to the periodic resync, watch the handler namespace and map
its events to NMState reconcile requests so externally modified
namespace metadata (e.g. annotations) is restored immediately. The
manager cache is restricted to the handler namespace for Namespace
objects to avoid caching every namespace in the cluster.

Assisted-By: Claude Fable 5
Signed-off-by: Mat Kowalski <mko@redhat.com>
@mkowalski
mkowalski force-pushed the fix-periodic-resync-operator branch from a23975e to d92203a Compare August 18, 2026 15:28
@kubevirt-prow kubevirt-prow Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 18, 2026

Copilot AI 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.

Pull request overview

Adds periodic reconciliation to restore externally modified operator-managed resources.

Changes:

  • Requeues successful reconciliations every five minutes.
  • Watches handler namespace changes for immediate reconciliation.
  • Restricts namespace caching and adds relevant tests.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
controllers/operator/nmstate_controller.go Adds periodic requeue and namespace event mapping.
controllers/operator/nmstate_controller_test.go Tests requeue results and namespace mapping.
cmd/operator/main.go Limits the namespace cache to the handler namespace.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@kubevirt-prow

kubevirt-prow Bot commented Aug 18, 2026

Copy link
Copy Markdown

@mkowalski: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-kubernetes-nmstate-e2e-upgrade-k8s d92203a link false /test pull-kubernetes-nmstate-e2e-upgrade-k8s
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. I understand the commands that are listed here.

@mkowalski

Copy link
Copy Markdown
Member Author

Hey @qinqon, let's try moving this one forward. I applied your last comment and hopefully we are ready to go

@mkowalski

Copy link
Copy Markdown
Member Author

/approve

Human review, looks good.

@kubevirt-prow

kubevirt-prow Bot commented Aug 19, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mkowalski

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

The pull request process is described 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

@kubevirt-prow kubevirt-prow Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. dco-signoff: yes Indicates the PR's author has DCO signed all their commits. kind/bug release-note Denotes a PR that will be considered when it comes time to generate release notes. size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants