Skip to content

Commit c17d3d0

Browse files
committed
feat(lb): implement TimeoutQueue, MaxConnections and FailoverHost on backends
1 parent 2fb1a32 commit c17d3d0

File tree

3 files changed

+174
-1
lines changed

3 files changed

+174
-1
lines changed

scaleway/loadbalancers.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,11 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S
10801080
return nil, err
10811081
}
10821082

1083+
timeoutQueue, err := getTimeoutQueue(service)
1084+
if err != nil {
1085+
return nil, err
1086+
}
1087+
10831088
onMarkedDownAction, err := getOnMarkedDownAction(service)
10841089
if err != nil {
10851090
return nil, err
@@ -1090,11 +1095,21 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S
10901095
return nil, err
10911096
}
10921097

1098+
maxConnections, err := getMaxConnections(service)
1099+
if err != nil {
1100+
return nil, err
1101+
}
1102+
10931103
maxRetries, err := getMaxRetries(service)
10941104
if err != nil {
10951105
return nil, err
10961106
}
10971107

1108+
failoverHost, err := getFailoverHost(service)
1109+
if err != nil {
1110+
return nil, err
1111+
}
1112+
10981113
healthCheck := &scwlb.HealthCheck{
10991114
Port: port.NodePort,
11001115
}
@@ -1195,10 +1210,13 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S
11951210
TimeoutServer: &timeoutServer,
11961211
TimeoutConnect: &timeoutConnect,
11971212
TimeoutTunnel: &timeoutTunnel,
1213+
TimeoutQueue: timeoutQueue,
11981214
OnMarkedDownAction: onMarkedDownAction,
11991215
HealthCheck: healthCheck,
12001216
RedispatchAttemptCount: redispatchAttemptCount,
1217+
MaxConnections: maxConnections,
12011218
MaxRetries: maxRetries,
1219+
FailoverHost: failoverHost,
12021220
}
12031221

