After upgrading from the Helm chart from 3.29.1 to 3.31.5, we encountered an infinite loop in the tigera-operator.
{"level":"info","ts":"2026-05-07T08:48:57Z","logger":"controller_ippool","msg":"Pool needs to be deleted","Request.Namespace":"","Request.Name":"default-ipv6-ippool","cidr":"fd20:5213:94f6:1e9:1f::/96","valid":[{"name":"default-ipv6-ippool","cidr":"fd20:5213:94f6:01e9:001f::/96","encapsulation":"None","natOutgoing":"Disabled","nodeSelector":"all()","blockSize":122,"disableBGPExport":false,"disableNewAllocations":false,"allowedUses":["Workload","Tunnel"],"assignmentMode":"Automatic"}]}
Note: fd20:5213:94f6:1e9:1f::/96 vs fd20:5213:94f6:01e9:001f::/96
The problem appears to be:
- The existing IPPool has the CIDR
fd20:5213:94f6:1e9:1f::/96
- In the helm chart, the CIDR is given with leading zeroes
fd20:5213:94f6:01e9:001f::/96
- The operator checks the strings of the CIDRs for equality
- It detects a mismatch even though both addresses are semantically equivalent and marks the IPPool for deletion
- The IPPool is then re-created with
fd20:5213:94f6:01e9:001f::/96
- But because net.ParseCIDR inside IPPool.create() normalizes the address,
fd20:5213:94f6:1e9:1f::/96 ends up in the IPPool resource, so we go back to 1.
This has been fixed inside libcalico-go, so the operator should probably check cidrChangeOK(p.CIDR, cidr) instead of p.CIDR == cidr.
We worked around it by setting the canonical address inside the Helm chart, but we would expect the operator to either
- successfully handle non-canonical addresses or
- decline non-canonical addresses with a clear error message during validation.
After upgrading from the Helm chart from 3.29.1 to 3.31.5, we encountered an infinite loop in the tigera-operator.
{"level":"info","ts":"2026-05-07T08:48:57Z","logger":"controller_ippool","msg":"Pool needs to be deleted","Request.Namespace":"","Request.Name":"default-ipv6-ippool","cidr":"fd20:5213:94f6:1e9:1f::/96","valid":[{"name":"default-ipv6-ippool","cidr":"fd20:5213:94f6:01e9:001f::/96","encapsulation":"None","natOutgoing":"Disabled","nodeSelector":"all()","blockSize":122,"disableBGPExport":false,"disableNewAllocations":false,"allowedUses":["Workload","Tunnel"],"assignmentMode":"Automatic"}]}Note:
fd20:5213:94f6:1e9:1f::/96vsfd20:5213:94f6:01e9:001f::/96The problem appears to be:
fd20:5213:94f6:1e9:1f::/96fd20:5213:94f6:01e9:001f::/96fd20:5213:94f6:01e9:001f::/96fd20:5213:94f6:1e9:1f::/96ends up in the IPPool resource, so we go back to 1.This has been fixed inside libcalico-go, so the operator should probably check
cidrChangeOK(p.CIDR, cidr)instead ofp.CIDR == cidr.We worked around it by setting the canonical address inside the Helm chart, but we would expect the operator to either