Skip to content

Commit a4a2632

Browse files
committed
feat(lb): implement TimeoutQueue, MaxConnections and FailoverHost on backends
1 parent 59b3d96 commit a4a2632

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
@@ -1018,6 +1018,11 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S
10181018
return nil, err
10191019
}
10201020

1021+
timeoutQueue, err := getTimeoutQueue(service)
1022+
if err != nil {
1023+
return nil, err
1024+
}
1025+
10211026
onMarkedDownAction, err := getOnMarkedDownAction(service)
10221027
if err != nil {
10231028
return nil, err
@@ -1028,11 +1033,21 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S
10281033
return nil, err
10291034
}
10301035

1036+
maxConnections, err := getMaxConnections(service)
1037+
if err != nil {
1038+
return nil, err
1039+
}
1040+
10311041
maxRetries, err := getMaxRetries(service)
10321042
if err != nil {
10331043
return nil, err
10341044
}
10351045

1046+
failoverHost, err := getFailoverHost(service)
1047+
if err != nil {
1048+
return nil, err
1049+
}
1050+
10361051
healthCheck := &scwlb.HealthCheck{
10371052
Port: port.NodePort,
10381053
}
@@ -1133,10 +1148,13 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S
11331148
TimeoutServer: &timeoutServer,
11341149
TimeoutConnect: &timeoutConnect,
11351150
TimeoutTunnel: &timeoutTunnel,
1151+
TimeoutQueue: timeoutQueue,
11361152
OnMarkedDownAction: onMarkedDownAction,
11371153
HealthCheck: healthCheck,
11381154
RedispatchAttemptCount: redispatchAttemptCount,
1155+
MaxConnections: maxConnections,
11391156
MaxRetries: maxRetries,
1157+
FailoverHost: failoverHost,
11401158
}
11411159

11421160
if stickySessions == scwlb.StickySessionsTypeCookie {
@@ -1254,6 +1272,10 @@ func backendEquals(got, want *scwlb.Backend) bool {
12541272
klog.V(3).Infof("backend.TimeoutTunnel: %s - %s", got.TimeoutTunnel, want.TimeoutTunnel)
12551273
return false
12561274
}
1275+
if !durationPtrEqual(got.TimeoutQueue.ToTimeDuration(), want.TimeoutQueue.ToTimeDuration()) {
1276+
klog.V(3).Infof("backend.TimeoutQueue: %s - %s", ptrScwDurationToString(got.TimeoutQueue), ptrScwDurationToString(want.TimeoutQueue))
1277+
return false
1278+
}
12571279
if got.OnMarkedDownAction != want.OnMarkedDownAction {
12581280
klog.V(3).Infof("backend.OnMarkedDownAction: %s - %s", got.OnMarkedDownAction, want.OnMarkedDownAction)
12591281
return false
@@ -1262,6 +1284,10 @@ func backendEquals(got, want *scwlb.Backend) bool {
12621284
klog.V(3).Infof("backend.RedispatchAttemptCount: %s - %s", ptrInt32ToString(got.RedispatchAttemptCount), ptrInt32ToString(want.RedispatchAttemptCount))
12631285
return false
12641286
}
1287+
if !int32PtrEqual(got.MaxConnections, want.MaxConnections) {
1288+
klog.V(3).Infof("backend.MaxConnections: %s - %s", ptrInt32ToString(got.MaxConnections), ptrInt32ToString(want.MaxConnections))
1289+
return false
1290+
}
12651291
if !int32PtrEqual(got.MaxRetries, want.MaxRetries) {
12661292
klog.V(3).Infof("backend.MaxRetries: %s - %s", ptrInt32ToString(got.MaxRetries), ptrInt32ToString(want.MaxRetries))
12671293
return false
@@ -1271,6 +1297,11 @@ func backendEquals(got, want *scwlb.Backend) bool {
12711297
return false
12721298
}
12731299

1300+
if !ptrStringEqual(got.FailoverHost, want.FailoverHost) {
1301+
klog.V(3).Infof("backend.FailoverHost: %s - %s", ptrStringToString(got.FailoverHost), ptrStringToString(want.FailoverHost))
1302+
return false
1303+
}
1304+
12741305
if !reflect.DeepEqual(got.HealthCheck, want.HealthCheck) {
12751306
klog.V(3).Infof("backend.HealthCheck: %v - %v", got.HealthCheck, want.HealthCheck)
12761307
return false
@@ -1448,9 +1479,12 @@ func (l *loadbalancers) createBackend(service *v1.Service, loadbalancer *scwlb.L
14481479
TimeoutServer: backend.TimeoutServer,
14491480
TimeoutConnect: backend.TimeoutConnect,
14501481
TimeoutTunnel: backend.TimeoutTunnel,
1482+
TimeoutQueue: backend.TimeoutQueue,
14511483
OnMarkedDownAction: backend.OnMarkedDownAction,
14521484
RedispatchAttemptCount: backend.RedispatchAttemptCount,
1485+
MaxConnections: backend.MaxConnections,
14531486
MaxRetries: backend.MaxRetries,
1487+
FailoverHost: backend.FailoverHost,
14541488
})
14551489
if err != nil {
14561490
return nil, err
@@ -1476,9 +1510,12 @@ func (l *loadbalancers) updateBackend(service *v1.Service, loadbalancer *scwlb.L
14761510
TimeoutServer: backend.TimeoutServer,
14771511
TimeoutConnect: backend.TimeoutConnect,
14781512
TimeoutTunnel: backend.TimeoutTunnel,
1513+
TimeoutQueue: backend.TimeoutQueue,
14791514
OnMarkedDownAction: backend.OnMarkedDownAction,
14801515
RedispatchAttemptCount: backend.RedispatchAttemptCount,
1516+
MaxConnections: backend.MaxConnections,
14811517
MaxRetries: backend.MaxRetries,
1518+
FailoverHost: backend.FailoverHost,
14821519
})
14831520
if err != nil {
14841521
return nil, err
@@ -1546,6 +1583,17 @@ func stringArrayEqual(got, want []string) bool {
15461583
return reflect.DeepEqual(got, want)
15471584
}
15481585

1586+
// ptrStringEqual returns true if both strings are equal
1587+
func ptrStringEqual(got, want *string) bool {
1588+
if got == nil && want == nil {
1589+
return true
1590+
}
1591+
if got == nil || want == nil {
1592+
return false
1593+
}
1594+
return *got == *want
1595+
}
1596+
15491597
// stringPtrArrayEqual returns true if both arrays contains the exact same elements regardless of the order
15501598
func stringPtrArrayEqual(got, want []*string) bool {
15511599
slices.SortStableFunc(got, func(a, b *string) int { return strings.Compare(*a, *b) })
@@ -1675,3 +1723,17 @@ func ptrBoolToString(b *bool) string {
16751723
}
16761724
return fmt.Sprintf("%t", *b)
16771725
}
1726+
1727+
func ptrStringToString(s *string) string {
1728+
if s == nil {
1729+
return "<nil>"
1730+
}
1731+
return *s
1732+
}
1733+
1734+
func ptrScwDurationToString(i *scw.Duration) string {
1735+
if i == nil {
1736+
return "<nil>"
1737+
}
1738+
return fmt.Sprintf("%s", i.ToTimeDuration().String())
1739+
}

scaleway/loadbalancers_annotations.go

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

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

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

181+
// 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.
182+
serviceAnnotationLoadBalancerFailoverHost = "service.beta.kubernetes.io/scw-loadbalancer-failover-host"
183+
174184
// serviceAnnotationLoadBalancerPrivate is the annotation to configure the LB to be private or public
175185
// The LB will be public if unset or false.
176186
serviceAnnotationLoadBalancerPrivate = "service.beta.kubernetes.io/scw-loadbalancer-private"
@@ -394,6 +404,21 @@ func getTimeoutTunnel(service *v1.Service) (time.Duration, error) {
394404
return timeoutTunnelDuration, nil
395405
}
396406

407+
func getTimeoutQueue(service *v1.Service) (*scw.Duration, error) {
408+
timeoutQueue, ok := service.Annotations[serviceAnnotationLoadBalancerTimeoutQueue]
409+
if !ok {
410+
return nil, nil
411+
}
412+
413+
timeoutQueueDuration, err := time.ParseDuration(timeoutQueue)
414+
if err != nil {
415+
klog.Errorf("invalid value for annotation %s", serviceAnnotationLoadBalancerTimeoutQueue)
416+
return nil, errLoadBalancerInvalidAnnotation
417+
}
418+
419+
return scw.NewDurationFromTimeDuration(timeoutQueueDuration), nil
420+
}
421+
397422
func getOnMarkedDownAction(service *v1.Service) (scwlb.OnMarkedDownAction, error) {
398423
onMarkedDownAction, ok := service.Annotations[serviceAnnotationLoadBalancerOnMarkedDownAction]
399424
if !ok {
@@ -426,6 +451,21 @@ func getRedisatchAttemptCount(service *v1.Service) (*int32, error) {
426451
return &redispatchAttemptCountInt32, nil
427452
}
428453

454+
func getMaxConnections(service *v1.Service) (*int32, error) {
455+
maxConnectionsCount, ok := service.Annotations[serviceAnnotationLoadBalancerMaxConnections]
456+
if !ok {
457+
return nil, nil
458+
}
459+
maxConnectionsCountInt, err := strconv.Atoi(maxConnectionsCount)
460+
if err != nil {
461+
klog.Errorf("invalid value for annotation %s", serviceAnnotationLoadBalancerMaxConnections)
462+
return nil, errLoadBalancerInvalidAnnotation
463+
464+
}
465+
maxConnectionsCountInt32 := int32(maxConnectionsCountInt)
466+
return &maxConnectionsCountInt32, nil
467+
}
468+
429469
func getMaxRetries(service *v1.Service) (*int32, error) {
430470
maxRetriesCount, ok := service.Annotations[serviceAnnotationLoadBalancerMaxRetries]
431471
if !ok {
@@ -442,6 +482,14 @@ func getMaxRetries(service *v1.Service) (*int32, error) {
442482
return &maxRetriesCountInt32, nil
443483
}
444484

485+
func getFailoverHost(service *v1.Service) (*string, error) {
486+
failoverHost, ok := service.Annotations[serviceAnnotationLoadBalancerFailoverHost]
487+
if !ok {
488+
return nil, nil
489+
}
490+
return &failoverHost, nil
491+
}
492+
445493
func getHealthCheckDelay(service *v1.Service) (time.Duration, error) {
446494
healthCheckDelay, ok := service.Annotations[serviceAnnotationLoadBalancerHealthCheckDelay]
447495
if !ok {

scaleway/loadbalancers_test.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,8 +744,13 @@ func TestBackendEquals(t *testing.T) {
744744
defaultTimeoutServer, _ := time.ParseDuration("3s")
745745
defaultTimeoutConnect, _ := time.ParseDuration("4s")
746746
defaultTimeoutTunnel, _ := time.ParseDuration("5s")
747+
defaultTimeoutQueueTime, _ := time.ParseDuration("5s")
748+
defaultTimeoutQueue := scw.NewDurationFromTimeDuration(defaultTimeoutQueueTime)
747749
otherDuration, _ := time.ParseDuration("50m")
750+
defaultString := "default"
751+
otherString := "other"
748752
boolTrue := true
753+
boolFalse := false
749754
var int0 int32 = 0
750755
var int1 int32 = 1
751756
var intOther int32 = 5
@@ -779,10 +784,13 @@ func TestBackendEquals(t *testing.T) {
779784
TimeoutServer: &defaultTimeoutServer,
780785
TimeoutConnect: &defaultTimeoutConnect,
781786
TimeoutTunnel: &defaultTimeoutTunnel,
787+
TimeoutQueue: defaultTimeoutQueue,
782788
OnMarkedDownAction: "action",
783789
ProxyProtocol: "proxy",
784790
RedispatchAttemptCount: &int0,
791+
MaxConnections: &int1,
785792
MaxRetries: &int1,
793+
FailoverHost: &defaultString,
786794
}
787795

788796
matrix := []struct {
@@ -853,6 +861,15 @@ func TestBackendEquals(t *testing.T) {
853861
want bool
854862
}{"with a different ForwardProtocol", reference, diff, false})
855863

864+
diff = deepCloneBackend(reference)
865+
diff.MaxConnections = &intOther
866+
matrix = append(matrix, struct {
867+
Name string
868+
a *scwlb.Backend
869+
b *scwlb.Backend
870+
want bool
871+
}{"with a different MaxConnections", reference, diff, false})
872+
856873
diff = deepCloneBackend(reference)
857874
diff.MaxRetries = &intOther
858875
matrix = append(matrix, struct {
@@ -943,6 +960,24 @@ func TestBackendEquals(t *testing.T) {
943960
want bool
944961
}{"with a different TimeoutTunnel", reference, diff, false})
945962

963+
diff = deepCloneBackend(reference)
964+
diff.TimeoutQueue = scw.NewDurationFromTimeDuration(otherDuration)
965+
matrix = append(matrix, struct {
966+
Name string
967+
a *scwlb.Backend
968+
b *scwlb.Backend
969+
want bool
970+
}{"with a different TimeoutQueue", reference, diff, false})
971+
972+
diff = deepCloneBackend(reference)
973+
diff.FailoverHost = &otherString
974+
matrix = append(matrix, struct {
975+
Name string
976+
a *scwlb.Backend
977+
b *scwlb.Backend
978+
want bool
979+
}{"with a different FailoverHost", reference, diff, false})
980+
946981
httpRef := deepCloneBackend(reference)
947982
httpRef.HealthCheck.TCPConfig = nil
948983
httpRef.HealthCheck.HTTPConfig = &scwlb.HealthCheckHTTPConfig{
@@ -1108,7 +1143,35 @@ func TestInt32PtrEqual(t *testing.T) {
11081143
t.Run(tt.name, func(t *testing.T) {
11091144
got := int32PtrEqual(tt.a, tt.b)
11101145
if got != tt.want {
1111-
t.Errorf("want: %v, got: %v", got, tt.want)
1146+
t.Errorf("want: %v, got: %v", tt.want, got)
1147+
}
1148+
})
1149+
}
1150+
}
1151+
func TestStrPtrEqual(t *testing.T) {
1152+
var str1 string = "test"
1153+
var otherStr1 string = "test"
1154+
var str2 string = "test2"
1155+
1156+
matrix := []struct {
1157+
name string
1158+
a *string
1159+
b *string
1160+
want bool
1161+
}{
1162+
{"same", &str1, &str1, true},
1163+
{"same with different ptr", &str1, &otherStr1, true},
1164+
{"with first nil", &str1, nil, false},
1165+
{"with last nil", nil, &str1, false},
1166+
{"with both nil", nil, nil, true},
1167+
{"with different values", &str1, &str2, false},
1168+
}
1169+
1170+
for _, tt := range matrix {
1171+
t.Run(tt.name, func(t *testing.T) {
1172+
got := ptrStringEqual(tt.a, tt.b)
1173+
if got != tt.want {
1174+
t.Errorf("want: %v, got: %v", tt.want, got)
11121175
}
11131176
})
11141177
}

0 commit comments

Comments
 (0)