Skip to content

Commit 6544740

Browse files
lukasmetznerjooola
andauthored
feat(load-balancer): ignore nodes that don't use known provider IDs (#780)
With this feature the load balancer is more resilient to hybrid clusters as it skips nodes with unkown provider id prefixes. --------- Co-authored-by: Jonas L. <jooola@users.noreply.github.com>
1 parent 81cc8b2 commit 6544740

File tree

5 files changed

+88
-17
lines changed

5 files changed

+88
-17
lines changed

hcloud/instances.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,13 @@ func (i *instances) lookupServer(
113113
}
114114

115115
if cloudServer != nil && hrobotServer != nil {
116-
i.recorder.Eventf(node, corev1.EventTypeWarning, "InstanceLookupFailed", "Node %s could not be uniquely associated with a Cloud or Robot server, as a server with this name exists in both APIs", node.Name)
116+
i.recorder.Eventf(
117+
node,
118+
corev1.EventTypeWarning,
119+
"InstanceLookupFailed",
120+
"Node %s could not be uniquely associated with a Cloud or Robot server, as a server with this name exists in both APIs",
121+
node.Name,
122+
)
117123
return nil, fmt.Errorf("found both a cloud & robot server for name %q", node.Name)
118124
}
119125

internal/hcops/load_balancer.go

