Skip to content

cmainstallprogression controller PatchStatus calls missing RetryOnConflict — MCAs stuck Unknown after hub restart #1582

Description

@mcphersonwhite-axon

Describe the bug

After a hub cluster restart, ManagedClusterAddOn objects for enrolled clusters remain in Unknown or ConfigurationUnsupported state indefinitely. The only recovery is manual intervention (patch status.supportedConfigs by hand or restart the addon-manager).

Root cause

pkg/addon/controllers/cmainstallprogression/controller.go contains three bare PatchStatus calls (one per code path: manual, self-managed, and placement install strategies) with no RetryOnConflict around them.

On hub restart, the informer cache is rebuilt and a reconcile event fires for every ClusterManagementAddOn simultaneously. Multiple reconcile goroutines race to patch the same CMA's status.supportedConfigs. Because the sdk-go patcher makes a single client.Patch() call with no retry, concurrent attempts fail with 409 Conflict — the resourceVersion has already changed between the GET (lister cache) and the PATCH.

The 409 causes the controller to requeue the CMA, but by that time the other goroutines have all failed too. The field never gets written.

Downstream effect: ClusterManagementAddOn.status.supportedConfigs stays empty → any spec.configs patch on a ManagedClusterAddOn is rejected with ConfigurationUnsupported → the addontemplate controller cannot render ManifestWorks → all addons remain Dead/Unknown on every spoke.

Affected versions

  • Confirmed on 1.3.1
  • Likely also affects 1.2.x (same code path)

Reproduction steps

  1. Enroll 3+ spoke clusters to a hub (enough to generate concurrent CMA reconcile events)
  2. Restart the cluster-manager deployment: kubectl rollout restart deployment/cluster-manager -n open-cluster-management
  3. Observe: kubectl get managedclusteraddon -A shows many addons in Unknown / ConfigurationUnsupported
  4. Observe: kubectl get clustermanagementaddon -o json | jq '.[].status.supportedConfigs' shows null on affected CMAs
  5. Observe cluster-manager logs for lines like: failed to patch cma status ... the object has been modified; please apply your changes to the latest version

Proposed fix

Wrap each of the three PatchStatus call sites in retry.RetryOnConflict(retry.DefaultRetry, ...). Because PatchStatus takes both newObject and oldObject and creates a merge patch (it does not re-fetch internally), each retry closure must re-fetch a fresh object from the API server before re-applying the status mutation.

A fix with a unit test is available at: (add PR link once opened)

The unit test (TestPatchStatusRetriesOn409Conflict) injects 2 consecutive 409 Conflicts via a PrependReactor, runs sync(), and asserts the reconciler succeeds and writes status.defaultConfigReferences.

Workaround

Until this is fixed upstream, operators can add a "status healer" controller that bootstraps status.supportedConfigs from spec.supportedConfigs after detecting the empty-status condition:

kubectl patch clustermanagementaddon <name> \
  --subresource=status --type=merge \
  --patch '{"status":{"supportedConfigs":[{"group":"addon.open-cluster-management.io","resource":"addondeploymentconfigs"},{"group":"addon.open-cluster-management.io","resource":"addontemplates"}]}}'

(Repeat for each affected CMA.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Status
    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions