Skip to content

Clear programClusterRoutes when clusterRoutingMode is removed - #13888

Draft
tomastigera wants to merge 1 commit into
projectcalico:masterfrom
tomastigera:tomas/core-13588-clear-stranded-routing-mode
Draft

Clear programClusterRoutes when clusterRoutingMode is removed#13888
tomastigera wants to merge 1 commit into
projectcalico:masterfrom
tomastigera:tomas/core-13588-clear-stranded-routing-mode

Conversation

@tomastigera

Copy link
Copy Markdown
Contributor

Problem

Setting Installation.spec.calicoNetwork.clusterRoutingMode and then removing it leaves the cluster running whatever that mode last wrote, while the Installation looks exactly like one that never touched the field. Nothing is written, nothing is logged, and status.computed is empty.

The state it can strand is the deprecated BIRD-programs-IPIP configuration. A user who sets BIRD to opt out of the v3.33 default and then removes the field expecting the default back stays on the deprecated arm, with no indication anywhere.

kubectl patch installation default --type=merge \
  -p '{"spec":{"calicoNetwork":{"clusterRoutingMode":"BIRD"}}}'
#  -> FelixConfiguration=Disabled  BGPConfiguration=Enabled      (correct)

kubectl patch installation default --type=json \
  -p '[{"op":"remove","path":"/spec/calicoNetwork/clusterRoutingMode"}]'
#  -> both still set, status.computed empty, operator log silent for 76s

Why the early return is there

setClusterRoutingOnFelixConfiguration and setClusterRoutingOnBGPConfiguration return early when the mode is nil. That is correct for a field that was never set: writing there would pin the operator's idea of today's Calico defaults into the datastore, which is exactly what clusterRoutingMode() documents as the reason for the gate. What it cannot do is tell never-set from set-then-unset.

Change

Use status.computed as the record of what the operator last applied. It is persisted on the CR so it survives restarts, and it still holds the previous reconcile's value at the point the setters run. A mode there plus no mode in the spec is the set-to-unset transition.

On that transition, clear programClusterRoutes and let Calico's own defaults decide again — which is what the existing comment asks for. The gate is preserved: an unset field still never writes a value.

A value the operator never wrote belongs to the user and is left alone, so configuring programClusterRoutes directly on FelixConfiguration keeps working while the Installation field is unset.

Tests

Two specs in core_controller_test.go, both driving real reconciles against the fake client:

  • set, then remove → both programClusterRoutes cleared
  • never set, user writes programClusterRoutes directly → left alone

Mutation-tested: reverting either setter to return false, nil fails the first spec. The existing "should not patch FelixConfig and BGPConfig when ClusterRouteMode not set" spec is unchanged and still passes, which is the regression guard for the documented no-write-on-unset behaviour.

Suite: 311 passed / 22 failed. The 22 are all cel_validation_test.go [BeforeEach] envtest setup failures; a control run on unmodified upstream/master in the same worktree fails the identical 22 (309 passed / 22 failed), so they are pre-existing and environmental.

Found during the v3.33 test cycle, Zephyr OS-R232 / RT-3.

CORE-13588

AI assistance

This PR was written in part with the assistance of generative AI.

Release Note

Fix the operator leaving a stale programClusterRoutes value behind when clusterRoutingMode is removed from the Installation, which could strand a cluster on the deprecated BIRD-programs-IPIP configuration.

Setting Installation.spec.calicoNetwork.clusterRoutingMode and then removing it
left the cluster running whatever that mode last wrote, while the Installation
looked exactly like one that never set the field. Nothing was written, nothing
logged, and status.computed was empty. The state it could strand is the
deprecated BIRD-programs-IPIP configuration, so a user who set BIRD to opt out
of the v3.33 default and then removed the field to get the default back stayed
on the deprecated arm with no indication anywhere.

The two setters return early when the mode is nil. That is right for a field
that was never set -- writing there would pin the operator's idea of today's
Calico defaults into the datastore, which clusterRoutingMode() documents as the
reason for the gate -- but it cannot tell never-set from set-then-unset.

Use status.computed as the record of what we last applied. It is persisted on
the CR, so it survives restarts, and it still holds the previous reconcile's
value at the point the setters run. A mode there plus no mode in the spec is
the set-to-unset transition, so clear programClusterRoutes and let Calico's own
defaults decide again. A value we never wrote belongs to the user and stays,
which keeps direct FelixConfiguration configuration working while the
Installation field is unset.

CORE-13588
@marvin-tigera marvin-tigera added this to the Calico v3.34.0 milestone Sep 11, 2026
@marvin-tigera marvin-tigera added release-note-required Change has user-facing impact (no matter how small) docs-pr-required Change is not yet documented labels Sep 11, 2026
@tomastigera tomastigera added docs-not-required Docs not required for this change cherry-pick-candidate and removed docs-pr-required Change is not yet documented labels Sep 11, 2026
@marvin-tigera marvin-tigera added docs-pr-required Change is not yet documented and removed docs-not-required Docs not required for this change cherry-pick-candidate labels Sep 11, 2026
@tomastigera tomastigera added docs-not-required Docs not required for this change cherry-pick-candidate and removed docs-pr-required Change is not yet documented labels Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cherry-pick-candidate docs-not-required Docs not required for this change release-note-required Change has user-facing impact (no matter how small)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants