Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions pkg/render/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ func (c *nodeComponent) createCalicoPluginConfig() map[string]any {

ipam := c.getCalicoIPAM()
if c.cfg.Installation.CNI.IPAM.Type == operatorv1.IPAMPluginHostLocal {
ipam = buildHostLocalIPAM(c.cfg.IPPools)
ipam = buildHostLocalIPAM(WorkloadIPPools(c.cfg.IPPools))
}

apiRoot := c.cfg.K8sServiceEp.CNIAPIRoot()
Expand Down Expand Up @@ -825,14 +825,15 @@ func (c *nodeComponent) nodeCNIConfigMap() *corev1.ConfigMap {

func (c *nodeComponent) getCalicoIPAM() map[string]any {
// Determine what address families to enable.
pools := WorkloadIPPools(c.cfg.IPPools)
var assign_ipv4 string
var assign_ipv6 string
if HasIPv4Pool(c.cfg.IPPools) {
if HasIPv4Pool(pools) {
assign_ipv4 = "true"
} else {
assign_ipv4 = "false"
}
if HasIPv6Pool(c.cfg.IPPools) {
if HasIPv6Pool(pools) {
assign_ipv6 = "true"
} else {
assign_ipv6 = "false"
Expand Down Expand Up @@ -1818,6 +1819,31 @@ func HasIPv6Pool(pools []operatorv1.IPPool) bool {
return false
}

// WorkloadIPPools returns the pools that can hand out addresses to ordinary workloads.
func WorkloadIPPools(pools []operatorv1.IPPool) []operatorv1.IPPool {
filtered := []operatorv1.IPPool{}
for _, pool := range pools {
if allowsWorkloads(pool) {
filtered = append(filtered, pool)
}
}
return filtered
}

func allowsWorkloads(pool operatorv1.IPPool) bool {
if len(pool.AllowedUses) == 0 {
// An unset allowedUses means Workload and Tunnel, for back-compatibility.
return true
}

for _, use := range pool.AllowedUses {
if use == operatorv1.IPPoolAllowedUseWorkload {
return true
}
}
return false
}

// bgpEnabled returns true if the given Installation enables BGP, false otherwise.
func bgpEnabled(instance *operatorv1.InstallationSpec) bool {
return instance.CalicoNetwork != nil &&
Expand Down
104 changes: 104 additions & 0 deletions pkg/render/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package render_test

import (
"encoding/json"
"fmt"
"strings"

Expand Down Expand Up @@ -3334,3 +3335,106 @@ func verifyInitContainers(ds *appsv1.DaemonSet, instance *operatorv1.Installatio
Expect(flexvolContainer).To(BeNil())
}
}

var _ = Describe("Node CNI address family selection", func() {
const l2Workload = operatorv1.IPPoolAllowedUse("L2Workload")

var cfg render.NodeConfiguration
var instance *operatorv1.InstallationSpec

// ipamConfig renders the CNI config map and returns the ipam block of the calico plugin.
ipamConfig := func() map[string]any {
component := render.Node(&cfg)
Expect(component.ResolveImages(nil)).To(BeNil())
resources, _ := component.Objects()

cm, ok := rtest.GetResource(resources, "cni-config", "calico-system", "", "v1", "ConfigMap").(*corev1.ConfigMap)
Expect(ok).To(BeTrue())

var parsed cniNetworkConfig
Expect(json.Unmarshal([]byte(cm.Data["config"]), &parsed)).NotTo(HaveOccurred())
Expect(parsed.Plugins).NotTo(BeEmpty())
return parsed.Plugins[0].IPAM
}

BeforeEach(func() {
ff := true
instance = &operatorv1.InstallationSpec{
CNI: &operatorv1.CNISpec{
Type: operatorv1.PluginCalico,
IPAM: &operatorv1.IPAMSpec{Type: operatorv1.IPAMPluginCalico},
},
CalicoNetwork: &operatorv1.CalicoNetworkSpec{
BGP: &bgpEnabled,
IPPools: []operatorv1.IPPool{{CIDR: "192.168.0.0/16"}},
NodeAddressAutodetectionV4: &operatorv1.NodeAddressAutodetection{FirstFound: &ff},
},
}
defaultCNIConfDir, defaultCNIBinDir := render.DefaultCNIDirectories(instance.KubernetesProvider)
instance.CNI.ConfDir, instance.CNI.BinDir = &defaultCNIConfDir, &defaultCNIBinDir

scheme := runtime.NewScheme()
Expect(apis.AddToScheme(scheme, false)).NotTo(HaveOccurred())
cli := ctrlrfake.DefaultFakeClientBuilder(scheme).Build()
certificateManager, err := certificatemanager.Create(cli, nil, clusterDomain, common.OperatorNamespace(), certificatemanager.AllowCACreation())
Expect(err).NotTo(HaveOccurred())

cfg = render.NodeConfiguration{
K8sServiceEp: k8sapi.ServiceEndpoint{},
Installation: instance,
TLS: getTyphaNodeTLS(cli, certificateManager),
ClusterDomain: clusterDomain,
FelixConfiguration: &v3.FelixConfiguration{Spec: v3.FelixConfigurationSpec{HealthPort: ptr.To(9099)}},
IPPools: instance.CalicoNetwork.IPPools,
}
})

It("should not enable IPv6 assignment for a pool that doesn't allow workloads", func() {
cfg.IPPools = append(cfg.IPPools, operatorv1.IPPool{
CIDR: "2001:db8:1::/122",
AllowedUses: []operatorv1.IPPoolAllowedUse{l2Workload},
})
ipam := ipamConfig()
Expect(ipam).To(HaveKeyWithValue("assign_ipv4", "true"))
Expect(ipam).To(HaveKeyWithValue("assign_ipv6", "false"))
})

It("should enable IPv6 assignment for a pool that allows workloads", func() {
cfg.IPPools = append(cfg.IPPools, operatorv1.IPPool{
CIDR: "2001:db8:1::/122",
AllowedUses: []operatorv1.IPPoolAllowedUse{l2Workload, operatorv1.IPPoolAllowedUseWorkload},
})
Expect(ipamConfig()).To(HaveKeyWithValue("assign_ipv6", "true"))
})

It("should enable IPv6 assignment for a pool with no allowed uses set", func() {
cfg.IPPools = append(cfg.IPPools, operatorv1.IPPool{CIDR: "2001:db8:1::/122"})
Expect(ipamConfig()).To(HaveKeyWithValue("assign_ipv6", "true"))
})

It("should disable IPv4 assignment when the only IPv4 pool is tunnel-only", func() {
cfg.IPPools = []operatorv1.IPPool{{
CIDR: "192.168.0.0/16",
AllowedUses: []operatorv1.IPPoolAllowedUse{operatorv1.IPPoolAllowedUseTunnel},
}}
Expect(ipamConfig()).To(HaveKeyWithValue("assign_ipv4", "false"))
})

It("should render host-local as single-stack when the IPv6 pool doesn't allow workloads", func() {
instance.CNI.IPAM.Type = operatorv1.IPAMPluginHostLocal
cfg.IPPools = append(cfg.IPPools, operatorv1.IPPool{
CIDR: "2001:db8:1::/122",
AllowedUses: []operatorv1.IPPoolAllowedUse{l2Workload},
})
Expect(ipamConfig()).To(Equal(map[string]any{"type": "host-local", "subnet": "usePodCidr"}))
})
})

// cniNetworkConfig mirrors the parts of the rendered CNI network config the tests assert on.
type cniNetworkConfig struct {
Plugins []cniPluginConfig `json:"plugins"`
}

type cniPluginConfig struct {
IPAM map[string]any `json:"ipam"`
}
Loading