From 25689d1c0808da73d94f8ad134a3f1c0e10d6dc0 Mon Sep 17 00:00:00 2001 From: Cody Bond Date: Thu, 1 May 2025 09:06:58 -0400 Subject: [PATCH] Error when node has no internal IP for nodebalancer backend --- cloud/linode/loadbalancers.go | 31 ++++++++++++------ cloud/linode/loadbalancers_test.go | 51 ++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index dc961203..fbd02b66 100644 --- a/cloud/linode/loadbalancers.go +++ b/cloud/linode/loadbalancers.go @@ -402,7 +402,11 @@ func (l *loadbalancers) updateNodeBalancer( subnetID = id } for _, node := range nodes { - newNodeOpts := l.buildNodeBalancerNodeConfigRebuildOptions(node, port.NodePort, subnetID) + newNodeOpts, err := l.buildNodeBalancerNodeConfigRebuildOptions(node, port.NodePort, subnetID) + if err != nil { + return err + } + oldNodeID, ok := oldNBNodeIDs[newNodeOpts.Address] if ok { newNodeOpts.ID = oldNodeID @@ -857,7 +861,12 @@ func (l *loadbalancers) buildLoadBalancerRequest(ctx context.Context, clusterNam createOpt := config.GetCreateOptions() for _, n := range nodes { - createOpt.Nodes = append(createOpt.Nodes, l.buildNodeBalancerNodeConfigRebuildOptions(n, port.NodePort, subnetID).NodeBalancerNodeCreateOptions) + opts, err := l.buildNodeBalancerNodeConfigRebuildOptions(n, port.NodePort, subnetID) + if err != nil { + return nil, fmt.Errorf("error creating NodeBalancer config: %w", err) + } + + createOpt.Nodes = append(createOpt.Nodes, opts.NodeBalancerNodeCreateOptions) } configs = append(configs, &createOpt) @@ -877,10 +886,14 @@ func coerceString(str string, minLen, maxLen int, padding string) string { return str } -func (l *loadbalancers) buildNodeBalancerNodeConfigRebuildOptions(node *v1.Node, nodePort int32, subnetID int) linodego.NodeBalancerConfigRebuildNodeOptions { +func (l *loadbalancers) buildNodeBalancerNodeConfigRebuildOptions(node *v1.Node, nodePort int32, subnetID int) (linodego.NodeBalancerConfigRebuildNodeOptions, error) { + privateIP, err := getNodePrivateIP(node, subnetID) + if err != nil { + return linodego.NodeBalancerConfigRebuildNodeOptions{}, err + } nodeOptions := linodego.NodeBalancerConfigRebuildNodeOptions{ NodeBalancerNodeCreateOptions: linodego.NodeBalancerNodeCreateOptions{ - Address: fmt.Sprintf("%v:%v", getNodePrivateIP(node, subnetID), nodePort), + Address: fmt.Sprintf("%v:%v", privateIP, nodePort), // NodeBalancer backends must be 3-32 chars in length // If < 3 chars, pad node name with "node-" prefix Label: coerceString(node.Name, 3, 32, "node-"), @@ -891,7 +904,7 @@ func (l *loadbalancers) buildNodeBalancerNodeConfigRebuildOptions(node *v1.Node, if subnetID != 0 { nodeOptions.NodeBalancerNodeCreateOptions.SubnetID = subnetID } - return nodeOptions + return nodeOptions, nil } func (l *loadbalancers) retrieveKubeClient() error { @@ -1004,20 +1017,20 @@ func getPortConfigAnnotation(service *v1.Service, port int) (portConfigAnnotatio // For services which don't have NodeBalancerBackendIPv4Range annotation, // Backend IP can be overwritten to the one specified using AnnLinodeNodePrivateIP // annotation over the NodeInternalIP. -func getNodePrivateIP(node *v1.Node, subnetID int) string { +func getNodePrivateIP(node *v1.Node, subnetID int) (string, error) { if subnetID == 0 { if address, exists := node.Annotations[annotations.AnnLinodeNodePrivateIP]; exists { - return address + return address, nil } } klog.Infof("Node %s, assigned IP addresses: %v", node.Name, node.Status.Addresses) for _, addr := range node.Status.Addresses { if addr.Type == v1.NodeInternalIP { - return addr.Address + return addr.Address, nil } } - return "" + return "", fmt.Errorf("node %s has no internal IP or %s annotation", node.Name, annotations.AnnLinodeNodePrivateIP) } func getTLSCertInfo(ctx context.Context, kubeClient kubernetes.Interface, namespace string, config portConfig) (string, string, error) { diff --git a/cloud/linode/loadbalancers_test.go b/cloud/linode/loadbalancers_test.go index 83cd431a..fbfc4c64 100644 --- a/cloud/linode/loadbalancers_test.go +++ b/cloud/linode/loadbalancers_test.go @@ -339,7 +339,19 @@ func testCreateNodeBalancer(t *testing.T, client *linodego.Client, _ *fakeAPI, a t.Error("type assertion failed") } nodes := []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "node-1"}}, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node-1", + }, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.1.1", + }, + }, + }, + }, } nb, err := lb.buildLoadBalancerRequest(t.Context(), "linodelb", svc, nodes) if err != nil { @@ -2949,6 +2961,7 @@ func Test_getNodePrivateIP(t *testing.T) { node *v1.Node address string subnetID int + err error }{ { "node internal ip specified", @@ -2964,6 +2977,7 @@ func Test_getNodePrivateIP(t *testing.T) { }, "127.0.0.1", 0, + nil, }, { "node internal ip not specified", @@ -2979,6 +2993,7 @@ func Test_getNodePrivateIP(t *testing.T) { }, "", 0, + fmt.Errorf("node has no internal IP or %s annotation", annotations.AnnLinodeNodePrivateIP), }, { "node internal ip annotation present", @@ -2999,6 +3014,7 @@ func Test_getNodePrivateIP(t *testing.T) { }, "192.168.42.42", 0, + nil, }, { "node internal ip annotation present and subnet id is not zero", @@ -3019,13 +3035,18 @@ func Test_getNodePrivateIP(t *testing.T) { }, "10.0.1.1", 100, + nil, }, } for _, test := range testcases { t.Run(test.name, func(t *testing.T) { - ip := getNodePrivateIP(test.node, test.subnetID) - if ip != test.address { + ip, err := getNodePrivateIP(test.node, test.subnetID) + if !reflect.DeepEqual(err, test.err) { + t.Error("unexpected error") + t.Logf("expected: %v", test.err) + t.Logf("actual: %v", err) + } else if ip != test.address { t.Error("unexpected certificate") t.Logf("expected: %q", test.address) t.Logf("actual: %q", ip) @@ -3061,16 +3082,40 @@ func testBuildLoadBalancerRequest(t *testing.T, client *linodego.Client, _ *fake ObjectMeta: metav1.ObjectMeta{ Name: "node-1", }, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.1.1", + }, + }, + }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "node-2", }, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.1.2", + }, + }, + }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "node-3", }, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.1.3", + }, + }, + }, }, }