+12
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,18 @@ func (l *LoadBalancerOps) ReconcileHCLBTargets(
613613
for _, node := range nodes {
614614
id, isCloudServer, err := providerid.ToServerID(node.Spec.ProviderID)
615615
if err != nil {
616+
if errors.As(err, new(*providerid.UnkownPrefixError)) {
617+
// ProviderID has unknown prefix, cluster might have non-hccm nodes that can not be added to the
618+
// Load Balancer. Emitting an event and ignoring that Node in this reconciliation loop.
619+
l.Recorder.Eventf(
620+
node,
621+
corev1.EventTypeWarning,
622+
"UnknownProviderIDPrefix",
623+
"Node could not be added to Load Balancer for service %s because the provider ID does not match any known format",
624+
svc.Name,
625+
)
626+
continue
627+
}
616628
return changed, fmt.Errorf("%s: %w", op, err)
617629
}
618630
if isCloudServer {

internal/hcops/load_balancer_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,33 @@ func TestLoadBalancerOps_ReconcileHCLBTargets(t *testing.T) {
12861286
},
12871287
cfg: config.HCCMConfiguration{LoadBalancer: config.LoadBalancerConfiguration{DisableIPv6: true}},
12881288
},
1289+
{
1290+
name: "provider id does not have one of the the expected prefixes",
1291+
k8sNodes: []*corev1.Node{
1292+
{Spec: corev1.NodeSpec{ProviderID: "hcloud://1"}},
1293+
{Spec: corev1.NodeSpec{ProviderID: "mycloud://2"}},
1294+
},
1295+
initialLB: &hcloud.LoadBalancer{
1296+
ID: 5,
1297+
Targets: []hcloud.LoadBalancerTarget{
1298+
{
1299+
Type: hcloud.LoadBalancerTargetTypeServer,
1300+
Server: &hcloud.LoadBalancerTargetServer{Server: &hcloud.Server{ID: 1}},
1301+
},
1302+
},
1303+
LoadBalancerType: &hcloud.LoadBalancerType{
1304+
MaxTargets: 2,
1305+
},
1306+
},
1307+
mock: func(_ *testing.T, _ *LBReconcilementTestCase) {
1308+
// Nothing to mock because no action will be taken besides emitting an event
1309+
},
1310+
perform: func(t *testing.T, tt *LBReconcilementTestCase) {
1311+
changed, err := tt.fx.LBOps.ReconcileHCLBTargets(tt.fx.Ctx, tt.initialLB, tt.service, tt.k8sNodes)
1312+
assert.NoError(t, err)
1313+
assert.False(t, changed)
1314+
},
1315+
},
12891316
{
12901317
name: "enable use of private network via default",
12911318
cfg: config.HCCMConfiguration{

internal/providerid/providerid.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ const (
2424
prefixRobotLegacy = "hcloud://bm-"
2525
)
2626

27+
type UnkownPrefixError struct {
28+
ProviderID string
29+
}
30+
31+
func (e *UnkownPrefixError) Error() string {
32+
return fmt.Sprintf(
33+
"Provider ID does not have one of the the expected prefixes (%s, %s, %s): %s",
34+
prefixCloud,
35+
prefixRobot,
36+
prefixRobotLegacy,
37+
e.ProviderID,
38+
)
39+
}
40+
2741
// ToServerID parses the Cloud or Robot Server ID from a ProviderID.
2842
//
2943
// This method supports all formats for the ProviderID that were ever used.
@@ -44,7 +58,7 @@ func ToServerID(providerID string) (id int64, isCloudServer bool, err error) {
4458
idString = strings.ReplaceAll(providerID, prefixCloud, "")
4559

4660
default:
47-
return 0, false, fmt.Errorf("providerID does not have one of the the expected prefixes (%s, %s, %s): %s", prefixCloud, prefixRobot, prefixRobotLegacy, providerID)
61+
return 0, false, &UnkownPrefixError{providerID}
4862
}
4963

5064
if idString == "" {

internal/providerid/providerid_test.go

+27-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package providerid
22

33
import (
4+
"errors"
45
"strings"
56
"testing"
7+
8+
"github.com/stretchr/testify/assert"
69
)
710

811
func TestFromCloudServerID(t *testing.T) {
@@ -59,92 +62,101 @@ func TestToServerID(t *testing.T) {
5962
providerID string
6063
wantID int64
6164
wantIsCloudServer bool
62-
wantErr bool
65+
wantErr error
6366
}{
6467
{
6568
name: "[cloud] simple id",
6669
providerID: "hcloud://1234",
6770
wantID: 1234,
6871
wantIsCloudServer: true,
69-
wantErr: false,
72+
wantErr: nil,
7073
},
7174
{
7275
name: "[cloud] large id",
7376
providerID: "hcloud://2251799813685247",
7477
wantID: 2251799813685247,
7578
wantIsCloudServer: true,
76-
wantErr: false,
79+
wantErr: nil,
7780
},
7881
{
7982
name: "[cloud] invalid id",
8083
providerID: "hcloud://my-cloud",
8184
wantID: 0,
8285
wantIsCloudServer: false,
83-
wantErr: true,
86+
wantErr: errors.New("unable to parse server id: hcloud://my-cloud"),
8487
},
8588
{
8689
name: "[cloud] missing id",
8790
providerID: "hcloud://",
8891
wantID: 0,
8992
wantIsCloudServer: false,
90-
wantErr: true,
93+
wantErr: errors.New("providerID is missing a serverID: hcloud://"),
9194
},
9295
{
9396
name: "[robot] simple id",
9497
providerID: "hrobot://4321",
9598
wantID: 4321,
9699
wantIsCloudServer: false,
97-
wantErr: false,
100+
wantErr: nil,
98101
},
99102
{
100103
name: "[robot] invalid id",
101104
providerID: "hrobot://my-robot",
102105
wantID: 0,
103106
wantIsCloudServer: false,
104-
wantErr: true,
107+
wantErr: errors.New("unable to parse server id: hrobot://my-robot"),
105108
},
106109
{
107110
name: "[robot] missing id",
108111
providerID: "hrobot://",
109112
wantID: 0,
110113
wantIsCloudServer: false,
111-
wantErr: true,
114+
wantErr: errors.New("providerID is missing a serverID: hrobot://"),
112115
},
113116
{
114117
name: "[robot-syself] simple id",
115118
providerID: "hcloud://bm-4321",
116119
wantID: 4321,
117120
wantIsCloudServer: false,
118-
wantErr: false,
121+
wantErr: nil,
119122
},
120123
{
121124
name: "[robot-syself] invalid id",
122125
providerID: "hcloud://bm-my-robot",
123126
wantID: 0,
124127
wantIsCloudServer: false,
125-
wantErr: true,
128+
wantErr: errors.New("unable to parse server id: hcloud://bm-my-robot"),
126129
},
127130
{
128131
name: "[robot-syself] missing id",
129132
providerID: "hcloud://bm-",
130133
wantID: 0,
131134
wantIsCloudServer: false,
132-
wantErr: true,
135+
wantErr: errors.New("providerID is missing a serverID: hcloud://bm-"),
133136
},
134137
{
135138
name: "unknown format",
136139
providerID: "foobar/321",
137140
wantID: 0,
138141
wantIsCloudServer: false,
139-
wantErr: true,
142+
wantErr: &UnkownPrefixError{"foobar/321"},
140143
},
141144
}
142145
for _, tt := range tests {
143146
t.Run(tt.name, func(t *testing.T) {
144147
gotID, gotIsCloudServer, err := ToServerID(tt.providerID)
145-
if (err != nil) != tt.wantErr {
146-
t.Errorf("ToServerID() error = %v, wantErr %v", err, tt.wantErr)
147-
return
148+
if tt.wantErr != nil {
149+
if err == nil {
150+
t.Errorf("ToServerID() expected error = %v, got nil", tt.wantErr)
151+
return
152+
}
153+
if errors.As(tt.wantErr, new(*UnkownPrefixError)) {
154+
assert.ErrorAsf(t, err, new(*UnkownPrefixError), "ToServerID() error = %v, wantErr %v", err, tt.wantErr)
155+
} else {
156+
assert.EqualErrorf(t, err, tt.wantErr.Error(), "ToServerID() error = %v, wantErr %v", err, tt.wantErr)
157+
}
158+
} else if err != nil {
159+
t.Errorf("ToServerID() unexpected error = %v, wantErr nil", err)
148160
}
149161
if gotID != tt.wantID {
150162
t.Errorf("ToServerID() gotID = %v, want %v", gotID, tt.wantID)

0 commit comments

Comments
 (0)