Skip to content

Treat an unset clusterRoutingMode as the mode Calico defaults to - #5201

Merged
nelljerram merged 1 commit into
tigera:masterfrom
nelljerram:felix-ipip-default-cluster-routing-mode
Aug 16, 2026
Merged

nelljerram merged 1 commit into
tigera:masterfrom
nelljerram:felix-ipip-default-cluster-routing-mode

Conversation

@nelljerram

Copy link
Copy Markdown
Member

Description

Type: bug fix. Note: master is currently frozen, so this is raised for review rather than
immediate merge.

clusterRoutingMode() returned "" for an unset clusterRoutingMode, so
felixProgramsIPIPClusterRoutes() returned false and the Installation validation concluded that
BIRD owned the IPIP cluster routes. Since Calico v3.33 that is wrong: Calico's own defaults give
Felix the cluster routes for IPIP IP Pools, and leave the unencapsulated ones with BIRD.

The visible effect is that a cluster which has simply taken Calico's default — the common case,
since the operator writes neither programClusterRoutes field when clusterRoutingMode is unset —
cannot disable BGP even when it only has IPIP pools. The Installation is accepted by the API
server but the operator degrades without touching the DaemonSet:

$ kubectl patch installation default --type=merge \
    -p '{"spec":{"calicoNetwork":{"bgp":"Disabled"}}}'
installation.operator.tigera.io/default patched

$ kubectl get tigerastatus calico -o jsonpath='{.status.conditions[0]}'
Degraded=True InvalidConfigurationError
  Invalid Installation provided: with BIRD cluster routing mode, IPIP encapsulation requires
  that BGP is enabled

This returns FelixIPIPOnly for an unset mode instead — exactly the split Calico's defaults
produce — so both predicates come out right: Felix owns IPIP, BIRD keeps no-encap.

The trade-off

Returning the effective mode ties the operator to the Calico version it ships with, which is why
the unset case previously returned "" (see the comment being replaced, from #5150). The trade is
deliberate: every caller has to reach some conclusion about who owns the routes, and answering
"nobody knows, so assume BIRD" is not a neutral default — it rejects a configuration that works.
The operator is released against a known Calico version, so encoding that version's defaults is
well defined. It will need revisiting when the no-encap default moves.

Deciding whether to write the programClusterRoutes fields is unaffected:
setClusterRoutingOnFelixConfiguration and setClusterRoutingOnBGPConfiguration test the field for
nil directly rather than going through this helper, so an unset mode still writes neither field, and
still means "whatever Calico's defaults are" rather than pinning today's defaults into the
datastore.

Testing

  • Three validation tests named for BIRD cluster routing mode were relying on unset meaning BIRD;
    they now set that mode explicitly, so they test what their names claim.
  • Two new tests pin the changed behaviour: unset + IPIP + BGP disabled is allowed, unset + no-encap
    • BGP disabled is still rejected.
  • go test ./pkg/controller/installation/: 306 passed, same 14 pre-existing failures as on clean
    master (Installation CRD CEL validation, which needs envtest binaries not present locally —
    318/304/14 before, 320/306/14 after).
  • Found while writing a Calico-side system test for the BIRD-to-Felix cluster route migration,
    which needs to disable BGP on an IPIP cluster and was blocked by this.

Related issues/PRs

Follows #5150, which added FelixIPIPOnly. Related to projectcalico/calico#13470.

Release Note

Fixed a bug that prevented BGP being disabled on a cluster with only IPIP IP Pools when clusterRoutingMode was left unset.

clusterRoutingMode returned "" when the field was unset, so
felixProgramsIPIPClusterRoutes returned false and validation concluded
that BIRD owned the IPIP cluster routes.  Since Calico v3.33 that is
wrong: Calico's own defaults give Felix the routes for IPIP IP Pools and
leave the unencapsulated ones with BIRD.

The visible effect is that a cluster which has simply taken Calico's
default -- the common case, since the operator writes neither
programClusterRoutes field when clusterRoutingMode is unset -- cannot
disable BGP even when it has only IPIP pools.  The Installation is
rejected with "with BIRD cluster routing mode, IPIP encapsulation
requires that BGP is enabled", and the operator degrades without
touching the DaemonSet.

Return FelixIPIPOnly for an unset mode instead, which is exactly the
split those defaults produce, and both predicates then come out right:
Felix owns IPIP, BIRD keeps no-encap.

This does tie the operator to the Calico version it ships with, which is
why the unset case previously returned "".  The trade is deliberate:
every caller has to reach some conclusion about who owns the routes, and
answering "assume BIRD" is not neutral -- it rejects a configuration
that works.  Revisit when the no-encap default moves.

Deciding whether to *write* the programClusterRoutes fields is
unaffected: setClusterRoutingOnFelixConfiguration and
setClusterRoutingOnBGPConfiguration test the field for nil directly, so
an unset mode still writes neither, and still means "whatever Calico's
defaults are" rather than pinning today's defaults into the datastore.

The three validation tests named for BIRD cluster routing mode were
relying on unset meaning BIRD; they now set that mode explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nelljerram
nelljerram requested a review from a team as a code owner August 14, 2026 22:38
Copilot AI lite review requested due to automatic review settings August 14, 2026 22:38
@marvin-tigera marvin-tigera added this to the v1.44.0 milestone Aug 14, 2026

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

Pull request overview

This PR fixes Installation validation behavior when spec.calicoNetwork.clusterRoutingMode is unset by treating it as Calico’s effective default behavior (since Calico v3.33): FelixIPIPOnly (Felix owns IPIP cluster routes; BIRD owns no-encap). This prevents a common defaulted cluster (unset mode + only IPIP pools) from being incorrectly blocked when disabling BGP.

Changes:

  • Update clusterRoutingMode() to return FelixIPIPOnly when the field is unset, so downstream predicates reflect Calico’s effective defaults.
  • Fix three validation tests that were implicitly relying on “unset means BIRD” by explicitly setting ClusterRoutingModeBIRD.
  • Add two new validation tests to pin the new/desired behavior for unset mode with IPIP vs no-encap pools when BGP is disabled.

Reviewed changes

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

File Description
pkg/controller/installation/core_controller.go Treat unset clusterRoutingMode as effective FelixIPIPOnly to match Calico v3.33 defaults and drive correct route-ownership predicates.
pkg/controller/installation/validation_test.go Make BIRD-mode tests explicit and add coverage for the updated “unset mode” validation behavior.

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

@nelljerram
nelljerram merged commit 6457d35 into tigera:master Aug 16, 2026
9 checks passed
@nelljerram
nelljerram deleted the felix-ipip-default-cluster-routing-mode branch August 16, 2026 08:49
@danudey danudey added the kind/bug Something isn't working label Oct 1, 2026
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.

5 participants