Skip to content

Commit ebbf4e0

Browse files
authored
Merge pull request #644 from yvetteli0314/master
Update Node pod CIDR in ipam controller for for Multi Pod CIDR suppor…
2 parents 398b1a1 + 0fe403b commit ebbf4e0

File tree

5 files changed

+156
-87
lines changed

5 files changed

+156
-87
lines changed

pkg/controller/nodeipam/ipam/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ go_test(
7070
"//pkg/controller/nodeipam/ipam/cidrset",
7171
"//pkg/controller/nodeipam/ipam/test",
7272
"//pkg/controller/testutil",
73+
"//pkg/util/node",
7374
"//providers/gce",
7475
"//vendor/github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta",
7576
"//vendor/github.com/google/go-cmp/cmp",

pkg/controller/nodeipam/ipam/cloud_cidr_allocator.go

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -311,70 +311,40 @@ func (ca *cloudCIDRAllocator) updateCIDRAllocation(nodeName string) error {
311311

312312
cidrStrings := make([]string, 0)
313313

314-
// if there are no interfaces or there is 1 interface
315-
// but does not have IP alias or IPv6 ranges no CIDR
316-
// can be allocated
317-
if len(instance.NetworkInterfaces) == 0 ||
318-
(len(instance.NetworkInterfaces) == 1 &&
319-
len(instance.NetworkInterfaces[0].AliasIpRanges) == 0 &&
320-
ca.cloud.GetIPV6Address(instance.NetworkInterfaces[0]) == nil) {
314+
if len(instance.NetworkInterfaces) == 0 || (len(instance.NetworkInterfaces) == 1 && len(instance.NetworkInterfaces[0].AliasIpRanges) == 0) {
321315
nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable")
322316
return fmt.Errorf("failed to allocate cidr: Node %v has no ranges from which CIDRs can be allocated", node.Name)
323317
}
324318

325319
// sets the v1.NodeNetworkUnavailable condition to False
326320
ca.setNetworkCondition(node)
327321

328-
// nodes in clusters WITHOUT multi-networking are expected to have
329-
// only 1 network-interface and 1 alias IPv4 range or/and 1 IPv6 address
330-
// multi-network cluster may have 1 interface with multiple alias
331-
if len(instance.NetworkInterfaces) == 1 &&
332-
(len(instance.NetworkInterfaces[0].AliasIpRanges) == 1 ||
333-
ca.cloud.GetIPV6Address(instance.NetworkInterfaces[0]) != nil) {
334-
// with 1 alias IPv4 range on single IPv4 or dual stack clusters
335-
if len(instance.NetworkInterfaces[0].AliasIpRanges) == 1 {
336-
cidrStrings = append(cidrStrings, instance.NetworkInterfaces[0].AliasIpRanges[0].IpCidrRange)
337-
}
338-
// with 1 IPv6 range on single IPv6 or dual stack cluster
322+
// nodes in clusters WITHOUT multi-networking are expected to have only 1 network-interface with 1 alias IP range.
323+
if len(instance.NetworkInterfaces) == 1 && len(instance.NetworkInterfaces[0].AliasIpRanges) == 1 {
324+
cidrStrings = append(cidrStrings, instance.NetworkInterfaces[0].AliasIpRanges[0].IpCidrRange)
339325
ipv6Addr := ca.cloud.GetIPV6Address(instance.NetworkInterfaces[0])
340326
if ipv6Addr != nil {
341327
cidrStrings = append(cidrStrings, ipv6Addr.String())
342328
}
343329
} else {
344330
// multi-networking enabled clusters
345-
cidrStrings, err = ca.performMultiNetworkCIDRAllocation(node, instance.NetworkInterfaces)
331+
hasNodeLabels, defaultSubnet, defaultPodRange := getNodeDefaultLabels(node)
332+
// if there's no node label get the cidrStrings with the old way by comparing the default Network and GNP
333+
cidrStrings, err = ca.performMultiNetworkCIDRAllocation(node, instance.NetworkInterfaces, hasNodeLabels)
346334
if err != nil {
347-
nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable")
348-
return fmt.Errorf("failed to get cidr(s) from provider: %v", err)
335+
nodeutil.RecordNodeStatusChange(ca.recorder, node, "AnnotationsNotAvailable")
336+
return fmt.Errorf("failed to perform node annotations for multi-networking: %v", err)
349337
}
350-
}
351-
if len(cidrStrings) == 0 {
352-
nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable")
353-
return fmt.Errorf("failed to allocate cidr: Node %v has no CIDRs", node.Name)
354-
}
355-
// Can have at most 2 ips (one for v4 and one for v6)
356-
if len(cidrStrings) > 2 {
357-
klog.InfoS("Got more than 2 ips, truncating to 2", "cidrStrings", cidrStrings)
358-
cidrStrings = cidrStrings[:2]
359-
}
360-
361-
cidrs, err := netutils.ParseCIDRs(cidrStrings)
362-
if err != nil {
363-
return fmt.Errorf("failed to parse strings %v as CIDRs: %v", cidrStrings, err)
364-
}
365-
if len(cidrs) > 1 {
366-
if dualStack, _ := netutils.IsDualStackCIDRs(cidrs); !dualStack {
367-
return fmt.Errorf("err: IPs are not dual stack, CIDRS: %v", cidrStrings)
338+
if hasNodeLabels {
339+
cidrStrings = ca.extractDefaultNwCIDRs(instance.NetworkInterfaces, defaultSubnet, defaultPodRange)
368340
}
369341
}
370342

371-
node.Spec.PodCIDR = cidrStrings[0]
372-
node.Spec.PodCIDRs = cidrStrings
373-
374-
err = ca.updateNodeCIDR(node, oldNode)
375-
if err != nil {
343+
// update Node.Spec.PodCIDR(s)
344+
if err = ca.updateNodePodCIDRWithCidrStrings(oldNode, node, cidrStrings); err != nil {
376345
return err
377346
}
347+
378348
if !reflect.DeepEqual(node.Annotations, oldNode.Annotations) {
379349
// retain old north interfaces annotation
380350
var oldNorthInterfacesAnnotation networkv1.NorthInterfacesAnnotation
@@ -409,6 +379,34 @@ func (ca *cloudCIDRAllocator) updateCIDRAllocation(nodeName string) error {
409379
return err
410380
}
411381

382+
// updateNodePodCIDRWithCidrStrings update the Node object with Spec.PodCIDR(s),
383+
// returns error if cidrStrings is not valid or fails to update the Node object
384+
func (ca *cloudCIDRAllocator) updateNodePodCIDRWithCidrStrings(oldNode *v1.Node, node *v1.Node, cidrStrings []string) error {
385+
if len(cidrStrings) == 0 {
386+
nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable")
387+
return fmt.Errorf("failed to allocate cidr: Node %v has no CIDRs", node.Name)
388+
}
389+
// Can have at most 2 ips (one for v4 and one for v6)
390+
if len(cidrStrings) > 2 {
391+
klog.InfoS("Got more than 2 ips, truncating to 2", "cidrStrings", cidrStrings)
392+
cidrStrings = cidrStrings[:2]
393+
}
394+
395+
cidrs, err := netutils.ParseCIDRs(cidrStrings)
396+
if err != nil {
397+
return fmt.Errorf("failed to parse strings %v as CIDRs: %v", cidrStrings, err)
398+
}
399+
if len(cidrs) > 1 {
400+
if dualStack, _ := netutils.IsDualStackCIDRs(cidrs); !dualStack {
401+
return fmt.Errorf("err: IPs are not dual stack, CIDRS: %v", cidrStrings)
402+
}
403+
}
404+
node.Spec.PodCIDR = cidrStrings[0]
405+
node.Spec.PodCIDRs = cidrStrings
406+
407+
return ca.updateNodeCIDR(node, oldNode)
408+
}
409+
412410
func (ca *cloudCIDRAllocator) setNetworkCondition(node *v1.Node) {
413411
cond := v1.NodeCondition{
414412
Type: v1.NodeNetworkUnavailable,

pkg/controller/nodeipam/ipam/cloud_cidr_allocator_test.go

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
clSetFake "k8s.io/cloud-provider-gcp/crd/client/network/clientset/versioned/fake"
4141
networkinformers "k8s.io/cloud-provider-gcp/crd/client/network/informers/externalversions"
4242
"k8s.io/cloud-provider-gcp/pkg/controller/testutil"
43+
utilnode "k8s.io/cloud-provider-gcp/pkg/util/node"
4344
"k8s.io/cloud-provider-gcp/providers/gce"
4445
metricsUtil "k8s.io/component-base/metrics/testutil"
4546
)
@@ -281,45 +282,6 @@ func TestUpdateCIDRAllocation(t *testing.T) {
281282
},
282283
expectedUpdate: true,
283284
},
284-
{
285-
name: "ipv6 single stack",
286-
fakeNodeHandler: &testutil.FakeNodeHandler{
287-
Existing: []*v1.Node{
288-
{
289-
ObjectMeta: metav1.ObjectMeta{
290-
Name: "test",
291-
},
292-
Spec: v1.NodeSpec{
293-
ProviderID: "gce://test-project/us-central1-b/test",
294-
},
295-
},
296-
},
297-
Clientset: fake.NewSimpleClientset(),
298-
},
299-
gceInstance: []*compute.Instance{
300-
{
301-
Name: "test",
302-
NetworkInterfaces: []*compute.NetworkInterface{
303-
{
304-
Ipv6Address: "2001:db9::110",
305-
},
306-
},
307-
},
308-
},
309-
nodeChanges: func(node *v1.Node) {
310-
node.Spec.PodCIDR = "2001:db9::/112"
311-
node.Spec.PodCIDRs = []string{"2001:db9::/112"}
312-
node.Status.Conditions = []v1.NodeCondition{
313-
{
314-
Type: "NetworkUnavailable",
315-
Status: "False",
316-
Reason: "RouteCreated",
317-
Message: "NodeController create implicit route",
318-
},
319-
}
320-
},
321-
expectedUpdate: true,
322-
},
323285
{
324286
name: "want error - incorrect cidr",
325287
fakeNodeHandler: &testutil.FakeNodeHandler{
@@ -531,6 +493,64 @@ func TestUpdateCIDRAllocation(t *testing.T) {
531493
expectedUpdate: true,
532494
expectedMetrics: map[string]float64{},
533495
},
496+
{
497+
name: "[mn] default network only, get PodCIDR with node labels",
498+
networks: []*networkv1.Network{
499+
network(networkv1.DefaultPodNetworkName, defaultGKENetworkParamsName, false),
500+
},
501+
gkeNwParams: []*networkv1.GKENetworkParamSet{
502+
gkeNetworkParams(defaultGKENetworkParamsName, defaultVPCName, defaultVPCSubnetName, nil),
503+
},
504+
fakeNodeHandler: &testutil.FakeNodeHandler{
505+
Existing: []*v1.Node{
506+
{
507+
ObjectMeta: metav1.ObjectMeta{
508+
Name: "test",
509+
Labels: map[string]string{
510+
utilnode.NodePoolSubnetLabelPrefix: "default",
511+
utilnode.NodePoolPodRangeLabelPrefix: defaultSecondaryRangeA,
512+
},
513+
},
514+
Spec: v1.NodeSpec{
515+
ProviderID: "gce://test-project/us-central1-b/test",
516+
},
517+
Status: v1.NodeStatus{
518+
Capacity: v1.ResourceList{},
519+
},
520+
},
521+
},
522+
Clientset: fake.NewSimpleClientset(),
523+
},
524+
gceInstance: []*compute.Instance{
525+
{
526+
Name: "test",
527+
NetworkInterfaces: []*compute.NetworkInterface{
528+
interfaces(defaultVPCName, defaultVPCSubnetName, "80.1.172.1", []*compute.AliasIpRange{
529+
{IpCidrRange: "192.168.1.0/24", SubnetworkRangeName: defaultSecondaryRangeA},
530+
{IpCidrRange: "10.11.1.0/24", SubnetworkRangeName: defaultSecondaryRangeB},
531+
}),
532+
},
533+
},
534+
},
535+
nodeChanges: func(node *v1.Node) {
536+
node.Spec.PodCIDR = "192.168.1.0/24"
537+
node.Spec.PodCIDRs = []string{"192.168.1.0/24"}
538+
node.Status.Conditions = []v1.NodeCondition{
539+
{
540+
Type: "NetworkUnavailable",
541+
Status: "False",
542+
Reason: "RouteCreated",
543+
Message: "NodeController create implicit route",
544+
},
545+
}
546+
node.Annotations = map[string]string{
547+
networkv1.NorthInterfacesAnnotationKey: "[]",
548+
networkv1.MultiNetworkAnnotationKey: "[]",
549+
}
550+
},
551+
expectedUpdate: true,
552+
expectedMetrics: map[string]float64{},
553+
},
534554
{
535555
name: "[mn] one additional network along with default network",
536556
networks: []*networkv1.Network{

pkg/controller/nodeipam/ipam/multinetwork_cloud_cidr_allocator.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,19 @@ import (
1111
"k8s.io/apimachinery/pkg/api/resource"
1212
"k8s.io/apimachinery/pkg/labels"
1313
networkv1 "k8s.io/cloud-provider-gcp/crd/apis/network/v1"
14+
utilnode "k8s.io/cloud-provider-gcp/pkg/util/node"
1415
"k8s.io/klog/v2"
1516
netutils "k8s.io/utils/net"
1617
)
1718

1819
// performMultiNetworkCIDRAllocation receives the existing Node object and its
1920
// GCE interfaces, and is updated with the corresponding annotations for
2021
// MultiNetwork and NorthInterfaces and the capacity for the additional networks.
21-
// It also returns calculated cidrs for default Network.
22+
// It also returns calculated cidrs for default Network if there're no node labels.
2223
//
2324
// NorthInterfacesAnnotationKey is modified on Network Ready condition changes.
2425
// MultiNetworkAnnotationKey is modified on Node's NodeNetworkAnnotationKey changes.
25-
func (ca *cloudCIDRAllocator) performMultiNetworkCIDRAllocation(node *v1.Node, interfaces []*compute.NetworkInterface) (defaultNwCIDRs []string, err error) {
26+
func (ca *cloudCIDRAllocator) performMultiNetworkCIDRAllocation(node *v1.Node, interfaces []*compute.NetworkInterface, hasNodeLabels bool) (defaultNwCIDRs []string, err error) {
2627
northInterfaces := networkv1.NorthInterfacesAnnotation{}
2728
additionalNodeNetworks := networkv1.MultiNetworkAnnotation{}
2829

@@ -96,13 +97,16 @@ func (ca *cloudCIDRAllocator) performMultiNetworkCIDRAllocation(node *v1.Node, i
9697
}
9798
klog.V(2).InfoS("found an allocatable secondary range for the interface on network", "nodeName", node.Name, "networkName", network.Name)
9899
processedNetworks[network.Name] = struct{}{}
99-
if networkv1.IsDefaultNetwork(network.Name) {
100+
// for defaultNwCIDRs, if there're no NodeLabels keep this,
101+
// otherwise get the CIDR with labels
102+
if networkv1.IsDefaultNetwork(network.Name) && !hasNodeLabels {
100103
defaultNwCIDRs = append(defaultNwCIDRs, ipRange.IpCidrRange)
101104
ipv6Addr := ca.cloud.GetIPV6Address(inf)
102105
if ipv6Addr != nil {
103106
defaultNwCIDRs = append(defaultNwCIDRs, ipv6Addr.String())
104107
}
105-
} else {
108+
}
109+
if !networkv1.IsDefaultNetwork(network.Name) {
106110
northInterfaces = append(northInterfaces, networkv1.NorthInterface{Network: network.Name, IpAddress: inf.NetworkIP})
107111
if _, ok := upStatusNetworks[network.Name]; ok {
108112
additionalNodeNetworks = append(additionalNodeNetworks, networkv1.NodeNetwork{Name: network.Name, Scope: "host-local", Cidrs: []string{ipRange.IpCidrRange}})
@@ -118,6 +122,42 @@ func (ca *cloudCIDRAllocator) performMultiNetworkCIDRAllocation(node *v1.Node, i
118122
return defaultNwCIDRs, nil
119123
}
120124

125+
// getNodeDefaultLabels returns true if the node has labels for subnet and Pod range
126+
func getNodeDefaultLabels(node *v1.Node) (bool, string, string) {
127+
defaultSubnet, foundSubnet := node.Labels[utilnode.NodePoolSubnetLabelPrefix]
128+
defaultPodRange, foundRange := node.Labels[utilnode.NodePoolPodRangeLabelPrefix]
129+
if !foundSubnet || defaultSubnet == "" || !foundRange || defaultPodRange == "" {
130+
return false, "", ""
131+
}
132+
return true, defaultSubnet, defaultPodRange
133+
}
134+
135+
// extractDefaultNwCIDRs returns the Pod CIDRs for default Network.
136+
// Different subnet can have the same secondary range name, here uses the subnet and range name
137+
// to find the matching CIDR(s)
138+
func (ca *cloudCIDRAllocator) extractDefaultNwCIDRs(interfaces []*compute.NetworkInterface, defaultSubnet, defaultPodRange string) (defaultNwCIDRs []string) {
139+
out:
140+
for _, inf := range interfaces {
141+
// extra the subnetwork name from the URL
142+
parts := strings.Split(inf.Subnetwork, "/subnetworks/")
143+
if parts[1] != defaultSubnet {
144+
continue
145+
}
146+
for _, ipRange := range inf.AliasIpRanges {
147+
if ipRange.SubnetworkRangeName != defaultPodRange {
148+
continue
149+
}
150+
defaultNwCIDRs = append(defaultNwCIDRs, ipRange.IpCidrRange)
151+
ipv6Addr := ca.cloud.GetIPV6Address(inf)
152+
if ipv6Addr != nil {
153+
defaultNwCIDRs = append(defaultNwCIDRs, ipv6Addr.String())
154+
}
155+
break out
156+
}
157+
}
158+
return defaultNwCIDRs
159+
}
160+
121161
func updateAnnotations(node *v1.Node, northInterfaces networkv1.NorthInterfacesAnnotation, additionalNodeNetworks networkv1.MultiNetworkAnnotation) error {
122162
northInterfaceAnn, err := networkv1.MarshalNorthInterfacesAnnotation(northInterfaces)
123163
if err != nil {

pkg/util/node/node.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ import (
3030
"k8s.io/klog/v2"
3131
)
3232

33+
// Labels definitions.
34+
const (
35+
// NodePoolPodRangeLabelPrefix is the prefix for the default Pod range
36+
// name for the node and it can be different with cluster Pod range
37+
NodePoolPodRangeLabelPrefix = "cloud.google.com/gke-np-default-pod-range"
38+
// NodePoolSubnetLabelPrefix is the prefix for the default subnet
39+
// name for the node
40+
NodePoolSubnetLabelPrefix = "cloud.google.com/gke-np-default-subnet"
41+
)
42+
3343
type nodeForConditionPatch struct {
3444
Status nodeStatusForPatch `json:"status"`
3545
}

0 commit comments

Comments
 (0)