12041222
if stickySessions == scwlb.StickySessionsTypeCookie {
@@ -1316,6 +1334,10 @@ func backendEquals(got, want *scwlb.Backend) bool {
13161334
klog.V(3).Infof("backend.TimeoutTunnel: %s - %s", got.TimeoutTunnel, want.TimeoutTunnel)
13171335
return false
13181336
}
1337+
if !durationPtrEqual(got.TimeoutQueue.ToTimeDuration(), want.TimeoutQueue.ToTimeDuration()) {
1338+
klog.V(3).Infof("backend.TimeoutQueue: %s - %s", ptrScwDurationToString(got.TimeoutQueue), ptrScwDurationToString(want.TimeoutQueue))
1339+
return false
1340+
}
13191341
if got.OnMarkedDownAction != want.OnMarkedDownAction {
13201342
klog.V(3).Infof("backend.OnMarkedDownAction: %s - %s", got.OnMarkedDownAction, want.OnMarkedDownAction)
13211343
return false
@@ -1324,6 +1346,10 @@ func backendEquals(got, want *scwlb.Backend) bool {
13241346
klog.V(3).Infof("backend.RedispatchAttemptCount: %s - %s", ptrInt32ToString(got.RedispatchAttemptCount), ptrInt32ToString(want.RedispatchAttemptCount))
13251347
return false
13261348
}
1349+
if !int32PtrEqual(got.MaxConnections, want.MaxConnections) {
1350+
klog.V(3).Infof("backend.MaxConnections: %s - %s", ptrInt32ToString(got.MaxConnections), ptrInt32ToString(want.MaxConnections))
1351+
return false
1352+
}
13271353
if !int32PtrEqual(got.MaxRetries, want.MaxRetries) {
13281354
klog.V(3).Infof("backend.MaxRetries: %s - %s", ptrInt32ToString(got.MaxRetries), ptrInt32ToString(want.MaxRetries))
13291355
return false
@@ -1333,6 +1359,11 @@ func backendEquals(got, want *scwlb.Backend) bool {
13331359
return false
13341360
}
13351361

1362+
if !ptrStringEqual(got.FailoverHost, want.FailoverHost) {
1363+
klog.V(3).Infof("backend.FailoverHost: %s - %s", ptrStringToString(got.FailoverHost), ptrStringToString(want.FailoverHost))
1364+
return false
1365+
}
1366+
13361367
if !reflect.DeepEqual(got.HealthCheck, want.HealthCheck) {
13371368
klog.V(3).Infof("backend.HealthCheck: %v - %v", got.HealthCheck, want.HealthCheck)
13381369
return false
@@ -1510,9 +1541,12 @@ func (l *loadbalancers) createBackend(service *v1.Service, loadbalancer *scwlb.L
15101541
TimeoutServer: backend.TimeoutServer,
15111542
TimeoutConnect: backend.TimeoutConnect,
15121543
TimeoutTunnel: backend.TimeoutTunnel,
1544+
TimeoutQueue: backend.TimeoutQueue,
15131545
OnMarkedDownAction: backend.OnMarkedDownAction,
15141546
RedispatchAttemptCount: backend.RedispatchAttemptCount,
1547+
MaxConnections: backend.MaxConnections,
15151548
MaxRetries: backend.MaxRetries,
1549+
FailoverHost: backend.FailoverHost,
15161550
})
15171551
if err != nil {
15181552
return nil, err
@@ -1538,9 +1572,12 @@ func (l *loadbalancers) updateBackend(service *v1.Service, loadbalancer *scwlb.L
15381572
TimeoutServer: backend.TimeoutServer,
15391573
TimeoutConnect: backend.TimeoutConnect,
15401574
TimeoutTunnel: backend.TimeoutTunnel,
1575+
TimeoutQueue: backend.TimeoutQueue,
15411576
OnMarkedDownAction: backend.OnMarkedDownAction,
15421577
RedispatchAttemptCount: backend.RedispatchAttemptCount,
1578+
MaxConnections: backend.MaxConnections,
15431579
MaxRetries: backend.MaxRetries,
1580+
FailoverHost: backend.FailoverHost,
15441581
})
15451582
if err != nil {
15461583
return nil, err
@@ -1608,6 +1645,17 @@ func stringArrayEqual(got, want []string) bool {
16081645
return reflect.DeepEqual(got, want)
16091646
}
16101647

1648+
// ptrStringEqual returns true if both strings are equal
1649+
func ptrStringEqual(got, want *string) bool {
1650+
if got == nil && want == nil {
1651+
return true
1652+
}
1653+
if got == nil || want == nil {
1654+
return false
1655+
}
1656+
return *got == *want
1657+
}
1658+
16111659
// stringPtrArrayEqual returns true if both arrays contains the exact same elements regardless of the order
16121660
func stringPtrArrayEqual(got, want []*string) bool {
16131661
slices.SortStableFunc(got, func(a, b *string) int { return strings.Compare(*a, *b) })
@@ -1799,3 +1847,17 @@ func nodesInitialized(nodes []*v1.Node) error {
17991847

18001848
return nil
18011849
}
1850+
1851+
func ptrStringToString(s *string) string {
1852+
if s == nil {
1853+
return "<nil>"
1854+
}
1855+
return *s
1856+
}
1857+
1858+
func ptrScwDurationToString(i *scw.Duration) string {
1859+
if i == nil {
1860+
return "<nil>"
1861+
}
1862+
return i.ToTimeDuration().String()
1863+
}

scaleway/loadbalancers_annotations.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ const (
124124
// The default value is "10m". The duration are go's time.Duration (ex: "1s", "2m", "4h", ...)
125125
serviceAnnotationLoadBalancerTimeoutTunnel = "service.beta.kubernetes.io/scw-loadbalancer-timeout-tunnel"
126126

127+
// serviceAnnotationLoadBalancerTimeoutQueue is the maximum time for a request to be left pending in queue when max_connections is reached.
128+
// The duration are go's time.Duration (ex: "1s", "2m", "4h", ...)
129+
serviceAnnotationLoadBalancerTimeoutQueue = "service.beta.kubernetes.io/scw-loadbalancer-timeout-queue"
130+
127131
// serviceAnnotationLoadBalancerOnMarkedDownAction is the annotation that modifes what occurs when a backend server is marked down
128132
// The default value is "on_marked_down_action_none" and the possible values are "on_marked_down_action_none" and "shutdown_sessions"
129133
serviceAnnotationLoadBalancerOnMarkedDownAction = "service.beta.kubernetes.io/scw-loadbalancer-on-marked-down-action"
@@ -168,10 +172,16 @@ const (
168172
// The default value is "0", which disable the redispatch
169173
serviceAnnotationLoadBalancerRedispatchAttemptCount = "service.beta.kubernetes.io/scw-loadbalancer-redispatch-attempt-count"
170174

175+
// serviceAnnotationLoadBalancerMaxConnections is the annotation to configure the number of connections
176+
serviceAnnotationLoadBalancerMaxConnections = "service.beta.kubernetes.io/scw-loadbalancer-max-connections"
177+
171178
// serviceAnnotationLoadBalancerMaxRetries is the annotation to configure the number of retry on connection failure
172179
// The default value is 3.
173180
serviceAnnotationLoadBalancerMaxRetries = "service.beta.kubernetes.io/scw-loadbalancer-max-retries"
174181

182+
// serviceAnnotationLoadBalancerFailoverHost is the annotation to specify the Scaleway Object Storage bucket website to be served as failover if all backend servers are down, e.g. failover-website.s3-website.fr-par.scw.cloud.
183+
serviceAnnotationLoadBalancerFailoverHost = "service.beta.kubernetes.io/scw-loadbalancer-failover-host"
184+
175185
// serviceAnnotationLoadBalancerPrivate is the annotation to configure the LB to be private or public
176186
// The LB will be public if unset or false.
177187
serviceAnnotationLoadBalancerPrivate = "service.beta.kubernetes.io/scw-loadbalancer-private"
@@ -422,6 +432,21 @@ func getTimeoutTunnel(service *v1.Service) (time.Duration, error) {
422432
return timeoutTunnelDuration, nil
423433
}
424434

435+
func getTimeoutQueue(service *v1.Service) (*scw.Duration, error) {
436+
timeoutQueue, ok := service.Annotations[serviceAnnotationLoadBalancerTimeoutQueue]
437+
if !ok {
438+
return nil, nil
439+
}
440+
441+
timeoutQueueDuration, err := time.ParseDuration(timeoutQueue)
442+
if err != nil {
443+
klog.Errorf("invalid value for annotation %s", serviceAnnotationLoadBalancerTimeoutQueue)
444+
return nil, errLoadBalancerInvalidAnnotation
445+
}
446+
447+
return scw.NewDurationFromTimeDuration(timeoutQueueDuration), nil
448+
}
449+
425450
func getOnMarkedDownAction(service *v1.Service) (scwlb.OnMarkedDownAction, error) {
426451
onMarkedDownAction, ok := service.Annotations[serviceAnnotationLoadBalancerOnMarkedDownAction]
427452
if !ok {
@@ -454,6 +479,21 @@ func getRedisatchAttemptCount(service *v1.Service) (*int32, error) {
454479
return &redispatchAttemptCountInt32, nil
455480
}
456481

482+
func getMaxConnections(service *v1.Service) (*int32, error) {
483+
maxConnectionsCount, ok := service.Annotations[serviceAnnotationLoadBalancerMaxConnections]
484+
if !ok {
485+
return nil, nil
486+
}
487+
maxConnectionsCountInt, err := strconv.Atoi(maxConnectionsCount)
488+
if err != nil {
489+
klog.Errorf("invalid value for annotation %s", serviceAnnotationLoadBalancerMaxConnections)
490+
return nil, errLoadBalancerInvalidAnnotation
491+
492+
}
493+
maxConnectionsCountInt32 := int32(maxConnectionsCountInt)
494+
return &maxConnectionsCountInt32, nil
495+
}
496+
457497
func getMaxRetries(service *v1.Service) (*int32, error) {
458498
maxRetriesCount, ok := service.Annotations[serviceAnnotationLoadBalancerMaxRetries]
459499
if !ok {
@@ -470,6 +510,14 @@ func getMaxRetries(service *v1.Service) (*int32, error) {
470510
return &maxRetriesCountInt32, nil
471511
}
472512

513+
func getFailoverHost(service *v1.Service) (*string, error) {
514+
failoverHost, ok := service.Annotations[serviceAnnotationLoadBalancerFailoverHost]
515+
if !ok {
516+
return nil, nil
517+
}
518+
return &failoverHost, nil
519+
}
520+
473521
func getHealthCheckDelay(service *v1.Service) (time.Duration, error) {
474522
healthCheckDelay, ok := service.Annotations[serviceAnnotationLoadBalancerHealthCheckDelay]
475523
if !ok {

scaleway/loadbalancers_test.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,13 @@ func TestBackendEquals(t *testing.T) {
747747
defaultTimeoutServer, _ := time.ParseDuration("3s")
748748
defaultTimeoutConnect, _ := time.ParseDuration("4s")
749749
defaultTimeoutTunnel, _ := time.ParseDuration("5s")
750+
defaultTimeoutQueueTime, _ := time.ParseDuration("5s")
751+
defaultTimeoutQueue := scw.NewDurationFromTimeDuration(defaultTimeoutQueueTime)
750752
otherDuration, _ := time.ParseDuration("50m")
753+
defaultString := "default"
754+
otherString := "other"
751755
boolTrue := true
756+
boolFalse := false
752757
var int0 int32 = 0
753758
var int1 int32 = 1
754759
var intOther int32 = 5
@@ -782,10 +787,13 @@ func TestBackendEquals(t *testing.T) {
782787
TimeoutServer: &defaultTimeoutServer,
783788
TimeoutConnect: &defaultTimeoutConnect,
784789
TimeoutTunnel: &defaultTimeoutTunnel,
790+
TimeoutQueue: defaultTimeoutQueue,
785791
OnMarkedDownAction: "action",
786792
ProxyProtocol: "proxy",
787793
RedispatchAttemptCount: &int0,
794+
MaxConnections: &int1,
788795
MaxRetries: &int1,
796+
FailoverHost: &defaultString,
789797
}
790798

791799
matrix := []struct {
@@ -856,6 +864,15 @@ func TestBackendEquals(t *testing.T) {
856864
want bool
857865
}{"with a different ForwardProtocol", reference, diff, false})
858866

867+
diff = deepCloneBackend(reference)
868+
diff.MaxConnections = &intOther
869+
matrix = append(matrix, struct {
870+
Name string
871+
a *scwlb.Backend
872+
b *scwlb.Backend
873+
want bool
874+
}{"with a different MaxConnections", reference, diff, false})
875+
859876
diff = deepCloneBackend(reference)
860877
diff.MaxRetries = &intOther
861878
matrix = append(matrix, struct {
@@ -946,6 +963,24 @@ func TestBackendEquals(t *testing.T) {
946963
want bool
947964
}{"with a different TimeoutTunnel", reference, diff, false})
948965

966+
diff = deepCloneBackend(reference)
967+
diff.TimeoutQueue = scw.NewDurationFromTimeDuration(otherDuration)
968+
matrix = append(matrix, struct {
969+
Name string
970+
a *scwlb.Backend
971+
b *scwlb.Backend
972+
want bool
973+
}{"with a different TimeoutQueue", reference, diff, false})
974+
975+
diff = deepCloneBackend(reference)
976+
diff.FailoverHost = &otherString
977+
matrix = append(matrix, struct {
978+
Name string
979+
a *scwlb.Backend
980+
b *scwlb.Backend
981+
want bool
982+
}{"with a different FailoverHost", reference, diff, false})
983+
949984
httpRef := deepCloneBackend(reference)
950985
httpRef.HealthCheck.TCPConfig = nil
951986
httpRef.HealthCheck.HTTPConfig = &scwlb.HealthCheckHTTPConfig{
@@ -1111,7 +1146,35 @@ func TestInt32PtrEqual(t *testing.T) {
11111146
t.Run(tt.name, func(t *testing.T) {
11121147
got := int32PtrEqual(tt.a, tt.b)
11131148
if got != tt.want {
1114-
t.Errorf("want: %v, got: %v", got, tt.want)
1149+
t.Errorf("want: %v, got: %v", tt.want, got)
1150+
}
1151+
})
1152+
}
1153+
}
1154+
func TestStrPtrEqual(t *testing.T) {
1155+
var str1 string = "test"
1156+
var otherStr1 string = "test"
1157+
var str2 string = "test2"
1158+
1159+
matrix := []struct {
1160+
name string
1161+
a *string
1162+
b *string
1163+
want bool
1164+
}{
1165+
{"same", &str1, &str1, true},
1166+
{"same with different ptr", &str1, &otherStr1, true},
1167+
{"with first nil", &str1, nil, false},
1168+
{"with last nil", nil, &str1, false},
1169+
{"with both nil", nil, nil, true},
1170+
{"with different values", &str1, &str2, false},
1171+
}
1172+
1173+
for _, tt := range matrix {
1174+
t.Run(tt.name, func(t *testing.T) {
1175+
got := ptrStringEqual(tt.a, tt.b)
1176+
if got != tt.want {
1177+
t.Errorf("want: %v, got: %v", tt.want, got)
11151178
}
11161179
})
11171180
}

0 commit comments

Comments
 (0)