From 2c9e60a8428468eec8e011392fa6d2b75a8cbb36 Mon Sep 17 00:00:00 2001 From: Will Hegedus Date: Wed, 8 Mar 2023 14:50:35 -0500 Subject: [PATCH 1/2] feat: truncate long labels in nodebalancers Nodebalancer backends can have a max label length of 32 chars. If a k8s node's name is longer than 32 chars, creating a nodebalancer fails. Example error: E0307 22:31:47.275176 1 controller.go:307] error processing service traefik/traefik (will retry): failed to ensure load balancer: [400] [configs[0].nodes[0].label] Length must be 3-32 characters; [configs[0].nodes[1].label] Length must be 3-32 characters; [configs[0].nodes[2].label] Length must be 3-32 characters; --- cloud/linode/loadbalancers.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index 95da8fa7..f7107946 100644 --- a/cloud/linode/loadbalancers.go +++ b/cloud/linode/loadbalancers.go @@ -644,12 +644,20 @@ func (l *loadbalancers) buildLoadBalancerRequest(ctx context.Context, clusterNam return l.createNodeBalancer(ctx, clusterName, service, configs) } +func trimString(s string, maxLen int) string { + if len(s) > maxLen { + return s[:maxLen] + } + return s +} + func (l *loadbalancers) buildNodeBalancerNodeCreateOptions(node *v1.Node, nodePort int32) linodego.NodeBalancerNodeCreateOptions { return linodego.NodeBalancerNodeCreateOptions{ Address: fmt.Sprintf("%v:%v", getNodePrivateIP(node), nodePort), - Label: node.Name, - Mode: "accept", - Weight: 100, + // NodeBalancer backends must be 3-32 chars in length + Label: trimString(node.Name, 32), + Mode: "accept", + Weight: 100, } } From 7d957dce6887ae3e66321ff3b8d03ad21bb97e09 Mon Sep 17 00:00:00 2001 From: Will Hegedus Date: Tue, 28 Nov 2023 13:47:31 -0500 Subject: [PATCH 2/2] feat: pad nodebalancer backend names Nodebalancers must have a name that is 3-32 characters in length. This adds support to pad the node name if it's too short, since in Kubernetes it's valid to have node names of only 1 or 2 characters. --- cloud/linode/loadbalancers.go | 10 +++++-- cloud/linode/loadbalancers_test.go | 42 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index f7107946..7cd1a0c5 100644 --- a/cloud/linode/loadbalancers.go +++ b/cloud/linode/loadbalancers.go @@ -644,9 +644,14 @@ func (l *loadbalancers) buildLoadBalancerRequest(ctx context.Context, clusterNam return l.createNodeBalancer(ctx, clusterName, service, configs) } -func trimString(s string, maxLen int) string { +func coerceString(s string, minLen, maxLen int, padding string) string { + if len(padding) == 0 { + padding = "x" + } if len(s) > maxLen { return s[:maxLen] + } else if len(s) < minLen { + return coerceString(fmt.Sprintf("%s%s", padding, s), minLen, maxLen, padding) } return s } @@ -655,7 +660,8 @@ func (l *loadbalancers) buildNodeBalancerNodeCreateOptions(node *v1.Node, nodePo return linodego.NodeBalancerNodeCreateOptions{ Address: fmt.Sprintf("%v:%v", getNodePrivateIP(node), nodePort), // NodeBalancer backends must be 3-32 chars in length - Label: trimString(node.Name, 32), + // If < 3 chars, pad node name with "node-" prefix + Label: coerceString(node.Name, 3, 32, "node-"), Mode: "accept", Weight: 100, } diff --git a/cloud/linode/loadbalancers_test.go b/cloud/linode/loadbalancers_test.go index 63afdcc7..489a2e7d 100644 --- a/cloud/linode/loadbalancers_test.go +++ b/cloud/linode/loadbalancers_test.go @@ -2114,3 +2114,45 @@ func addTLSSecret(t *testing.T, kubeClient kubernetes.Interface) { t.Fatalf("failed to add TLS secret: %s\n", err) } } + +func Test_LoadbalNodeNameCoercion(t *testing.T) { + type testCase struct { + nodeName string + padding string + expectedOutput string + } + testCases := []testCase{ + { + nodeName: "n", + padding: "z", + expectedOutput: "zzn", + }, + { + nodeName: "n", + padding: "node-", + expectedOutput: "node-n", + }, + { + nodeName: "n", + padding: "", + expectedOutput: "xxn", + }, + { + nodeName: "infra-logging-controlplane-3-atl1-us-prod", + padding: "node-", + expectedOutput: "infra-logging-controlplane-3-atl", + }, + { + nodeName: "node1", + padding: "node-", + expectedOutput: "node1", + }, + } + + for _, tc := range testCases { + if out := coerceString(tc.nodeName, 3, 32, tc.padding); out != tc.expectedOutput { + t.Fatalf("Expected loadbal backend name to be %s (got: %s)", tc.expectedOutput, out) + } + } + +}